diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index e7a8c98d26..44c1ddf739 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -1,5 +1,4 @@ FROM mcr.microsoft.com/devcontainers/python:3.12 -# [Optional] Uncomment this section to install additional OS packages. -# RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \ -# && apt-get -y install --no-install-recommends +RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \ + && apt-get -y install libgmp-dev libmpfr-dev libmpc-dev diff --git a/.devcontainer/README.md b/.devcontainer/README.md index df12a3c2d6..2b18630a21 100644 --- a/.devcontainer/README.md +++ b/.devcontainer/README.md @@ -34,4 +34,4 @@ if you see such error message when you open this project in codespaces: ![Alt text](troubleshooting.png) a simple workaround is change `/signin` endpoint into another one, then login with GitHub account and close the tab, then change it back to `/signin` endpoint. Then all things will be fine. -The reason is `signin` endpoint is not allowed in codespaces, details can be found [here](https://github.com/orgs/community/discussions/5204) \ No newline at end of file +The reason is `signin` endpoint is not allowed in codespaces, details can be found [here](https://github.com/orgs/community/discussions/5204) diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 339ad60ce0..8246544061 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -2,7 +2,7 @@ // README at: https://github.com/devcontainers/templates/tree/main/src/anaconda { "name": "Python 3.12", - "build": { + "build": { "context": "..", "dockerfile": "Dockerfile" }, diff --git a/.devcontainer/noop.txt b/.devcontainer/noop.txt index dde8dc3c10..49de88dbd4 100644 --- a/.devcontainer/noop.txt +++ b/.devcontainer/noop.txt @@ -1,3 +1,3 @@ This file copied into the container along with environment.yml* from the parent -folder. This file is included to prevents the Dockerfile COPY instruction from -failing if no environment.yml is found. \ No newline at end of file +folder. This file is included to prevents the Dockerfile COPY instruction from +failing if no environment.yml is found. diff --git a/.devcontainer/post_create_command.sh b/.devcontainer/post_create_command.sh index cc8eb552b0..93ecac48f2 100755 --- a/.devcontainer/post_create_command.sh +++ b/.devcontainer/post_create_command.sh @@ -1,12 +1,13 @@ #!/bin/bash -npm add -g pnpm@10.8.0 +npm add -g pnpm@10.11.1 cd web && pnpm install pipx install uv echo 'alias start-api="cd /workspaces/dify/api && uv run python -m flask run --host 0.0.0.0 --port=5001 --debug"' >> ~/.bashrc echo 'alias start-worker="cd /workspaces/dify/api && uv run python -m celery -A app.celery worker -P gevent -c 1 --loglevel INFO -Q dataset,generation,mail,ops_trace,app_deletion"' >> ~/.bashrc echo 'alias start-web="cd /workspaces/dify/web && pnpm dev"' >> ~/.bashrc +echo 'alias start-web-prod="cd /workspaces/dify/web && pnpm build && pnpm start"' >> ~/.bashrc echo 'alias start-containers="cd /workspaces/dify/docker && docker-compose -f docker-compose.middleware.yaml -p dify --env-file middleware.env up -d"' >> ~/.bashrc echo 'alias stop-containers="cd /workspaces/dify/docker && docker-compose -f docker-compose.middleware.yaml -p dify --env-file middleware.env down"' >> ~/.bashrc diff --git a/web/.editorconfig b/.editorconfig similarity index 51% rename from web/.editorconfig rename to .editorconfig index e1d3f0b992..374da0b5d2 100644 --- a/web/.editorconfig +++ b/.editorconfig @@ -5,18 +5,35 @@ root = true # Unix-style newlines with a newline ending every file [*] +charset = utf-8 end_of_line = lf insert_final_newline = true +trim_trailing_whitespace = true + +[*.py] +indent_size = 4 +indent_style = space + +[*.{yml,yaml}] +indent_style = space +indent_size = 2 + +[*.toml] +indent_size = 4 +indent_style = space + +# Markdown and MDX are whitespace sensitive languages. +# Do not remove trailing spaces. +[*.{md,mdx}] +trim_trailing_whitespace = false # Matches multiple files with brace expansion notation # Set default charset [*.{js,tsx}] -charset = utf-8 indent_style = space indent_size = 2 - -# Matches the exact files either package.json or .travis.yml -[{package.json,.travis.yml}] +# Matches the exact files package.json +[package.json] indent_style = space indent_size = 2 diff --git a/.gitattributes b/.gitattributes index a10da53408..a32a39f65c 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,5 +1,5 @@ # Ensure that .sh scripts use LF as line separator, even if they are checked out -# to Windows(NTFS) file-system, by a user of Docker for Windows. +# to Windows(NTFS) file-system, by a user of Docker for Windows. # These .sh scripts will be run from the Container after `docker compose up -d`. # If they appear to be CRLF style, Dash from the Container will fail to execute # them. diff --git a/.github/actions/setup-uv/action.yml b/.github/actions/setup-uv/action.yml index a596be63f7..0499b44dba 100644 --- a/.github/actions/setup-uv/action.yml +++ b/.github/actions/setup-uv/action.yml @@ -8,7 +8,7 @@ inputs: uv-version: description: UV version to set up required: true - default: '0.6.14' + default: '~=0.7.11' uv-lockfile: description: Path to the UV lockfile to restore cache from required: true diff --git a/.github/linters/editorconfig-checker.json b/.github/linters/editorconfig-checker.json new file mode 100644 index 0000000000..ce6e9ae341 --- /dev/null +++ b/.github/linters/editorconfig-checker.json @@ -0,0 +1,22 @@ +{ + "Verbose": false, + "Debug": false, + "IgnoreDefaults": false, + "SpacesAfterTabs": false, + "NoColor": false, + "Exclude": [ + "^web/public/vs/", + "^web/public/pdf.worker.min.mjs$", + "web/app/components/base/icons/src/vender/" + ], + "AllowedContentTypes": [], + "PassedFiles": [], + "Disable": { + "EndOfLine": false, + "Indentation": false, + "IndentSize": true, + "InsertFinalNewline": false, + "TrimTrailingWhitespace": false, + "MaxLineLength": false + } +} diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index b4a6eb9adb..f4a5f754e0 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,25 +1,23 @@ -# Summary +> [!IMPORTANT] +> +> 1. Make sure you have read our [contribution guidelines](https://github.com/langgenius/dify/blob/main/CONTRIBUTING.md) +> 2. Ensure there is an associated issue and you have been assigned to it +> 3. Use the correct syntax to link this PR: `Fixes #`. -Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. List any dependencies that are required for this change. +## Summary -> [!Tip] -> Close issue syntax: `Fixes #` or `Resolves #`, see [documentation](https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue#linking-a-pull-request-to-an-issue-using-a-keyword) for more details. + - -# Screenshots +## Screenshots | Before | After | |--------|-------| | ... | ... | -# Checklist - -> [!IMPORTANT] -> Please review the checklist below before submitting your pull request. +## Checklist - [ ] This change requires a documentation update, included: [Dify Document](https://github.com/langgenius/dify-docs) - [x] I understand that this PR may be closed in case there was no previous discussion or issues. (This doesn't apply to typos!) - [x] I've added a test for each change that was introduced, and I tried as much as possible to make a single atomic change. - [x] I've updated the documentation accordingly. - [x] I ran `dev/reformat`(backend) and `cd web && npx lint-staged`(frontend) to appease the lint gods - diff --git a/.github/workflows/api-tests.yml b/.github/workflows/api-tests.yml index 02583cda06..f08befefb8 100644 --- a/.github/workflows/api-tests.yml +++ b/.github/workflows/api-tests.yml @@ -88,3 +88,6 @@ jobs: - name: Run Workflow run: uv run --project api bash dev/pytest/pytest_workflow.sh + + - name: Run Tool + run: uv run --project api bash dev/pytest/pytest_tools.sh diff --git a/.github/workflows/deploy-rag-dev.yml b/.github/workflows/deploy-rag-dev.yml new file mode 100644 index 0000000000..86265aad6d --- /dev/null +++ b/.github/workflows/deploy-rag-dev.yml @@ -0,0 +1,28 @@ +name: Deploy RAG Dev + +permissions: + contents: read + +on: + workflow_run: + workflows: ["Build and Push API & Web"] + branches: + - "deploy/rag-dev" + types: + - completed + +jobs: + deploy: + runs-on: ubuntu-latest + if: | + github.event.workflow_run.conclusion == 'success' && + github.event.workflow_run.head_branch == 'deploy/rag-dev' + steps: + - name: Deploy to server + uses: appleboy/ssh-action@v0.1.8 + with: + host: ${{ secrets.RAG_SSH_HOST }} + username: ${{ secrets.SSH_USER }} + key: ${{ secrets.SSH_PRIVATE_KEY }} + script: | + ${{ vars.SSH_SCRIPT || secrets.SSH_SCRIPT }} diff --git a/.github/workflows/expose_service_ports.sh b/.github/workflows/expose_service_ports.sh index 10d95cb736..01772ccf9f 100755 --- a/.github/workflows/expose_service_ports.sh +++ b/.github/workflows/expose_service_ports.sh @@ -10,6 +10,7 @@ yq eval '.services["elasticsearch"].ports += ["9200:9200"]' -i docker/docker-com yq eval '.services.couchbase-server.ports += ["8091-8096:8091-8096"]' -i docker/docker-compose.yaml yq eval '.services.couchbase-server.ports += ["11210:11210"]' -i docker/docker-compose.yaml yq eval '.services.tidb.ports += ["4000:4000"]' -i docker/tidb/docker-compose.yaml +yq eval '.services.oceanbase.ports += ["2881:2881"]' -i docker/docker-compose.yaml yq eval '.services.opengauss.ports += ["6600:6600"]' -i docker/docker-compose.yaml echo "Ports exposed for sandbox, weaviate, tidb, qdrant, chroma, milvus, pgvector, pgvecto-rs, elasticsearch, couchbase, opengauss" diff --git a/.github/workflows/style.yml b/.github/workflows/style.yml index 98e5fd5150..b06ab9653e 100644 --- a/.github/workflows/style.yml +++ b/.github/workflows/style.yml @@ -9,6 +9,12 @@ concurrency: group: style-${{ github.head_ref || github.run_id }} cancel-in-progress: true +permissions: + checks: write + statuses: write + contents: read + + jobs: python-style: name: Python Style @@ -43,8 +49,8 @@ jobs: if: steps.changed-files.outputs.any_changed == 'true' run: | uv run --directory api ruff --version - uv run --directory api ruff check ./ - uv run --directory api ruff format --check ./ + uv run --directory api ruff check --diff ./ + uv run --directory api ruff format --check --diff ./ - name: Dotenv check if: steps.changed-files.outputs.any_changed == 'true' @@ -133,6 +139,7 @@ jobs: - name: Checkout code uses: actions/checkout@v4 with: + fetch-depth: 0 persist-credentials: false - name: Check changed files @@ -163,3 +170,14 @@ jobs: VALIDATE_DOCKERFILE_HADOLINT: true VALIDATE_XML: true VALIDATE_YAML: true + + - name: EditorConfig checks + uses: super-linter/super-linter/slim@v7 + env: + DEFAULT_BRANCH: main + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + IGNORE_GENERATED_FILES: true + IGNORE_GITIGNORED_FILES: true + # EditorConfig validation + VALIDATE_EDITORCONFIG: true + EDITORCONFIG_FILE_NAME: editorconfig-checker.json diff --git a/.github/workflows/translate-i18n-base-on-english.yml b/.github/workflows/translate-i18n-base-on-english.yml index 3f8082eb69..c79d58563f 100644 --- a/.github/workflows/translate-i18n-base-on-english.yml +++ b/.github/workflows/translate-i18n-base-on-english.yml @@ -31,11 +31,19 @@ jobs: echo "FILES_CHANGED=false" >> $GITHUB_ENV fi + - name: Install pnpm + uses: pnpm/action-setup@v4 + with: + version: 10 + run_install: false + - name: Set up Node.js if: env.FILES_CHANGED == 'true' uses: actions/setup-node@v4 with: node-version: 'lts/*' + cache: pnpm + cache-dependency-path: ./web/package.json - name: Install dependencies if: env.FILES_CHANGED == 'true' diff --git a/.github/workflows/vdb-tests.yml b/.github/workflows/vdb-tests.yml index c784817e72..512d14b2ee 100644 --- a/.github/workflows/vdb-tests.yml +++ b/.github/workflows/vdb-tests.yml @@ -31,6 +31,13 @@ jobs: with: persist-credentials: false + - name: Free Disk Space + uses: endersonmenezes/free-disk-space@v2 + with: + remove_dotnet: true + remove_haskell: true + remove_tool_cache: true + - name: Setup UV and Python uses: ./.github/actions/setup-uv with: @@ -59,7 +66,7 @@ jobs: tidb tiflash - - name: Set up Vector Stores (Weaviate, Qdrant, PGVector, Milvus, PgVecto-RS, Chroma, MyScale, ElasticSearch, Couchbase) + - name: Set up Vector Stores (Weaviate, Qdrant, PGVector, Milvus, PgVecto-RS, Chroma, MyScale, ElasticSearch, Couchbase, OceanBase) uses: hoverkraft-tech/compose-action@v2.0.2 with: compose-file: | @@ -75,9 +82,12 @@ jobs: pgvector chroma elasticsearch + oceanbase - - name: Check TiDB Ready - run: uv run --project api python api/tests/integration_tests/vdb/tidb_vector/check_tiflash_ready.py + - name: Check VDB Ready (TiDB, Oceanbase) + run: | + uv run --project api python api/tests/integration_tests/vdb/tidb_vector/check_tiflash_ready.py + uv run --project api python api/tests/integration_tests/vdb/oceanbase/check_oceanbase_ready.py - name: Test Vector Stores run: uv run --project api bash dev/pytest/pytest_vdb.sh diff --git a/.gitignore b/.gitignore index 8818ab6f65..74a9ef63ef 100644 --- a/.gitignore +++ b/.gitignore @@ -192,12 +192,12 @@ sdks/python-client/dist sdks/python-client/dify_client.egg-info .vscode/* -!.vscode/launch.json +!.vscode/launch.json.template +!.vscode/README.md pyrightconfig.json api/.vscode .idea/ -.vscode # pnpm /.pnpm-store @@ -207,3 +207,6 @@ plugins.jsonl # mise mise.toml + +# Next.js build output +.next/ diff --git a/.vscode/README.md b/.vscode/README.md new file mode 100644 index 0000000000..26516f0540 --- /dev/null +++ b/.vscode/README.md @@ -0,0 +1,14 @@ +# Debugging with VS Code + +This `launch.json.template` file provides various debug configurations for the Dify project within VS Code / Cursor. To use these configurations, you should copy the contents of this file into a new file named `launch.json` in the same `.vscode` directory. + +## How to Use + +1. **Create `launch.json`**: If you don't have one, create a file named `launch.json` inside the `.vscode` directory. +2. **Copy Content**: Copy the entire content from `launch.json.template` into your newly created `launch.json` file. +3. **Select Debug Configuration**: Go to the Run and Debug view in VS Code / Cursor (Ctrl+Shift+D or Cmd+Shift+D). +4. **Start Debugging**: Select the desired configuration from the dropdown menu and click the green play button. + +## Tips + +- If you need to debug with Edge browser instead of Chrome, modify the `serverReadyAction` configuration in the "Next.js: debug full stack" section, change `"debugWithChrome"` to `"debugWithEdge"` to use Microsoft Edge for debugging. diff --git a/.vscode/launch.json.template b/.vscode/launch.json.template new file mode 100644 index 0000000000..f5a7f0893b --- /dev/null +++ b/.vscode/launch.json.template @@ -0,0 +1,68 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Python: Flask API", + "type": "debugpy", + "request": "launch", + "module": "flask", + "env": { + "FLASK_APP": "app.py", + "FLASK_ENV": "development", + "GEVENT_SUPPORT": "True" + }, + "args": [ + "run", + "--host=0.0.0.0", + "--port=5001", + "--no-debugger", + "--no-reload" + ], + "jinja": true, + "justMyCode": true, + "cwd": "${workspaceFolder}/api", + "python": "${workspaceFolder}/api/.venv/bin/python" + }, + { + "name": "Python: Celery Worker (Solo)", + "type": "debugpy", + "request": "launch", + "module": "celery", + "env": { + "GEVENT_SUPPORT": "True" + }, + "args": [ + "-A", + "app.celery", + "worker", + "-P", + "solo", + "-c", + "1", + "-Q", + "dataset,generation,mail,ops_trace", + "--loglevel", + "INFO" + ], + "justMyCode": false, + "cwd": "${workspaceFolder}/api", + "python": "${workspaceFolder}/api/.venv/bin/python" + }, + { + "name": "Next.js: debug full stack", + "type": "node", + "request": "launch", + "program": "${workspaceFolder}/web/node_modules/next/dist/bin/next", + "runtimeArgs": ["--inspect"], + "skipFiles": ["/**"], + "serverReadyAction": { + "action": "debugWithChrome", + "killOnServerStop": true, + "pattern": "- Local:.+(https?://.+)", + "uriFormat": "%s", + "webRoot": "${workspaceFolder}/web" + }, + "cwd": "${workspaceFolder}/web" + } + ] +} diff --git a/CONTRIBUTING_ES.md b/CONTRIBUTING_ES.md index 261aa0fda1..98cbb5b457 100644 --- a/CONTRIBUTING_ES.md +++ b/CONTRIBUTING_ES.md @@ -90,4 +90,4 @@ Recomendamos revisar este documento cuidadosamente antes de proceder con la conf No dudes en contactarnos si encuentras algún problema durante el proceso de configuración. ## Obteniendo Ayuda -Si alguna vez te quedas atascado o tienes una pregunta urgente mientras contribuyes, simplemente envíanos tus consultas a través del issue relacionado de GitHub, o únete a nuestro [Discord](https://discord.gg/8Tpq4AcN9c) para una charla rápida. \ No newline at end of file +Si alguna vez te quedas atascado o tienes una pregunta urgente mientras contribuyes, simplemente envíanos tus consultas a través del issue relacionado de GitHub, o únete a nuestro [Discord](https://discord.gg/8Tpq4AcN9c) para una charla rápida. diff --git a/CONTRIBUTING_FR.md b/CONTRIBUTING_FR.md index c3418f86cc..fc8410dfd6 100644 --- a/CONTRIBUTING_FR.md +++ b/CONTRIBUTING_FR.md @@ -90,4 +90,4 @@ Nous recommandons de revoir attentivement ce document avant de procéder à la c N'hésitez pas à nous contacter si vous rencontrez des problèmes pendant le processus de configuration. ## Obtenir de l'aide -Si jamais vous êtes bloqué ou avez une question urgente en contribuant, envoyez-nous simplement vos questions via le problème GitHub concerné, ou rejoignez notre [Discord](https://discord.gg/8Tpq4AcN9c) pour une discussion rapide. \ No newline at end of file +Si jamais vous êtes bloqué ou avez une question urgente en contribuant, envoyez-nous simplement vos questions via le problème GitHub concerné, ou rejoignez notre [Discord](https://discord.gg/8Tpq4AcN9c) pour une discussion rapide. diff --git a/CONTRIBUTING_KR.md b/CONTRIBUTING_KR.md index fcf44d495a..78d3f38c47 100644 --- a/CONTRIBUTING_KR.md +++ b/CONTRIBUTING_KR.md @@ -90,4 +90,4 @@ PR 설명에 기존 이슈를 연결하거나 새 이슈를 여는 것을 잊지 설정 과정에서 문제가 발생하면 언제든지 연락해 주세요. ## 도움 받기 -기여하는 동안 막히거나 긴급한 질문이 있으면, 관련 GitHub 이슈를 통해 질문을 보내거나, 빠른 대화를 위해 우리의 [Discord](https://discord.gg/8Tpq4AcN9c)에 참여하세요. \ No newline at end of file +기여하는 동안 막히거나 긴급한 질문이 있으면, 관련 GitHub 이슈를 통해 질문을 보내거나, 빠른 대화를 위해 우리의 [Discord](https://discord.gg/8Tpq4AcN9c)에 참여하세요. diff --git a/CONTRIBUTING_PT.md b/CONTRIBUTING_PT.md index bba76c17ee..7347fd7f9c 100644 --- a/CONTRIBUTING_PT.md +++ b/CONTRIBUTING_PT.md @@ -90,4 +90,4 @@ Recomendamos revisar este documento cuidadosamente antes de prosseguir com a con Sinta-se à vontade para entrar em contato se encontrar quaisquer problemas durante o processo de configuração. ## Obtendo Ajuda -Se você ficar preso ou tiver uma dúvida urgente enquanto contribui, simplesmente envie suas perguntas através do problema relacionado no GitHub, ou entre no nosso [Discord](https://discord.gg/8Tpq4AcN9c) para uma conversa rápida. \ No newline at end of file +Se você ficar preso ou tiver uma dúvida urgente enquanto contribui, simplesmente envie suas perguntas através do problema relacionado no GitHub, ou entre no nosso [Discord](https://discord.gg/8Tpq4AcN9c) para uma conversa rápida. diff --git a/CONTRIBUTING_TR.md b/CONTRIBUTING_TR.md index 4e216d22a4..681f05689b 100644 --- a/CONTRIBUTING_TR.md +++ b/CONTRIBUTING_TR.md @@ -90,4 +90,4 @@ Kuruluma geçmeden önce bu belgeyi dikkatlice incelemenizi öneririz, çünkü Kurulum süreci sırasında herhangi bir sorunla karşılaşırsanız bizimle iletişime geçmekten çekinmeyin. ## Yardım Almak -Katkıda bulunurken takılırsanız veya yanıcı bir sorunuz olursa, sorularınızı ilgili GitHub sorunu aracılığıyla bize gönderin veya hızlı bir sohbet için [Discord'umuza](https://discord.gg/8Tpq4AcN9c) katılın. \ No newline at end of file +Katkıda bulunurken takılırsanız veya yanıcı bir sorunuz olursa, sorularınızı ilgili GitHub sorunu aracılığıyla bize gönderin veya hızlı bir sohbet için [Discord'umuza](https://discord.gg/8Tpq4AcN9c) katılın. diff --git a/README.md b/README.md index 65e8001dd2..ca09adec08 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -![cover-v5-optimized](https://github.com/langgenius/dify/assets/13230914/f9e19af5-61ba-4119-b926-d10c4c06ebab) +![cover-v5-optimized](./images/GitHub_README_if.png)

📌 Introducing Dify Workflow File Upload: Recreate Google NotebookLM Podcast @@ -54,7 +54,7 @@ README in বাংলা

-Dify is an open-source LLM app development platform. Its intuitive interface combines agentic AI workflow, RAG pipeline, agent capabilities, model management, observability features and more, letting you quickly go from prototype to production. +Dify is an open-source LLM app development platform. Its intuitive interface combines agentic AI workflow, RAG pipeline, agent capabilities, model management, observability features, and more, allowing you to quickly move from prototype to production. ## Quick start @@ -87,8 +87,6 @@ Please refer to our [FAQ](https://docs.dify.ai/getting-started/install-self-host **1. Workflow**: Build and test powerful AI workflows on a visual canvas, leveraging all the following features and beyond. -https://github.com/langgenius/dify/assets/13230914/356df23e-1604-483d-80a6-9517ece318aa - **2. Comprehensive model support**: Seamless integration with hundreds of proprietary / open-source LLMs from dozens of inference providers and self-hosted solutions, covering GPT, Mistral, Llama3, and any OpenAI API-compatible models. A full list of supported model providers can be found [here](https://docs.dify.ai/getting-started/readme/model-providers). @@ -188,7 +186,7 @@ All of Dify's offerings come with corresponding APIs, so you could effortlessly - **Dify for enterprise / organizations
** We provide additional enterprise-centric features. [Log your questions for us through this chatbot](https://udify.app/chat/22L1zSxg6yW1cWQg) or [send us an email](mailto:business@dify.ai?subject=[GitHub]Business%20License%20Inquiry) to discuss enterprise needs.
- > For startups and small businesses using AWS, check out [Dify Premium on AWS Marketplace](https://aws.amazon.com/marketplace/pp/prodview-t22mebxzwjhu6) and deploy it to your own AWS VPC with one-click. It's an affordable AMI offering with the option to create apps with custom logo and branding. + > For startups and small businesses using AWS, check out [Dify Premium on AWS Marketplace](https://aws.amazon.com/marketplace/pp/prodview-t22mebxzwjhu6) and deploy it to your own AWS VPC with one click. It's an affordable AMI offering with the option to create apps with custom logo and branding. ## Staying ahead @@ -233,11 +231,11 @@ Deploy Dify to AWS with [CDK](https://aws.amazon.com/cdk/) For those who'd like to contribute code, see our [Contribution Guide](https://github.com/langgenius/dify/blob/main/CONTRIBUTING.md). At the same time, please consider supporting Dify by sharing it on social media and at events and conferences. -> We are looking for contributors to help with translating Dify to languages other than Mandarin or English. If you are interested in helping, please see the [i18n README](https://github.com/langgenius/dify/blob/main/web/i18n/README.md) for more information, and leave us a comment in the `global-users` channel of our [Discord Community Server](https://discord.gg/8Tpq4AcN9c). +> We are looking for contributors to help translate Dify into languages other than Mandarin or English. If you are interested in helping, please see the [i18n README](https://github.com/langgenius/dify/blob/main/web/i18n/README.md) for more information, and leave us a comment in the `global-users` channel of our [Discord Community Server](https://discord.gg/8Tpq4AcN9c). ## Community & contact -- [Github Discussion](https://github.com/langgenius/dify/discussions). Best for: sharing feedback and asking questions. +- [GitHub Discussion](https://github.com/langgenius/dify/discussions). Best for: sharing feedback and asking questions. - [GitHub Issues](https://github.com/langgenius/dify/issues). Best for: bugs you encounter using Dify.AI, and feature proposals. See our [Contribution Guide](https://github.com/langgenius/dify/blob/main/CONTRIBUTING.md). - [Discord](https://discord.gg/FngNHpbcY7). Best for: sharing your applications and hanging out with the community. - [X(Twitter)](https://twitter.com/dify_ai). Best for: sharing your applications and hanging out with the community. diff --git a/README_AR.md b/README_AR.md index 4f93802fda..df288fd33c 100644 --- a/README_AR.md +++ b/README_AR.md @@ -1,4 +1,4 @@ -![cover-v5-optimized](https://github.com/langgenius/dify/assets/13230914/f9e19af5-61ba-4119-b926-d10c4c06ebab) +![cover-v5-optimized](./images/GitHub_README_if.png)

Dify Cloud · @@ -54,8 +54,6 @@ **1. سير العمل**: قم ببناء واختبار سير عمل الذكاء الاصطناعي القوي على قماش بصري، مستفيدًا من جميع الميزات التالية وأكثر. - - **2. الدعم الشامل للنماذج**: تكامل سلس مع مئات من LLMs الخاصة / مفتوحة المصدر من عشرات من موفري التحليل والحلول المستضافة ذاتيًا، مما يغطي GPT و Mistral و Llama3 وأي نماذج متوافقة مع واجهة OpenAI API. يمكن العثور على قائمة كاملة بمزودي النموذج المدعومين [هنا](https://docs.dify.ai/getting-started/readme/model-providers). ![providers-v5](https://github.com/langgenius/dify/assets/13230914/5a17bdbe-097a-4100-8363-40255b70f6e3) @@ -225,7 +223,7 @@ docker compose up -d ## المجتمع والاتصال -- [مناقشة Github](https://github.com/langgenius/dify/discussions). الأفضل لـ: مشاركة التعليقات وطرح الأسئلة. +- [مناقشة GitHub](https://github.com/langgenius/dify/discussions). الأفضل لـ: مشاركة التعليقات وطرح الأسئلة. - [المشكلات على GitHub](https://github.com/langgenius/dify/issues). الأفضل لـ: الأخطاء التي تواجهها في استخدام Dify.AI، واقتراحات الميزات. انظر [دليل المساهمة](https://github.com/langgenius/dify/blob/main/CONTRIBUTING.md). - [Discord](https://discord.gg/FngNHpbcY7). الأفضل لـ: مشاركة تطبيقاتك والترفيه مع المجتمع. - [تويتر](https://twitter.com/dify_ai). الأفضل لـ: مشاركة تطبيقاتك والترفيه مع المجتمع. diff --git a/README_BN.md b/README_BN.md index 7599fae9ff..4a5b5f3928 100644 --- a/README_BN.md +++ b/README_BN.md @@ -1,4 +1,4 @@ -![cover-v5-optimized](https://github.com/langgenius/dify/assets/13230914/f9e19af5-61ba-4119-b926-d10c4c06ebab) +![cover-v5-optimized](./images/GitHub_README_if.png)

📌 ডিফাই ওয়ার্কফ্লো ফাইল আপলোড পরিচিতি: গুগল নোটবুক-এলএম পডকাস্ট পুনর্নির্মাণ @@ -84,8 +84,6 @@ docker compose up -d **১. ওয়ার্কফ্লো**: ভিজ্যুয়াল ক্যানভাসে AI ওয়ার্কফ্লো তৈরি এবং পরীক্ষা করুন, নিম্নলিখিত সব ফিচার এবং তার বাইরেও আরও অনেক কিছু ব্যবহার করে। - - **২. মডেল সাপোর্ট**: GPT, Mistral, Llama3, এবং যেকোনো OpenAI API-সামঞ্জস্যপূর্ণ মডেলসহ, কয়েক ডজন ইনফারেন্স প্রদানকারী এবং সেল্ফ-হোস্টেড সমাধান থেকে শুরু করে প্রোপ্রাইটরি/ওপেন-সোর্স LLM-এর সাথে সহজে ইন্টিগ্রেশন। সমর্থিত মডেল প্রদানকারীদের একটি সম্পূর্ণ তালিকা পাওয়া যাবে [এখানে](https://docs.dify.ai/getting-started/readme/model-providers)। @@ -236,7 +234,7 @@ GitHub-এ ডিফাইকে স্টার দিয়ে রাখুন ## কমিউনিটি এবং যোগাযোগ -- [Github Discussion](https://github.com/langgenius/dify/discussions) ফিডব্যাক এবং প্রতিক্রিয়া জানানোর মাধ্যম। +- [GitHub Discussion](https://github.com/langgenius/dify/discussions) ফিডব্যাক এবং প্রতিক্রিয়া জানানোর মাধ্যম। - [GitHub Issues](https://github.com/langgenius/dify/issues). Dify.AI ব্যবহার করে আপনি যেসব বাগের সম্মুখীন হন এবং ফিচার প্রস্তাবনা। আমাদের [অবদান নির্দেশিকা](https://github.com/langgenius/dify/blob/main/CONTRIBUTING.md) দেখুন। - [Discord](https://discord.gg/FngNHpbcY7) আপনার এপ্লিকেশন শেয়ার এবং কমিউনিটি আড্ডার মাধ্যম। - [X(Twitter)](https://twitter.com/dify_ai) আপনার এপ্লিকেশন শেয়ার এবং কমিউনিটি আড্ডার মাধ্যম। diff --git a/README_CN.md b/README_CN.md index 973629f459..ba7ee0006d 100644 --- a/README_CN.md +++ b/README_CN.md @@ -1,4 +1,4 @@ -![cover-v5-optimized](https://github.com/langgenius/dify/assets/13230914/f9e19af5-61ba-4119-b926-d10c4c06ebab) +![cover-v5-optimized](./images/GitHub_README_if.png)

Dify 云服务 · @@ -61,11 +61,6 @@ Dify 是一个开源的 LLM 应用开发平台。其直观的界面结合了 AI **1. 工作流**: 在画布上构建和测试功能强大的 AI 工作流程,利用以下所有功能以及更多功能。 - - https://github.com/langgenius/dify/assets/13230914/356df23e-1604-483d-80a6-9517ece318aa - - - **2. 全面的模型支持**: 与数百种专有/开源 LLMs 以及数十种推理提供商和自托管解决方案无缝集成,涵盖 GPT、Mistral、Llama3 以及任何与 OpenAI API 兼容的模型。完整的支持模型提供商列表可在[此处](https://docs.dify.ai/getting-started/readme/model-providers)找到。 @@ -248,7 +243,7 @@ docker compose up -d 我们欢迎您为 Dify 做出贡献,以帮助改善 Dify。包括:提交代码、问题、新想法,或分享您基于 Dify 创建的有趣且有用的 AI 应用程序。同时,我们也欢迎您在不同的活动、会议和社交媒体上分享 Dify。 -- [Github Discussion](https://github.com/langgenius/dify/discussions). 👉:分享您的应用程序并与社区交流。 +- [GitHub Discussion](https://github.com/langgenius/dify/discussions). 👉:分享您的应用程序并与社区交流。 - [GitHub Issues](https://github.com/langgenius/dify/issues)。👉:使用 Dify.AI 时遇到的错误和问题,请参阅[贡献指南](CONTRIBUTING.md)。 - [电子邮件支持](mailto:hello@dify.ai?subject=[GitHub]Questions%20About%20Dify)。👉:关于使用 Dify.AI 的问题。 - [Discord](https://discord.gg/FngNHpbcY7)。👉:分享您的应用程序并与社区交流。 diff --git a/README_DE.md b/README_DE.md index 738c0e3b67..f6023a3935 100644 --- a/README_DE.md +++ b/README_DE.md @@ -1,4 +1,4 @@ -![cover-v5-optimized](https://github.com/langgenius/dify/assets/13230914/f9e19af5-61ba-4119-b926-d10c4c06ebab) +![cover-v5-optimized](./images/GitHub_README_if.png)

📌 Einführung in Dify Workflow File Upload: Google NotebookLM Podcast nachbilden @@ -83,11 +83,6 @@ Bitte beachten Sie unsere [FAQ](https://docs.dify.ai/getting-started/install-sel **1. Workflow**: Erstellen und testen Sie leistungsstarke KI-Workflows auf einer visuellen Oberfläche, wobei Sie alle der folgenden Funktionen und darüber hinaus nutzen können. - - https://github.com/langgenius/dify/assets/13230914/356df23e-1604-483d-80a6-9517ece318aa - - - **2. Umfassende Modellunterstützung**: Nahtlose Integration mit Hunderten von proprietären und Open-Source-LLMs von Dutzenden Inferenzanbietern und selbstgehosteten Lösungen, die GPT, Mistral, Llama3 und alle mit der OpenAI API kompatiblen Modelle abdecken. Eine vollständige Liste der unterstützten Modellanbieter finden Sie [hier](https://docs.dify.ai/getting-started/readme/model-providers). @@ -235,7 +230,7 @@ Falls Sie Code beitragen möchten, lesen Sie bitte unseren [Contribution Guide]( ## Gemeinschaft & Kontakt -* [Github Discussion](https://github.com/langgenius/dify/discussions). Am besten geeignet für: den Austausch von Feedback und das Stellen von Fragen. +* [GitHub Discussion](https://github.com/langgenius/dify/discussions). Am besten geeignet für: den Austausch von Feedback und das Stellen von Fragen. * [GitHub Issues](https://github.com/langgenius/dify/issues). Am besten für: Fehler, auf die Sie bei der Verwendung von Dify.AI stoßen, und Funktionsvorschläge. Siehe unseren [Contribution Guide](https://github.com/langgenius/dify/blob/main/CONTRIBUTING.md). * [Discord](https://discord.gg/FngNHpbcY7). Am besten geeignet für: den Austausch von Bewerbungen und den Austausch mit der Community. * [X(Twitter)](https://twitter.com/dify_ai). Am besten geeignet für: den Austausch von Bewerbungen und den Austausch mit der Community. diff --git a/README_ES.md b/README_ES.md index 212268b73d..12f2ce8c11 100644 --- a/README_ES.md +++ b/README_ES.md @@ -1,4 +1,4 @@ -![cover-v5-optimized](https://github.com/langgenius/dify/assets/13230914/f9e19af5-61ba-4119-b926-d10c4c06ebab) +![cover-v5-optimized](./images/GitHub_README_if.png)

Dify Cloud · @@ -59,11 +59,6 @@ Dify es una plataforma de desarrollo de aplicaciones de LLM de código abierto. **1. Flujo de trabajo**: Construye y prueba potentes flujos de trabajo de IA en un lienzo visual, aprovechando todas las siguientes características y más. - - https://github.com/langgenius/dify/assets/13230914/356df23e-1604-483d-80a6-9517ece318aa - - - **2. Soporte de modelos completo**: Integración perfecta con cientos de LLMs propietarios / de código abierto de docenas de proveedores de inferencia y soluciones auto-alojadas, que cubren GPT, Mistral, Llama3 y cualquier modelo compatible con la API de OpenAI. Se puede encontrar una lista completa de proveedores de modelos admitidos [aquí](https://docs.dify.ai/getting-started/readme/model-providers). diff --git a/README_FR.md b/README_FR.md index 89eea7d058..b106615b31 100644 --- a/README_FR.md +++ b/README_FR.md @@ -1,4 +1,4 @@ -![cover-v5-optimized](https://github.com/langgenius/dify/assets/13230914/f9e19af5-61ba-4119-b926-d10c4c06ebab) +![cover-v5-optimized](./images/GitHub_README_if.png)

Dify Cloud · @@ -59,11 +59,6 @@ Dify est une plateforme de développement d'applications LLM open source. Son in **1. Flux de travail** : Construisez et testez des flux de travail d'IA puissants sur un canevas visuel, en utilisant toutes les fonctionnalités suivantes et plus encore. - - https://github.com/langgenius/dify/assets/13230914/356df23e-1604-483d-80a6-9517ece318aa - - - **2. Prise en charge complète des modèles** : Intégration transparente avec des centaines de LLM propriétaires / open source provenant de dizaines de fournisseurs d'inférence et de solutions auto-hébergées, couvrant GPT, Mistral, Llama3, et tous les modèles compatibles avec l'API OpenAI. Une liste complète des fournisseurs de modèles pris en charge se trouve [ici](https://docs.dify.ai/getting-started/readme/model-providers). diff --git a/README_JA.md b/README_JA.md index adca219753..26703f3958 100644 --- a/README_JA.md +++ b/README_JA.md @@ -1,4 +1,4 @@ -![cover-v5-optimized](https://github.com/langgenius/dify/assets/13230914/f9e19af5-61ba-4119-b926-d10c4c06ebab) +![cover-v5-optimized](./images/GitHub_README_if.png)

Dify Cloud · @@ -60,11 +60,6 @@ DifyはオープンソースのLLMアプリケーション開発プラットフ **1. ワークフロー**: 強力なAIワークフローをビジュアルキャンバス上で構築し、テストできます。すべての機能、および以下の機能を使用できます。 - - https://github.com/langgenius/dify/assets/13230914/356df23e-1604-483d-80a6-9517ece318aa - - - **2. 総合的なモデルサポート**: 数百ものプロプライエタリ/オープンソースのLLMと、数十もの推論プロバイダーおよびセルフホスティングソリューションとのシームレスな統合を提供します。GPT、Mistral、Llama3、OpenAI APIと互換性のあるすべてのモデルを統合されています。サポートされているモデルプロバイダーの完全なリストは[こちら](https://docs.dify.ai/getting-started/readme/model-providers)をご覧ください。 @@ -241,7 +236,7 @@ docker compose up -d ## コミュニティ & お問い合わせ -* [Github Discussion](https://github.com/langgenius/dify/discussions). 主に: フィードバックの共有や質問。 +* [GitHub Discussion](https://github.com/langgenius/dify/discussions). 主に: フィードバックの共有や質問。 * [GitHub Issues](https://github.com/langgenius/dify/issues). 主に: Dify.AIを使用する際に発生するエラーや問題については、[貢献ガイド](CONTRIBUTING_JA.md)を参照してください * [Discord](https://discord.gg/FngNHpbcY7). 主に: アプリケーションの共有やコミュニティとの交流。 * [X(Twitter)](https://twitter.com/dify_ai). 主に: アプリケーションの共有やコミュニティとの交流。 diff --git a/README_KL.md b/README_KL.md index 17e6c9d509..ea91baa5aa 100644 --- a/README_KL.md +++ b/README_KL.md @@ -1,4 +1,4 @@ -![cover-v5-optimized](https://github.com/langgenius/dify/assets/13230914/f9e19af5-61ba-4119-b926-d10c4c06ebab) +![cover-v5-optimized](./images/GitHub_README_if.png)

Dify Cloud · @@ -59,11 +59,6 @@ Dify is an open-source LLM app development platform. Its intuitive interface com **1. Workflow**: Build and test powerful AI workflows on a visual canvas, leveraging all the following features and beyond. - - https://github.com/langgenius/dify/assets/13230914/356df23e-1604-483d-80a6-9517ece318aa - - - **2. Comprehensive model support**: Seamless integration with hundreds of proprietary / open-source LLMs from dozens of inference providers and self-hosted solutions, covering GPT, Mistral, Llama3, and any OpenAI API-compatible models. A full list of supported model providers can be found [here](https://docs.dify.ai/getting-started/readme/model-providers). @@ -240,7 +235,7 @@ At the same time, please consider supporting Dify by sharing it on social media ## Community & Contact -* [Github Discussion](https://github.com/langgenius/dify/discussions +* [GitHub Discussion](https://github.com/langgenius/dify/discussions ). Best for: sharing feedback and asking questions. * [GitHub Issues](https://github.com/langgenius/dify/issues). Best for: bugs you encounter using Dify.AI, and feature proposals. See our [Contribution Guide](https://github.com/langgenius/dify/blob/main/CONTRIBUTING.md). diff --git a/README_KR.md b/README_KR.md index d44723f9b6..89301e8b2c 100644 --- a/README_KR.md +++ b/README_KR.md @@ -1,4 +1,4 @@ -![cover-v5-optimized](https://github.com/langgenius/dify/assets/13230914/f9e19af5-61ba-4119-b926-d10c4c06ebab) +![cover-v5-optimized](./images/GitHub_README_if.png)

Dify 클라우드 · @@ -54,11 +54,6 @@ **1. 워크플로우**: 다음 기능들을 비롯한 다양한 기능을 활용하여 시각적 캔버스에서 강력한 AI 워크플로우를 구축하고 테스트하세요. - - https://github.com/langgenius/dify/assets/13230914/356df23e-1604-483d-80a6-9517ece318aa - - - **2. 포괄적인 모델 지원:**: 수십 개의 추론 제공업체와 자체 호스팅 솔루션에서 제공하는 수백 개의 독점 및 오픈 소스 LLM과 원활하게 통합되며, GPT, Mistral, Llama3 및 모든 OpenAI API 호환 모델을 포함합니다. 지원되는 모델 제공업체의 전체 목록은 [여기](https://docs.dify.ai/getting-started/readme/model-providers)에서 확인할 수 있습니다. @@ -234,7 +229,7 @@ Dify를 Kubernetes에 배포하고 프리미엄 스케일링 설정을 구성했 ## 커뮤니티 & 연락처 -* [Github 토론](https://github.com/langgenius/dify/discussions). 피드백 공유 및 질문하기에 적합합니다. +* [GitHub 토론](https://github.com/langgenius/dify/discussions). 피드백 공유 및 질문하기에 적합합니다. * [GitHub 이슈](https://github.com/langgenius/dify/issues). Dify.AI 사용 중 발견한 버그와 기능 제안에 적합합니다. [기여 가이드](https://github.com/langgenius/dify/blob/main/CONTRIBUTING.md)를 참조하세요. * [디스코드](https://discord.gg/FngNHpbcY7). 애플리케이션 공유 및 커뮤니티와 소통하기에 적합합니다. * [트위터](https://twitter.com/dify_ai). 애플리케이션 공유 및 커뮤니티와 소통하기에 적합합니다. diff --git a/README_PT.md b/README_PT.md index 9dc2207279..157772d528 100644 --- a/README_PT.md +++ b/README_PT.md @@ -1,5 +1,4 @@ -![cover-v5-optimized](https://github.com/langgenius/dify/assets/13230914/f9e19af5-61ba-4119-b926-d10c4c06ebab) - +![cover-v5-optimized](./images/GitHub_README_if.png)

📌 Introduzindo o Dify Workflow com Upload de Arquivo: Recrie o Podcast Google NotebookLM

@@ -59,11 +58,6 @@ Dify é uma plataforma de desenvolvimento de aplicativos LLM de código aberto. **1. Workflow**: Construa e teste workflows poderosos de IA em uma interface visual, aproveitando todos os recursos a seguir e muito mais. - - https://github.com/langgenius/dify/assets/13230914/356df23e-1604-483d-80a6-9517ece318aa - - - **2. Suporte abrangente a modelos**: Integração perfeita com centenas de LLMs proprietários e de código aberto de diversas provedoras e soluções auto-hospedadas, abrangendo GPT, Mistral, Llama3 e qualquer modelo compatível com a API da OpenAI. A lista completa de provedores suportados pode ser encontrada [aqui](https://docs.dify.ai/getting-started/readme/model-providers). diff --git a/README_SI.md b/README_SI.md index caa5975973..14de1ea792 100644 --- a/README_SI.md +++ b/README_SI.md @@ -1,259 +1,254 @@ -![cover-v5-optimized](https://github.com/langgenius/dify/assets/13230914/f9e19af5-61ba-4119-b926-d10c4c06ebab) - -

- 📌 Predstavljamo nalaganje datotek Dify Workflow: znova ustvarite Google NotebookLM Podcast -

- -

- Dify Cloud · - Samostojno gostovanje · - Dokumentacija · - Pregled ponudb izdelkov Dify -

- -

- - Static Badge - - Static Badge - - chat on Discord - - follow on X(Twitter) - - follow on LinkedIn - - Docker Pulls - - Commits last month - - Issues closed - - Discussion posts -

- -

- README in English - 简体中文版自述文件 - 日本語のREADME - README en Español - README en Français - README tlhIngan Hol - README in Korean - README بالعربية - Türkçe README - README Tiếng Việt - README Slovenščina - README in বাংলা -

- - -Dify je odprtokodna platforma za razvoj aplikacij LLM. Njegov intuitivni vmesnik združuje agentski potek dela z umetno inteligenco, cevovod RAG, zmogljivosti agentov, upravljanje modelov, funkcije opazovanja in več, kar vam omogoča hiter prehod od prototipa do proizvodnje. - -## Hitri začetek -> Preden namestite Dify, se prepričajte, da vaša naprava izpolnjuje naslednje minimalne sistemske zahteve: -> ->- CPU >= 2 Core ->- RAM >= 4 GiB - -
- -Najlažji način za zagon strežnika Dify je prek docker compose . Preden zaženete Dify z naslednjimi ukazi, se prepričajte, da sta Docker in Docker Compose nameščena na vašem računalniku: - -```bash -cd dify -cd docker -cp .env.example .env -docker compose up -d -``` - -Po zagonu lahko dostopate do nadzorne plošče Dify v brskalniku na [http://localhost/install](http://localhost/install) in začnete postopek inicializacije. - -#### Iskanje pomoči -Prosimo, glejte naša pogosta vprašanja [FAQ](https://docs.dify.ai/getting-started/install-self-hosted/faqs) če naletite na težave pri nastavitvi Dify. Če imate še vedno težave, se obrnite na [skupnost ali nas](#community--contact). - -> Če želite prispevati k Difyju ali narediti dodaten razvoj, glejte naš vodnik za [uvajanje iz izvorne kode](https://docs.dify.ai/getting-started/install-self-hosted/local-source-code) - -## Ključne značilnosti -**1. Potek dela**: - Zgradite in preizkusite zmogljive poteke dela AI na vizualnem platnu, pri čemer izkoristite vse naslednje funkcije in več. - - - https://github.com/langgenius/dify/assets/13230914/356df23e-1604-483d-80a6-9517ece318aa - - - -**2. Celovita podpora za modele**: - Brezhibna integracija s stotinami lastniških/odprtokodnih LLM-jev ducatov ponudnikov sklepanja in samostojnih rešitev, ki pokrivajo GPT, Mistral, Llama3 in vse modele, združljive z API-jem OpenAI. Celoten seznam podprtih ponudnikov modelov najdete [tukaj](https://docs.dify.ai/getting-started/readme/model-providers). - -![providers-v5](https://github.com/langgenius/dify/assets/13230914/5a17bdbe-097a-4100-8363-40255b70f6e3) - - -**3. Prompt IDE**: - intuitivni vmesnik za ustvarjanje pozivov, primerjavo zmogljivosti modela in dodajanje dodatnih funkcij, kot je pretvorba besedila v govor, aplikaciji, ki temelji na klepetu. - -**4. RAG Pipeline**: - E Obsežne zmogljivosti RAG, ki pokrivajo vse od vnosa dokumenta do priklica, s podporo za ekstrakcijo besedila iz datotek PDF, PPT in drugih običajnih formatov dokumentov. - -**5. Agent capabilities**: - definirate lahko agente, ki temeljijo na klicanju funkcij LLM ali ReAct, in dodate vnaprej izdelana orodja ali orodja po meri za agenta. Dify ponuja več kot 50 vgrajenih orodij za agente AI, kot so Google Search, DALL·E, Stable Diffusion in WolframAlpha. - -**6. LLMOps**: - Spremljajte in analizirajte dnevnike aplikacij in učinkovitost skozi čas. Pozive, nabore podatkov in modele lahko nenehno izboljšujete na podlagi proizvodnih podatkov in opomb. - -**7. Backend-as-a-Service**: - AVse ponudbe Difyja so opremljene z ustreznimi API-ji, tako da lahko Dify brez težav integrirate v svojo poslovno logiko. - -## Primerjava Funkcij - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
FunkcijaDify.AILangChainFlowiseOpenAI Assistants API
Programski pristopAPI + usmerjeno v aplikacijePython kodaUsmerjeno v aplikacijeUsmerjeno v API
Podprti LLM-jiBogata izbiraBogata izbiraBogata izbiraSamo OpenAI
RAG pogon
Agent
Potek dela
Spremljanje
Funkcija za podjetja (SSO/nadzor dostopa)
Lokalna namestitev
- -## Uporaba Dify - -- **Cloud
** -Gostimo storitev Dify Cloud za vsakogar, ki jo lahko preizkusite brez nastavitev. Zagotavlja vse zmožnosti različice za samostojno namestitev in vključuje 200 brezplačnih klicev GPT-4 v načrtu peskovnika. - -- **Self-hosting Dify Community Edition
** -Hitro zaženite Dify v svojem okolju s tem [začetnim vodnikom](#quick-start) . Za dodatne reference in podrobnejša navodila uporabite našo [dokumentacijo](https://docs.dify.ai) . - - -- **Dify za podjetja/organizacije
** -Ponujamo dodatne funkcije, osredotočene na podjetja. Zabeležite svoja vprašanja prek tega klepetalnega robota ali nam pošljite e-pošto, da se pogovorimo o potrebah podjetja.
- > Za novoustanovljena podjetja in mala podjetja, ki uporabljajo AWS, si oglejte Dify Premium na AWS Marketplace in ga z enim klikom uvedite v svoj AWS VPC. To je cenovno ugodna ponudba AMI z možnostjo ustvarjanja aplikacij z logotipom in blagovno znamko po meri. - - -## Staying ahead - -Star Dify on GitHub and be instantly notified of new releases. - -![star-us](https://github.com/langgenius/dify/assets/13230914/b823edc1-6388-4e25-ad45-2f6b187adbb4) - - -## Napredne nastavitve - -Če morate prilagoditi konfiguracijo, si oglejte komentarje v naši datoteki .env.example in posodobite ustrezne vrednosti v svoji .env datoteki. Poleg tega boste morda morali prilagoditi docker-compose.yamlsamo datoteko, na primer spremeniti različice slike, preslikave vrat ali namestitve nosilca, glede na vaše specifično okolje in zahteve za uvajanje. Po kakršnih koli spremembah ponovno zaženite docker-compose up -d. Celoten seznam razpoložljivih spremenljivk okolja najdete tukaj . - -Če želite konfigurirati visoko razpoložljivo nastavitev, so na voljo Helm Charts in datoteke YAML, ki jih prispeva skupnost, ki omogočajo uvedbo Difyja v Kubernetes. - -- [Helm Chart by @LeoQuote](https://github.com/douban/charts/tree/master/charts/dify) -- [Helm Chart by @BorisPolonsky](https://github.com/BorisPolonsky/dify-helm) -- [YAML file by @Winson-030](https://github.com/Winson-030/dify-kubernetes) -- [YAML file by @wyy-holding](https://github.com/wyy-holding/dify-k8s) - -#### Uporaba Terraform za uvajanje - -namestite Dify v Cloud Platform z enim klikom z uporabo [terraform](https://www.terraform.io/) - -##### Azure Global -- [Azure Terraform by @nikawang](https://github.com/nikawang/dify-azure-terraform) - -##### Google Cloud -- [Google Cloud Terraform by @sotazum](https://github.com/DeNA/dify-google-cloud-terraform) - -#### Uporaba AWS CDK za uvajanje - -Uvedite Dify v AWS z uporabo [CDK](https://aws.amazon.com/cdk/) - -##### AWS -- [AWS CDK by @KevinZhao](https://github.com/aws-samples/solution-for-deploying-dify-on-aws) - -## Prispevam - -Za tiste, ki bi radi prispevali kodo, si oglejte naš vodnik za prispevke . Hkrati vas prosimo, da podprete Dify tako, da ga delite na družbenih medijih ter na dogodkih in konferencah. - - - -> Iščemo sodelavce za pomoč pri prevajanju Difyja v jezike, ki niso mandarinščina ali angleščina. Če želite pomagati, si oglejte i18n README za več informacij in nam pustite komentar v global-userskanalu našega strežnika skupnosti Discord . - -## Skupnost in stik - -* [Github Discussion](https://github.com/langgenius/dify/discussions). Najboljše za: izmenjavo povratnih informacij in postavljanje vprašanj. -* [GitHub Issues](https://github.com/langgenius/dify/issues). Najboljše za: hrošče, na katere naletite pri uporabi Dify.AI, in predloge funkcij. Oglejte si naš [vodnik za prispevke](https://github.com/langgenius/dify/blob/main/CONTRIBUTING.md). -* [Discord](https://discord.gg/FngNHpbcY7). Najboljše za: deljenje vaših aplikacij in druženje s skupnostjo. -* [X(Twitter)](https://twitter.com/dify_ai). Najboljše za: deljenje vaših aplikacij in druženje s skupnostjo. - -**Contributors** - - - - - -## Star history - -[![Star History Chart](https://api.star-history.com/svg?repos=langgenius/dify&type=Date)](https://star-history.com/#langgenius/dify&Date) - - -## Varnostno razkritje - -Zaradi zaščite vaše zasebnosti se izogibajte objavljanju varnostnih vprašanj na GitHub. Namesto tega pošljite vprašanja na security@dify.ai in zagotovili vam bomo podrobnejši odgovor. - -## Licenca - -To skladišče je na voljo pod [odprtokodno licenco Dify](LICENSE) , ki je v bistvu Apache 2.0 z nekaj dodatnimi omejitvami. +![cover-v5-optimized](./images/GitHub_README_if.png) + +

+ 📌 Predstavljamo nalaganje datotek Dify Workflow: znova ustvarite Google NotebookLM Podcast +

+ +

+ Dify Cloud · + Samostojno gostovanje · + Dokumentacija · + Pregled ponudb izdelkov Dify +

+ +

+ + Static Badge + + Static Badge + + chat on Discord + + follow on X(Twitter) + + follow on LinkedIn + + Docker Pulls + + Commits last month + + Issues closed + + Discussion posts +

+ +

+ README in English + 简体中文版自述文件 + 日本語のREADME + README en Español + README en Français + README tlhIngan Hol + README in Korean + README بالعربية + Türkçe README + README Tiếng Việt + README Slovenščina + README in বাংলা +

+ + +Dify je odprtokodna platforma za razvoj aplikacij LLM. Njegov intuitivni vmesnik združuje agentski potek dela z umetno inteligenco, cevovod RAG, zmogljivosti agentov, upravljanje modelov, funkcije opazovanja in več, kar vam omogoča hiter prehod od prototipa do proizvodnje. + +## Hitri začetek +> Preden namestite Dify, se prepričajte, da vaša naprava izpolnjuje naslednje minimalne sistemske zahteve: +> +>- CPU >= 2 Core +>- RAM >= 4 GiB + +
+ +Najlažji način za zagon strežnika Dify je prek docker compose . Preden zaženete Dify z naslednjimi ukazi, se prepričajte, da sta Docker in Docker Compose nameščena na vašem računalniku: + +```bash +cd dify +cd docker +cp .env.example .env +docker compose up -d +``` + +Po zagonu lahko dostopate do nadzorne plošče Dify v brskalniku na [http://localhost/install](http://localhost/install) in začnete postopek inicializacije. + +#### Iskanje pomoči +Prosimo, glejte naša pogosta vprašanja [FAQ](https://docs.dify.ai/getting-started/install-self-hosted/faqs) če naletite na težave pri nastavitvi Dify. Če imate še vedno težave, se obrnite na [skupnost ali nas](#community--contact). + +> Če želite prispevati k Difyju ali narediti dodaten razvoj, glejte naš vodnik za [uvajanje iz izvorne kode](https://docs.dify.ai/getting-started/install-self-hosted/local-source-code) + +## Ključne značilnosti +**1. Potek dela**: + Zgradite in preizkusite zmogljive poteke dela AI na vizualnem platnu, pri čemer izkoristite vse naslednje funkcije in več. + +**2. Celovita podpora za modele**: + Brezhibna integracija s stotinami lastniških/odprtokodnih LLM-jev ducatov ponudnikov sklepanja in samostojnih rešitev, ki pokrivajo GPT, Mistral, Llama3 in vse modele, združljive z API-jem OpenAI. Celoten seznam podprtih ponudnikov modelov najdete [tukaj](https://docs.dify.ai/getting-started/readme/model-providers). + +![providers-v5](https://github.com/langgenius/dify/assets/13230914/5a17bdbe-097a-4100-8363-40255b70f6e3) + + +**3. Prompt IDE**: + intuitivni vmesnik za ustvarjanje pozivov, primerjavo zmogljivosti modela in dodajanje dodatnih funkcij, kot je pretvorba besedila v govor, aplikaciji, ki temelji na klepetu. + +**4. RAG Pipeline**: + E Obsežne zmogljivosti RAG, ki pokrivajo vse od vnosa dokumenta do priklica, s podporo za ekstrakcijo besedila iz datotek PDF, PPT in drugih običajnih formatov dokumentov. + +**5. Agent capabilities**: + definirate lahko agente, ki temeljijo na klicanju funkcij LLM ali ReAct, in dodate vnaprej izdelana orodja ali orodja po meri za agenta. Dify ponuja več kot 50 vgrajenih orodij za agente AI, kot so Google Search, DALL·E, Stable Diffusion in WolframAlpha. + +**6. LLMOps**: + Spremljajte in analizirajte dnevnike aplikacij in učinkovitost skozi čas. Pozive, nabore podatkov in modele lahko nenehno izboljšujete na podlagi proizvodnih podatkov in opomb. + +**7. Backend-as-a-Service**: + AVse ponudbe Difyja so opremljene z ustreznimi API-ji, tako da lahko Dify brez težav integrirate v svojo poslovno logiko. + +## Primerjava Funkcij + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FunkcijaDify.AILangChainFlowiseOpenAI Assistants API
Programski pristopAPI + usmerjeno v aplikacijePython kodaUsmerjeno v aplikacijeUsmerjeno v API
Podprti LLM-jiBogata izbiraBogata izbiraBogata izbiraSamo OpenAI
RAG pogon
Agent
Potek dela
Spremljanje
Funkcija za podjetja (SSO/nadzor dostopa)
Lokalna namestitev
+ +## Uporaba Dify + +- **Cloud
** +Gostimo storitev Dify Cloud za vsakogar, ki jo lahko preizkusite brez nastavitev. Zagotavlja vse zmožnosti različice za samostojno namestitev in vključuje 200 brezplačnih klicev GPT-4 v načrtu peskovnika. + +- **Self-hosting Dify Community Edition
** +Hitro zaženite Dify v svojem okolju s tem [začetnim vodnikom](#quick-start) . Za dodatne reference in podrobnejša navodila uporabite našo [dokumentacijo](https://docs.dify.ai) . + + +- **Dify za podjetja/organizacije
** +Ponujamo dodatne funkcije, osredotočene na podjetja. Zabeležite svoja vprašanja prek tega klepetalnega robota ali nam pošljite e-pošto, da se pogovorimo o potrebah podjetja.
+ > Za novoustanovljena podjetja in mala podjetja, ki uporabljajo AWS, si oglejte Dify Premium na AWS Marketplace in ga z enim klikom uvedite v svoj AWS VPC. To je cenovno ugodna ponudba AMI z možnostjo ustvarjanja aplikacij z logotipom in blagovno znamko po meri. + + +## Staying ahead + +Star Dify on GitHub and be instantly notified of new releases. + +![star-us](https://github.com/langgenius/dify/assets/13230914/b823edc1-6388-4e25-ad45-2f6b187adbb4) + + +## Napredne nastavitve + +Če morate prilagoditi konfiguracijo, si oglejte komentarje v naši datoteki .env.example in posodobite ustrezne vrednosti v svoji .env datoteki. Poleg tega boste morda morali prilagoditi docker-compose.yamlsamo datoteko, na primer spremeniti različice slike, preslikave vrat ali namestitve nosilca, glede na vaše specifično okolje in zahteve za uvajanje. Po kakršnih koli spremembah ponovno zaženite docker-compose up -d. Celoten seznam razpoložljivih spremenljivk okolja najdete tukaj . + +Če želite konfigurirati visoko razpoložljivo nastavitev, so na voljo Helm Charts in datoteke YAML, ki jih prispeva skupnost, ki omogočajo uvedbo Difyja v Kubernetes. + +- [Helm Chart by @LeoQuote](https://github.com/douban/charts/tree/master/charts/dify) +- [Helm Chart by @BorisPolonsky](https://github.com/BorisPolonsky/dify-helm) +- [YAML file by @Winson-030](https://github.com/Winson-030/dify-kubernetes) +- [YAML file by @wyy-holding](https://github.com/wyy-holding/dify-k8s) + +#### Uporaba Terraform za uvajanje + +namestite Dify v Cloud Platform z enim klikom z uporabo [terraform](https://www.terraform.io/) + +##### Azure Global +- [Azure Terraform by @nikawang](https://github.com/nikawang/dify-azure-terraform) + +##### Google Cloud +- [Google Cloud Terraform by @sotazum](https://github.com/DeNA/dify-google-cloud-terraform) + +#### Uporaba AWS CDK za uvajanje + +Uvedite Dify v AWS z uporabo [CDK](https://aws.amazon.com/cdk/) + +##### AWS +- [AWS CDK by @KevinZhao](https://github.com/aws-samples/solution-for-deploying-dify-on-aws) + +## Prispevam + +Za tiste, ki bi radi prispevali kodo, si oglejte naš vodnik za prispevke . Hkrati vas prosimo, da podprete Dify tako, da ga delite na družbenih medijih ter na dogodkih in konferencah. + + + +> Iščemo sodelavce za pomoč pri prevajanju Difyja v jezike, ki niso mandarinščina ali angleščina. Če želite pomagati, si oglejte i18n README za več informacij in nam pustite komentar v global-userskanalu našega strežnika skupnosti Discord . + +## Skupnost in stik + +* [GitHub Discussion](https://github.com/langgenius/dify/discussions). Najboljše za: izmenjavo povratnih informacij in postavljanje vprašanj. +* [GitHub Issues](https://github.com/langgenius/dify/issues). Najboljše za: hrošče, na katere naletite pri uporabi Dify.AI, in predloge funkcij. Oglejte si naš [vodnik za prispevke](https://github.com/langgenius/dify/blob/main/CONTRIBUTING.md). +* [Discord](https://discord.gg/FngNHpbcY7). Najboljše za: deljenje vaših aplikacij in druženje s skupnostjo. +* [X(Twitter)](https://twitter.com/dify_ai). Najboljše za: deljenje vaših aplikacij in druženje s skupnostjo. + +**Contributors** + + + + + +## Star history + +[![Star History Chart](https://api.star-history.com/svg?repos=langgenius/dify&type=Date)](https://star-history.com/#langgenius/dify&Date) + + +## Varnostno razkritje + +Zaradi zaščite vaše zasebnosti se izogibajte objavljanju varnostnih vprašanj na GitHub. Namesto tega pošljite vprašanja na security@dify.ai in zagotovili vam bomo podrobnejši odgovor. + +## Licenca + +To skladišče je na voljo pod [odprtokodno licenco Dify](LICENSE) , ki je v bistvu Apache 2.0 z nekaj dodatnimi omejitvami. diff --git a/README_TR.md b/README_TR.md index ab2853a019..563a05af3c 100644 --- a/README_TR.md +++ b/README_TR.md @@ -1,4 +1,4 @@ -![cover-v5-optimized](https://github.com/langgenius/dify/assets/13230914/f9e19af5-61ba-4119-b926-d10c4c06ebab) +![cover-v5-optimized](./images/GitHub_README_if.png)

Dify Bulut · @@ -55,11 +55,6 @@ Dify, açık kaynaklı bir LLM uygulama geliştirme platformudur. Sezgisel aray **1. Workflow**: Görsel bir arayüz üzerinde güçlü AI iş akışları oluşturun ve test edin, aşağıdaki tüm özellikleri ve daha fazlasını kullanarak. - - https://github.com/langgenius/dify/assets/13230914/356df23e-1604-483d-80a6-9517ece318aa - - - **2. Kapsamlı model desteği**: Çok sayıda çıkarım sağlayıcısı ve kendi kendine barındırılan çözümlerden yüzlerce özel / açık kaynaklı LLM ile sorunsuz entegrasyon sağlar. GPT, Mistral, Llama3 ve OpenAI API uyumlu tüm modelleri kapsar. Desteklenen model sağlayıcılarının tam listesine [buradan](https://docs.dify.ai/getting-started/readme/model-providers) ulaşabilirsiniz. @@ -232,7 +227,7 @@ Aynı zamanda, lütfen Dify'ı sosyal medyada, etkinliklerde ve konferanslarda p ## Topluluk & iletişim -* [Github Tartışmaları](https://github.com/langgenius/dify/discussions). En uygun: geri bildirim paylaşmak ve soru sormak için. +* [GitHub Tartışmaları](https://github.com/langgenius/dify/discussions). En uygun: geri bildirim paylaşmak ve soru sormak için. * [GitHub Sorunları](https://github.com/langgenius/dify/issues). En uygun: Dify.AI kullanırken karşılaştığınız hatalar ve özellik önerileri için. [Katkı Kılavuzumuza](https://github.com/langgenius/dify/blob/main/CONTRIBUTING.md) bakın. * [Discord](https://discord.gg/FngNHpbcY7). En uygun: uygulamalarınızı paylaşmak ve toplulukla vakit geçirmek için. * [X(Twitter)](https://twitter.com/dify_ai). En uygun: uygulamalarınızı paylaşmak ve toplulukla vakit geçirmek için. diff --git a/README_TW.md b/README_TW.md index 8263a22b64..f4a76ac109 100644 --- a/README_TW.md +++ b/README_TW.md @@ -1,4 +1,4 @@ -![cover-v5-optimized](https://github.com/langgenius/dify/assets/13230914/f9e19af5-61ba-4119-b926-d10c4c06ebab) +![cover-v5-optimized](./images/GitHub_README_if.png)

📌 介紹 Dify 工作流程檔案上傳功能:重現 Google NotebookLM Podcast @@ -86,8 +86,6 @@ docker compose up -d **1. 工作流程**: 在視覺化畫布上建立和測試強大的 AI 工作流程,利用以下所有功能及更多。 -https://github.com/langgenius/dify/assets/13230914/356df23e-1604-483d-80a6-9517ece318aa - **2. 全面的模型支援**: 無縫整合來自數十個推理提供商和自託管解決方案的數百個專有/開源 LLM,涵蓋 GPT、Mistral、Llama3 和任何與 OpenAI API 兼容的模型。您可以在[此處](https://docs.dify.ai/getting-started/readme/model-providers)找到支援的模型提供商完整列表。 @@ -235,7 +233,7 @@ Dify 的所有功能都提供相應的 API,因此您可以輕鬆地將 Dify ## 社群與聯絡方式 -- [Github Discussion](https://github.com/langgenius/dify/discussions):最適合分享反饋和提問。 +- [GitHub Discussion](https://github.com/langgenius/dify/discussions):最適合分享反饋和提問。 - [GitHub Issues](https://github.com/langgenius/dify/issues):最適合報告使用 Dify.AI 時遇到的問題和提出功能建議。請參閱我們的[貢獻指南](https://github.com/langgenius/dify/blob/main/CONTRIBUTING.md)。 - [Discord](https://discord.gg/FngNHpbcY7):最適合分享您的應用程式並與社群互動。 - [X(Twitter)](https://twitter.com/dify_ai):最適合分享您的應用程式並與社群互動。 diff --git a/README_VI.md b/README_VI.md index 852ed7aaa0..4e1e05cbf3 100644 --- a/README_VI.md +++ b/README_VI.md @@ -1,4 +1,4 @@ -![cover-v5-optimized](https://github.com/langgenius/dify/assets/13230914/f9e19af5-61ba-4119-b926-d10c4c06ebab) +![cover-v5-optimized](./images/GitHub_README_if.png)

Dify Cloud · @@ -55,11 +55,6 @@ Dify là một nền tảng phát triển ứng dụng LLM mã nguồn mở. Gia **1. Quy trình làm việc**: Xây dựng và kiểm tra các quy trình làm việc AI mạnh mẽ trên một canvas trực quan, tận dụng tất cả các tính năng sau đây và hơn thế nữa. - - https://github.com/langgenius/dify/assets/13230914/356df23e-1604-483d-80a6-9517ece318aa - - - **2. Hỗ trợ mô hình toàn diện**: Tích hợp liền mạch với hàng trăm mô hình LLM độc quyền / mã nguồn mở từ hàng chục nhà cung cấp suy luận và giải pháp tự lưu trữ, bao gồm GPT, Mistral, Llama3, và bất kỳ mô hình tương thích API OpenAI nào. Danh sách đầy đủ các nhà cung cấp mô hình được hỗ trợ có thể được tìm thấy [tại đây](https://docs.dify.ai/getting-started/readme/model-providers). diff --git a/api/.dockerignore b/api/.dockerignore index 447edcda08..a0ce59d221 100644 --- a/api/.dockerignore +++ b/api/.dockerignore @@ -16,4 +16,4 @@ logs .ruff_cache # venv -.venv \ No newline at end of file +.venv diff --git a/api/.env.example b/api/.env.example index b5820fcdc2..7878308588 100644 --- a/api/.env.example +++ b/api/.env.example @@ -152,6 +152,7 @@ QDRANT_API_KEY=difyai123456 QDRANT_CLIENT_TIMEOUT=20 QDRANT_GRPC_ENABLED=false QDRANT_GRPC_PORT=6334 +QDRANT_REPLICATION_FACTOR=1 #Couchbase configuration COUCHBASE_CONNECTION_STRING=127.0.0.1 @@ -269,6 +270,7 @@ OPENSEARCH_PORT=9200 OPENSEARCH_USER=admin OPENSEARCH_PASSWORD=admin OPENSEARCH_SECURE=true +OPENSEARCH_VERIFY_CERTS=true # Baidu configuration BAIDU_VECTOR_DB_ENDPOINT=http://127.0.0.1:5287 @@ -297,6 +299,7 @@ LINDORM_URL=http://ld-*******************-proxy-search-pub.lindorm.aliyuncs.com: LINDORM_USERNAME=admin LINDORM_PASSWORD=admin USING_UGC_INDEX=False +LINDORM_QUERY_TIMEOUT=1 # OceanBase Vector configuration OCEANBASE_VECTOR_HOST=127.0.0.1 @@ -347,6 +350,7 @@ SENTRY_DSN= # DEBUG DEBUG=false +ENABLE_REQUEST_LOGGING=False SQLALCHEMY_ECHO=false # Notion import configuration, support public and internal @@ -475,6 +479,7 @@ LOGIN_LOCKOUT_DURATION=86400 ENABLE_OTEL=false OTLP_BASE_ENDPOINT=http://localhost:4318 OTLP_API_KEY= +OTEL_EXPORTER_OTLP_PROTOCOL= OTEL_EXPORTER_TYPE=otlp OTEL_SAMPLING_RATE=0.1 OTEL_BATCH_EXPORT_SCHEDULE_DELAY=5000 @@ -486,3 +491,10 @@ OTEL_METRIC_EXPORT_TIMEOUT=30000 # Prevent Clickjacking ALLOW_EMBED=false + +# Dataset queue monitor configuration +QUEUE_MONITOR_THRESHOLD=200 +# You can configure multiple ones, separated by commas. eg: test1@dify.ai,test2@dify.ai +QUEUE_MONITOR_ALERT_EMAILS= +# Monitor interval in minutes, default is 30 minutes +QUEUE_MONITOR_INTERVAL=30 diff --git a/api/.ruff.toml b/api/.ruff.toml index 41a24abad9..facb0d5419 100644 --- a/api/.ruff.toml +++ b/api/.ruff.toml @@ -43,6 +43,7 @@ select = [ "S307", # suspicious-eval-usage, disallow use of `eval` and `ast.literal_eval` "S301", # suspicious-pickle-usage, disallow use of `pickle` and its wrappers. "S302", # suspicious-marshal-usage, disallow use of `marshal` module + "S311", # suspicious-non-cryptographic-random-usage ] ignore = [ diff --git a/api/Dockerfile b/api/Dockerfile index cff696ff56..7e4997507f 100644 --- a/api/Dockerfile +++ b/api/Dockerfile @@ -4,7 +4,7 @@ FROM python:3.12-slim-bookworm AS base WORKDIR /app/api # Install uv -ENV UV_VERSION=0.6.14 +ENV UV_VERSION=0.7.11 RUN pip install --no-cache-dir uv==${UV_VERSION} diff --git a/api/README.md b/api/README.md index c542f11b16..9308d5dc44 100644 --- a/api/README.md +++ b/api/README.md @@ -90,3 +90,4 @@ ```bash uv run -P api bash dev/pytest/pytest_all_tests.sh ``` + diff --git a/api/app.py b/api/app.py index 9830a80904..4f393f6c20 100644 --- a/api/app.py +++ b/api/app.py @@ -18,7 +18,7 @@ else: # so we need to disable gevent in debug mode. # If you are using debugpy and set GEVENT_SUPPORT=True, you can debug with gevent. if (flask_debug := os.environ.get("FLASK_DEBUG", "0")) and flask_debug.lower() in {"false", "0", "no"}: - from gevent import monkey # type: ignore + from gevent import monkey # gevent monkey.patch_all() diff --git a/api/app_factory.py b/api/app_factory.py index 9648d770ab..3a258be28f 100644 --- a/api/app_factory.py +++ b/api/app_factory.py @@ -52,10 +52,9 @@ def initialize_extensions(app: DifyApp): ext_mail, ext_migrate, ext_otel, - ext_otel_patch, ext_proxy_fix, ext_redis, - ext_repositories, + ext_request_logging, ext_sentry, ext_set_secretkey, ext_storage, @@ -76,7 +75,6 @@ def initialize_extensions(app: DifyApp): ext_migrate, ext_redis, ext_storage, - ext_repositories, ext_celery, ext_login, ext_mail, @@ -85,8 +83,8 @@ def initialize_extensions(app: DifyApp): ext_proxy_fix, ext_blueprints, ext_commands, - ext_otel_patch, # Apply patch before initializing OpenTelemetry ext_otel, + ext_request_logging, ] for ext in extensions: short_name = ext.__name__.split(".")[-1] diff --git a/api/commands.py b/api/commands.py index e70d6e0b49..0a6cc61a68 100644 --- a/api/commands.py +++ b/api/commands.py @@ -6,6 +6,7 @@ from typing import Optional import click from flask import current_app +from sqlalchemy import select from werkzeug.exceptions import NotFound from configs import dify_config @@ -17,6 +18,7 @@ from core.rag.models.document import Document from events.app_event import app_was_created from extensions.ext_database import db from extensions.ext_redis import redis_client +from extensions.ext_storage import storage from libs.helper import email as email_validate from libs.password import hash_password, password_pattern, valid_password from libs.rsa import generate_key_pair @@ -25,7 +27,7 @@ from models.dataset import Dataset, DatasetCollectionBinding, DatasetMetadata, D from models.dataset import Document as DatasetDocument from models.model import Account, App, AppAnnotationSetting, AppMode, Conversation, MessageAnnotation from models.provider import Provider, ProviderModel -from services.account_service import RegisterService, TenantService +from services.account_service import AccountService, RegisterService, TenantService from services.clear_free_plan_tenant_expired_logs import ClearFreePlanTenantExpiredLogs from services.plugin.data_migration import PluginDataMigration from services.plugin.plugin_migration import PluginMigration @@ -66,6 +68,7 @@ def reset_password(email, new_password, password_confirm): account.password = base64_password_hashed account.password_salt = base64_salt db.session.commit() + AccountService.reset_login_error_rate_limit(email) click.echo(click.style("Password reset successfully.", fg="green")) @@ -271,6 +274,7 @@ def migrate_knowledge_vector_database(): upper_collection_vector_types = { VectorType.MILVUS, VectorType.PGVECTOR, + VectorType.VASTBASE, VectorType.RELYT, VectorType.WEAVIATE, VectorType.ORACLE, @@ -295,11 +299,11 @@ def migrate_knowledge_vector_database(): page = 1 while True: try: - datasets = ( - Dataset.query.filter(Dataset.indexing_technique == "high_quality") - .order_by(Dataset.created_at.desc()) - .paginate(page=page, per_page=50) + stmt = ( + select(Dataset).filter(Dataset.indexing_technique == "high_quality").order_by(Dataset.created_at.desc()) ) + + datasets = db.paginate(select=stmt, page=page, per_page=50, max_per_page=50, error_out=False) except NotFound: break @@ -442,13 +446,13 @@ def convert_to_agent_apps(): WHERE a.mode = 'chat' AND am.agent_mode is not null AND ( - am.agent_mode like '%"strategy": "function_call"%' + am.agent_mode like '%"strategy": "function_call"%' OR am.agent_mode like '%"strategy": "react"%' - ) + ) AND ( - am.agent_mode like '{"enabled": true%' + am.agent_mode like '{"enabled": true%' OR am.agent_mode like '{"max_iteration": %' - ) ORDER BY a.created_at DESC LIMIT 1000 + ) ORDER BY a.created_at DESC LIMIT 1000 """ with db.engine.begin() as conn: @@ -549,11 +553,12 @@ def old_metadata_migration(): page = 1 while True: try: - documents = ( - DatasetDocument.query.filter(DatasetDocument.doc_metadata is not None) + stmt = ( + select(DatasetDocument) + .filter(DatasetDocument.doc_metadata.is_not(None)) .order_by(DatasetDocument.created_at.desc()) - .paginate(page=page, per_page=50) ) + documents = db.paginate(select=stmt, page=page, per_page=50, max_per_page=50, error_out=False) except NotFound: break if not documents: @@ -590,11 +595,15 @@ def old_metadata_migration(): ) db.session.add(dataset_metadata_binding) else: - dataset_metadata_binding = DatasetMetadataBinding.query.filter( - DatasetMetadataBinding.dataset_id == document.dataset_id, - DatasetMetadataBinding.document_id == document.id, - DatasetMetadataBinding.metadata_id == dataset_metadata.id, - ).first() + dataset_metadata_binding = ( + db.session.query(DatasetMetadataBinding) # type: ignore + .filter( + DatasetMetadataBinding.dataset_id == document.dataset_id, + DatasetMetadataBinding.document_id == document.id, + DatasetMetadataBinding.metadata_id == dataset_metadata.id, + ) + .first() + ) if not dataset_metadata_binding: dataset_metadata_binding = DatasetMetadataBinding( tenant_id=document.tenant_id, @@ -666,7 +675,7 @@ def upgrade_db(): click.echo(click.style("Starting database migration.", fg="green")) # run db migration - import flask_migrate # type: ignore + import flask_migrate flask_migrate.upgrade() @@ -814,3 +823,334 @@ def clear_free_plan_tenant_expired_logs(days: int, batch: int, tenant_ids: list[ ClearFreePlanTenantExpiredLogs.process(days, batch, tenant_ids) click.echo(click.style("Clear free plan tenant expired logs completed.", fg="green")) + + +@click.option("-f", "--force", is_flag=True, help="Skip user confirmation and force the command to execute.") +@click.command("clear-orphaned-file-records", help="Clear orphaned file records.") +def clear_orphaned_file_records(force: bool): + """ + Clear orphaned file records in the database. + """ + + # define tables and columns to process + files_tables = [ + {"table": "upload_files", "id_column": "id", "key_column": "key"}, + {"table": "tool_files", "id_column": "id", "key_column": "file_key"}, + ] + ids_tables = [ + {"type": "uuid", "table": "message_files", "column": "upload_file_id"}, + {"type": "text", "table": "documents", "column": "data_source_info"}, + {"type": "text", "table": "document_segments", "column": "content"}, + {"type": "text", "table": "messages", "column": "answer"}, + {"type": "text", "table": "workflow_node_executions", "column": "inputs"}, + {"type": "text", "table": "workflow_node_executions", "column": "process_data"}, + {"type": "text", "table": "workflow_node_executions", "column": "outputs"}, + {"type": "text", "table": "conversations", "column": "introduction"}, + {"type": "text", "table": "conversations", "column": "system_instruction"}, + {"type": "text", "table": "accounts", "column": "avatar"}, + {"type": "text", "table": "apps", "column": "icon"}, + {"type": "text", "table": "sites", "column": "icon"}, + {"type": "json", "table": "messages", "column": "inputs"}, + {"type": "json", "table": "messages", "column": "message"}, + ] + + # notify user and ask for confirmation + click.echo( + click.style( + "This command will first find and delete orphaned file records from the message_files table,", fg="yellow" + ) + ) + click.echo( + click.style( + "and then it will find and delete orphaned file records in the following tables:", + fg="yellow", + ) + ) + for files_table in files_tables: + click.echo(click.style(f"- {files_table['table']}", fg="yellow")) + click.echo( + click.style("The following tables and columns will be scanned to find orphaned file records:", fg="yellow") + ) + for ids_table in ids_tables: + click.echo(click.style(f"- {ids_table['table']} ({ids_table['column']})", fg="yellow")) + click.echo("") + + click.echo(click.style("!!! USE WITH CAUTION !!!", fg="red")) + click.echo( + click.style( + ( + "Since not all patterns have been fully tested, " + "please note that this command may delete unintended file records." + ), + fg="yellow", + ) + ) + click.echo( + click.style("This cannot be undone. Please make sure to back up your database before proceeding.", fg="yellow") + ) + click.echo( + click.style( + ( + "It is also recommended to run this during the maintenance window, " + "as this may cause high load on your instance." + ), + fg="yellow", + ) + ) + if not force: + click.confirm("Do you want to proceed?", abort=True) + + # start the cleanup process + click.echo(click.style("Starting orphaned file records cleanup.", fg="white")) + + # clean up the orphaned records in the message_files table where message_id doesn't exist in messages table + try: + click.echo( + click.style("- Listing message_files records where message_id doesn't exist in messages table", fg="white") + ) + query = ( + "SELECT mf.id, mf.message_id " + "FROM message_files mf LEFT JOIN messages m ON mf.message_id = m.id " + "WHERE m.id IS NULL" + ) + orphaned_message_files = [] + with db.engine.begin() as conn: + rs = conn.execute(db.text(query)) + for i in rs: + orphaned_message_files.append({"id": str(i[0]), "message_id": str(i[1])}) + + if orphaned_message_files: + click.echo(click.style(f"Found {len(orphaned_message_files)} orphaned message_files records:", fg="white")) + for record in orphaned_message_files: + click.echo(click.style(f" - id: {record['id']}, message_id: {record['message_id']}", fg="black")) + + if not force: + click.confirm( + ( + f"Do you want to proceed " + f"to delete all {len(orphaned_message_files)} orphaned message_files records?" + ), + abort=True, + ) + + click.echo(click.style("- Deleting orphaned message_files records", fg="white")) + query = "DELETE FROM message_files WHERE id IN :ids" + with db.engine.begin() as conn: + conn.execute(db.text(query), {"ids": tuple([record["id"] for record in orphaned_message_files])}) + click.echo( + click.style(f"Removed {len(orphaned_message_files)} orphaned message_files records.", fg="green") + ) + else: + click.echo(click.style("No orphaned message_files records found. There is nothing to delete.", fg="green")) + except Exception as e: + click.echo(click.style(f"Error deleting orphaned message_files records: {str(e)}", fg="red")) + + # clean up the orphaned records in the rest of the *_files tables + try: + # fetch file id and keys from each table + all_files_in_tables = [] + for files_table in files_tables: + click.echo(click.style(f"- Listing file records in table {files_table['table']}", fg="white")) + query = f"SELECT {files_table['id_column']}, {files_table['key_column']} FROM {files_table['table']}" + with db.engine.begin() as conn: + rs = conn.execute(db.text(query)) + for i in rs: + all_files_in_tables.append({"table": files_table["table"], "id": str(i[0]), "key": i[1]}) + click.echo(click.style(f"Found {len(all_files_in_tables)} files in tables.", fg="white")) + + # fetch referred table and columns + guid_regexp = "[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}" + all_ids_in_tables = [] + for ids_table in ids_tables: + query = "" + if ids_table["type"] == "uuid": + click.echo( + click.style( + f"- Listing file ids in column {ids_table['column']} in table {ids_table['table']}", fg="white" + ) + ) + query = ( + f"SELECT {ids_table['column']} FROM {ids_table['table']} WHERE {ids_table['column']} IS NOT NULL" + ) + with db.engine.begin() as conn: + rs = conn.execute(db.text(query)) + for i in rs: + all_ids_in_tables.append({"table": ids_table["table"], "id": str(i[0])}) + elif ids_table["type"] == "text": + click.echo( + click.style( + f"- Listing file-id-like strings in column {ids_table['column']} in table {ids_table['table']}", + fg="white", + ) + ) + query = ( + f"SELECT regexp_matches({ids_table['column']}, '{guid_regexp}', 'g') AS extracted_id " + f"FROM {ids_table['table']}" + ) + with db.engine.begin() as conn: + rs = conn.execute(db.text(query)) + for i in rs: + for j in i[0]: + all_ids_in_tables.append({"table": ids_table["table"], "id": j}) + elif ids_table["type"] == "json": + click.echo( + click.style( + ( + f"- Listing file-id-like JSON string in column {ids_table['column']} " + f"in table {ids_table['table']}" + ), + fg="white", + ) + ) + query = ( + f"SELECT regexp_matches({ids_table['column']}::text, '{guid_regexp}', 'g') AS extracted_id " + f"FROM {ids_table['table']}" + ) + with db.engine.begin() as conn: + rs = conn.execute(db.text(query)) + for i in rs: + for j in i[0]: + all_ids_in_tables.append({"table": ids_table["table"], "id": j}) + click.echo(click.style(f"Found {len(all_ids_in_tables)} file ids in tables.", fg="white")) + + except Exception as e: + click.echo(click.style(f"Error fetching keys: {str(e)}", fg="red")) + return + + # find orphaned files + all_files = [file["id"] for file in all_files_in_tables] + all_ids = [file["id"] for file in all_ids_in_tables] + orphaned_files = list(set(all_files) - set(all_ids)) + if not orphaned_files: + click.echo(click.style("No orphaned file records found. There is nothing to delete.", fg="green")) + return + click.echo(click.style(f"Found {len(orphaned_files)} orphaned file records.", fg="white")) + for file in orphaned_files: + click.echo(click.style(f"- orphaned file id: {file}", fg="black")) + if not force: + click.confirm(f"Do you want to proceed to delete all {len(orphaned_files)} orphaned file records?", abort=True) + + # delete orphaned records for each file + try: + for files_table in files_tables: + click.echo(click.style(f"- Deleting orphaned file records in table {files_table['table']}", fg="white")) + query = f"DELETE FROM {files_table['table']} WHERE {files_table['id_column']} IN :ids" + with db.engine.begin() as conn: + conn.execute(db.text(query), {"ids": tuple(orphaned_files)}) + except Exception as e: + click.echo(click.style(f"Error deleting orphaned file records: {str(e)}", fg="red")) + return + click.echo(click.style(f"Removed {len(orphaned_files)} orphaned file records.", fg="green")) + + +@click.option("-f", "--force", is_flag=True, help="Skip user confirmation and force the command to execute.") +@click.command("remove-orphaned-files-on-storage", help="Remove orphaned files on the storage.") +def remove_orphaned_files_on_storage(force: bool): + """ + Remove orphaned files on the storage. + """ + + # define tables and columns to process + files_tables = [ + {"table": "upload_files", "key_column": "key"}, + {"table": "tool_files", "key_column": "file_key"}, + ] + storage_paths = ["image_files", "tools", "upload_files"] + + # notify user and ask for confirmation + click.echo(click.style("This command will find and remove orphaned files on the storage,", fg="yellow")) + click.echo( + click.style("by comparing the files on the storage with the records in the following tables:", fg="yellow") + ) + for files_table in files_tables: + click.echo(click.style(f"- {files_table['table']}", fg="yellow")) + click.echo(click.style("The following paths on the storage will be scanned to find orphaned files:", fg="yellow")) + for storage_path in storage_paths: + click.echo(click.style(f"- {storage_path}", fg="yellow")) + click.echo("") + + click.echo(click.style("!!! USE WITH CAUTION !!!", fg="red")) + click.echo( + click.style( + "Currently, this command will work only for opendal based storage (STORAGE_TYPE=opendal).", fg="yellow" + ) + ) + click.echo( + click.style( + "Since not all patterns have been fully tested, please note that this command may delete unintended files.", + fg="yellow", + ) + ) + click.echo( + click.style("This cannot be undone. Please make sure to back up your storage before proceeding.", fg="yellow") + ) + click.echo( + click.style( + ( + "It is also recommended to run this during the maintenance window, " + "as this may cause high load on your instance." + ), + fg="yellow", + ) + ) + if not force: + click.confirm("Do you want to proceed?", abort=True) + + # start the cleanup process + click.echo(click.style("Starting orphaned files cleanup.", fg="white")) + + # fetch file id and keys from each table + all_files_in_tables = [] + try: + for files_table in files_tables: + click.echo(click.style(f"- Listing files from table {files_table['table']}", fg="white")) + query = f"SELECT {files_table['key_column']} FROM {files_table['table']}" + with db.engine.begin() as conn: + rs = conn.execute(db.text(query)) + for i in rs: + all_files_in_tables.append(str(i[0])) + click.echo(click.style(f"Found {len(all_files_in_tables)} files in tables.", fg="white")) + except Exception as e: + click.echo(click.style(f"Error fetching keys: {str(e)}", fg="red")) + + all_files_on_storage = [] + for storage_path in storage_paths: + try: + click.echo(click.style(f"- Scanning files on storage path {storage_path}", fg="white")) + files = storage.scan(path=storage_path, files=True, directories=False) + all_files_on_storage.extend(files) + except FileNotFoundError as e: + click.echo(click.style(f" -> Skipping path {storage_path} as it does not exist.", fg="yellow")) + continue + except Exception as e: + click.echo(click.style(f" -> Error scanning files on storage path {storage_path}: {str(e)}", fg="red")) + continue + click.echo(click.style(f"Found {len(all_files_on_storage)} files on storage.", fg="white")) + + # find orphaned files + orphaned_files = list(set(all_files_on_storage) - set(all_files_in_tables)) + if not orphaned_files: + click.echo(click.style("No orphaned files found. There is nothing to remove.", fg="green")) + return + click.echo(click.style(f"Found {len(orphaned_files)} orphaned files.", fg="white")) + for file in orphaned_files: + click.echo(click.style(f"- orphaned file: {file}", fg="black")) + if not force: + click.confirm(f"Do you want to proceed to remove all {len(orphaned_files)} orphaned files?", abort=True) + + # delete orphaned files + removed_files = 0 + error_files = 0 + for file in orphaned_files: + try: + storage.delete(file) + removed_files += 1 + click.echo(click.style(f"- Removing orphaned file: {file}", fg="white")) + except Exception as e: + error_files += 1 + click.echo(click.style(f"- Error deleting orphaned file {file}: {str(e)}", fg="red")) + continue + if error_files == 0: + click.echo(click.style(f"Removed {removed_files} orphaned files without errors.", fg="green")) + else: + click.echo(click.style(f"Removed {removed_files} orphaned files, with {error_files} errors.", fg="yellow")) diff --git a/api/configs/deploy/__init__.py b/api/configs/deploy/__init__.py index 950936d3c6..63f4dfba63 100644 --- a/api/configs/deploy/__init__.py +++ b/api/configs/deploy/__init__.py @@ -17,6 +17,12 @@ class DeploymentConfig(BaseSettings): default=False, ) + # Request logging configuration + ENABLE_REQUEST_LOGGING: bool = Field( + description="Enable request and response body logging", + default=False, + ) + EDITION: str = Field( description="Deployment edition of the application (e.g., 'SELF_HOSTED', 'CLOUD')", default="SELF_HOSTED", diff --git a/api/configs/feature/__init__.py b/api/configs/feature/__init__.py index f498dccbbc..a3da5c1b49 100644 --- a/api/configs/feature/__init__.py +++ b/api/configs/feature/__init__.py @@ -74,7 +74,7 @@ class CodeExecutionSandboxConfig(BaseSettings): CODE_EXECUTION_ENDPOINT: HttpUrl = Field( description="URL endpoint for the code execution service", - default="http://sandbox:8194", + default=HttpUrl("http://sandbox:8194"), ) CODE_EXECUTION_API_KEY: str = Field( @@ -145,7 +145,7 @@ class PluginConfig(BaseSettings): PLUGIN_DAEMON_URL: HttpUrl = Field( description="Plugin API URL", - default="http://localhost:5002", + default=HttpUrl("http://localhost:5002"), ) PLUGIN_DAEMON_KEY: str = Field( @@ -188,7 +188,7 @@ class MarketplaceConfig(BaseSettings): MARKETPLACE_API_URL: HttpUrl = Field( description="Marketplace API URL", - default="https://marketplace.dify.ai", + default=HttpUrl("https://marketplace.dify.ai"), ) @@ -398,6 +398,11 @@ class InnerAPIConfig(BaseSettings): default=False, ) + INNER_API_KEY: Optional[str] = Field( + description="API key for accessing the internal API", + default=None, + ) + class LoggingConfig(BaseSettings): """ diff --git a/api/configs/middleware/__init__.py b/api/configs/middleware/__init__.py index c2ad24094a..2dcf1710b0 100644 --- a/api/configs/middleware/__init__.py +++ b/api/configs/middleware/__init__.py @@ -1,8 +1,8 @@ import os from typing import Any, Literal, Optional -from urllib.parse import quote_plus +from urllib.parse import parse_qsl, quote_plus -from pydantic import Field, NonNegativeInt, PositiveFloat, PositiveInt, computed_field +from pydantic import Field, NonNegativeFloat, NonNegativeInt, PositiveFloat, PositiveInt, computed_field from pydantic_settings import BaseSettings from .cache.redis_config import RedisConfig @@ -39,6 +39,7 @@ from .vdb.tencent_vector_config import TencentVectorDBConfig from .vdb.tidb_on_qdrant_config import TidbOnQdrantConfig from .vdb.tidb_vector_config import TiDBVectorConfig from .vdb.upstash_config import UpstashConfig +from .vdb.vastbase_vector_config import VastbaseVectorConfig from .vdb.vikingdb_config import VikingDBConfig from .vdb.weaviate_config import WeaviateConfig @@ -172,17 +173,31 @@ class DatabaseConfig(BaseSettings): RETRIEVAL_SERVICE_EXECUTORS: NonNegativeInt = Field( description="Number of processes for the retrieval service, default to CPU cores.", - default=os.cpu_count(), + default=os.cpu_count() or 1, ) - @computed_field + @computed_field # type: ignore[misc] + @property def SQLALCHEMY_ENGINE_OPTIONS(self) -> dict[str, Any]: + # Parse DB_EXTRAS for 'options' + db_extras_dict = dict(parse_qsl(self.DB_EXTRAS)) + options = db_extras_dict.get("options", "") + # Always include timezone + timezone_opt = "-c timezone=UTC" + if options: + # Merge user options and timezone + merged_options = f"{options} {timezone_opt}" + else: + merged_options = timezone_opt + + connect_args = {"options": merged_options} + return { "pool_size": self.SQLALCHEMY_POOL_SIZE, "max_overflow": self.SQLALCHEMY_MAX_OVERFLOW, "pool_recycle": self.SQLALCHEMY_POOL_RECYCLE, "pool_pre_ping": self.SQLALCHEMY_POOL_PRE_PING, - "connect_args": {"options": "-c timezone=UTC"}, + "connect_args": connect_args, } @@ -241,6 +256,25 @@ class InternalTestConfig(BaseSettings): ) +class DatasetQueueMonitorConfig(BaseSettings): + """ + Configuration settings for Dataset Queue Monitor + """ + + QUEUE_MONITOR_THRESHOLD: Optional[NonNegativeInt] = Field( + description="Threshold for dataset queue monitor", + default=200, + ) + QUEUE_MONITOR_ALERT_EMAILS: Optional[str] = Field( + description="Emails for dataset queue monitor alert, separated by commas", + default=None, + ) + QUEUE_MONITOR_INTERVAL: Optional[NonNegativeFloat] = Field( + description="Interval for dataset queue monitor in minutes", + default=30, + ) + + class MiddlewareConfig( # place the configs in alphabet order CeleryConfig, @@ -270,6 +304,7 @@ class MiddlewareConfig( OpenSearchConfig, OracleConfig, PGVectorConfig, + VastbaseVectorConfig, PGVectoRSConfig, QdrantConfig, RelytConfig, @@ -287,5 +322,6 @@ class MiddlewareConfig( BaiduVectorDBConfig, OpenGaussConfig, TableStoreConfig, + DatasetQueueMonitorConfig, ): pass diff --git a/api/configs/middleware/cache/redis_config.py b/api/configs/middleware/cache/redis_config.py index 2e98c31ec3..916f52e165 100644 --- a/api/configs/middleware/cache/redis_config.py +++ b/api/configs/middleware/cache/redis_config.py @@ -83,3 +83,13 @@ class RedisConfig(BaseSettings): description="Password for Redis Clusters authentication (if required)", default=None, ) + + REDIS_SERIALIZATION_PROTOCOL: int = Field( + description="Redis serialization protocol (RESP) version", + default=3, + ) + + REDIS_ENABLE_CLIENT_SIDE_CACHE: bool = Field( + description="Enable client side cache in redis", + default=False, + ) diff --git a/api/configs/middleware/storage/amazon_s3_storage_config.py b/api/configs/middleware/storage/amazon_s3_storage_config.py index f2d94b12ff..e14c210718 100644 --- a/api/configs/middleware/storage/amazon_s3_storage_config.py +++ b/api/configs/middleware/storage/amazon_s3_storage_config.py @@ -1,4 +1,4 @@ -from typing import Optional +from typing import Literal, Optional from pydantic import Field from pydantic_settings import BaseSettings @@ -34,7 +34,7 @@ class S3StorageConfig(BaseSettings): default=None, ) - S3_ADDRESS_STYLE: str = Field( + S3_ADDRESS_STYLE: Literal["auto", "virtual", "path"] = Field( description="S3 addressing style: 'auto', 'path', or 'virtual'", default="auto", ) diff --git a/api/configs/middleware/vdb/lindorm_config.py b/api/configs/middleware/vdb/lindorm_config.py index 95e1d1cfca..e80e3f4a35 100644 --- a/api/configs/middleware/vdb/lindorm_config.py +++ b/api/configs/middleware/vdb/lindorm_config.py @@ -32,3 +32,4 @@ class LindormConfig(BaseSettings): description="Using UGC index will store the same type of Index in a single index but can retrieve separately.", default=False, ) + LINDORM_QUERY_TIMEOUT: Optional[float] = Field(description="The lindorm search request timeout (s)", default=2.0) diff --git a/api/configs/middleware/vdb/opensearch_config.py b/api/configs/middleware/vdb/opensearch_config.py index 81dde4c04d..9fd9b60194 100644 --- a/api/configs/middleware/vdb/opensearch_config.py +++ b/api/configs/middleware/vdb/opensearch_config.py @@ -1,4 +1,5 @@ -from typing import Optional +import enum +from typing import Literal, Optional from pydantic import Field, PositiveInt from pydantic_settings import BaseSettings @@ -9,6 +10,14 @@ class OpenSearchConfig(BaseSettings): Configuration settings for OpenSearch """ + class AuthMethod(enum.StrEnum): + """ + Authentication method for OpenSearch + """ + + BASIC = "basic" + AWS_MANAGED_IAM = "aws_managed_iam" + OPENSEARCH_HOST: Optional[str] = Field( description="Hostname or IP address of the OpenSearch server (e.g., 'localhost' or 'opensearch.example.com')", default=None, @@ -19,6 +28,21 @@ class OpenSearchConfig(BaseSettings): default=9200, ) + OPENSEARCH_SECURE: bool = Field( + description="Whether to use SSL/TLS encrypted connection for OpenSearch (True for HTTPS, False for HTTP)", + default=False, + ) + + OPENSEARCH_VERIFY_CERTS: bool = Field( + description="Whether to verify SSL certificates for HTTPS connections (recommended to set True in production)", + default=True, + ) + + OPENSEARCH_AUTH_METHOD: AuthMethod = Field( + description="Authentication method for OpenSearch connection (default is 'basic')", + default=AuthMethod.BASIC, + ) + OPENSEARCH_USER: Optional[str] = Field( description="Username for authenticating with OpenSearch", default=None, @@ -29,7 +53,11 @@ class OpenSearchConfig(BaseSettings): default=None, ) - OPENSEARCH_SECURE: bool = Field( - description="Whether to use SSL/TLS encrypted connection for OpenSearch (True for HTTPS, False for HTTP)", - default=False, + OPENSEARCH_AWS_REGION: Optional[str] = Field( + description="AWS region for OpenSearch (e.g. 'us-west-2')", + default=None, + ) + + OPENSEARCH_AWS_SERVICE: Optional[Literal["es", "aoss"]] = Field( + description="AWS service for OpenSearch (e.g. 'aoss' for OpenSearch Serverless)", default=None ) diff --git a/api/configs/middleware/vdb/qdrant_config.py b/api/configs/middleware/vdb/qdrant_config.py index b70f624652..0a753eddec 100644 --- a/api/configs/middleware/vdb/qdrant_config.py +++ b/api/configs/middleware/vdb/qdrant_config.py @@ -33,3 +33,8 @@ class QdrantConfig(BaseSettings): description="Port number for gRPC connection to Qdrant server (default is 6334)", default=6334, ) + + QDRANT_REPLICATION_FACTOR: PositiveInt = Field( + description="Replication factor for Qdrant collections (default is 1)", + default=1, + ) diff --git a/api/configs/middleware/vdb/vastbase_vector_config.py b/api/configs/middleware/vdb/vastbase_vector_config.py new file mode 100644 index 0000000000..816d6df90a --- /dev/null +++ b/api/configs/middleware/vdb/vastbase_vector_config.py @@ -0,0 +1,45 @@ +from typing import Optional + +from pydantic import Field, PositiveInt +from pydantic_settings import BaseSettings + + +class VastbaseVectorConfig(BaseSettings): + """ + Configuration settings for Vector (Vastbase with vector extension) + """ + + VASTBASE_HOST: Optional[str] = Field( + description="Hostname or IP address of the Vastbase server with Vector extension (e.g., 'localhost')", + default=None, + ) + + VASTBASE_PORT: PositiveInt = Field( + description="Port number on which the Vastbase server is listening (default is 5432)", + default=5432, + ) + + VASTBASE_USER: Optional[str] = Field( + description="Username for authenticating with the Vastbase database", + default=None, + ) + + VASTBASE_PASSWORD: Optional[str] = Field( + description="Password for authenticating with the Vastbase database", + default=None, + ) + + VASTBASE_DATABASE: Optional[str] = Field( + description="Name of the Vastbase database to connect to", + default=None, + ) + + VASTBASE_MIN_CONNECTION: PositiveInt = Field( + description="Min connection of the Vastbase database", + default=1, + ) + + VASTBASE_MAX_CONNECTION: PositiveInt = Field( + description="Max connection of the Vastbase database", + default=5, + ) diff --git a/api/configs/observability/otel/otel_config.py b/api/configs/observability/otel/otel_config.py index 568a800d10..1b88ddcfe6 100644 --- a/api/configs/observability/otel/otel_config.py +++ b/api/configs/observability/otel/otel_config.py @@ -27,6 +27,11 @@ class OTelConfig(BaseSettings): default="otlp", ) + OTEL_EXPORTER_OTLP_PROTOCOL: str = Field( + description="OTLP exporter protocol ('grpc' or 'http')", + default="http", + ) + OTEL_SAMPLING_RATE: float = Field(default=0.1, description="Sampling rate for traces (0.0 to 1.0)") OTEL_BATCH_EXPORT_SCHEDULE_DELAY: int = Field( diff --git a/api/configs/packaging/__init__.py b/api/configs/packaging/__init__.py index a33c7727dc..0107df22c5 100644 --- a/api/configs/packaging/__init__.py +++ b/api/configs/packaging/__init__.py @@ -9,7 +9,7 @@ class PackagingInfo(BaseSettings): CURRENT_VERSION: str = Field( description="Dify version", - default="1.3.0", + default="1.4.3", ) COMMIT_SHA: str = Field( diff --git a/api/configs/remote_settings_sources/nacos/http_request.py b/api/configs/remote_settings_sources/nacos/http_request.py index 2785bd955b..9b3359c6ad 100644 --- a/api/configs/remote_settings_sources/nacos/http_request.py +++ b/api/configs/remote_settings_sources/nacos/http_request.py @@ -60,8 +60,7 @@ class NacosHttpClient: sign_str = tenant + "+" if group: sign_str = sign_str + group + "+" - if sign_str: - sign_str += ts + sign_str += ts # Directly concatenate ts without conditional checks, because the nacos auth header forced it. return sign_str def get_access_token(self, force_refresh=False): diff --git a/api/constants/__init__.py b/api/constants/__init__.py index 9162357466..a84de0a451 100644 --- a/api/constants/__init__.py +++ b/api/constants/__init__.py @@ -16,11 +16,25 @@ AUDIO_EXTENSIONS.extend([ext.upper() for ext in AUDIO_EXTENSIONS]) if dify_config.ETL_TYPE == "Unstructured": - DOCUMENT_EXTENSIONS = ["txt", "markdown", "md", "mdx", "pdf", "html", "htm", "xlsx", "xls"] + DOCUMENT_EXTENSIONS = ["txt", "markdown", "md", "mdx", "pdf", "html", "htm", "xlsx", "xls", "vtt", "properties"] DOCUMENT_EXTENSIONS.extend(("doc", "docx", "csv", "eml", "msg", "pptx", "xml", "epub")) if dify_config.UNSTRUCTURED_API_URL: DOCUMENT_EXTENSIONS.append("ppt") DOCUMENT_EXTENSIONS.extend([ext.upper() for ext in DOCUMENT_EXTENSIONS]) else: - DOCUMENT_EXTENSIONS = ["txt", "markdown", "md", "mdx", "pdf", "html", "htm", "xlsx", "xls", "docx", "csv"] + DOCUMENT_EXTENSIONS = [ + "txt", + "markdown", + "md", + "mdx", + "pdf", + "html", + "htm", + "xlsx", + "xls", + "docx", + "csv", + "vtt", + "properties", + ] DOCUMENT_EXTENSIONS.extend([ext.upper() for ext in DOCUMENT_EXTENSIONS]) diff --git a/api/constants/mimetypes.py b/api/constants/mimetypes.py new file mode 100644 index 0000000000..38988cdd24 --- /dev/null +++ b/api/constants/mimetypes.py @@ -0,0 +1,7 @@ +# The two constants below should keep in sync. +# Default content type for files which have no explicit content type. + +DEFAULT_MIME_TYPE = "application/octet-stream" +# Default file extension for files which have no explicit content type, should +# correspond to the `DEFAULT_MIME_TYPE` above. +DEFAULT_EXTENSION = ".bin" diff --git a/api/contexts/__init__.py b/api/contexts/__init__.py index 127b8fe76d..ae41a2c03a 100644 --- a/api/contexts/__init__.py +++ b/api/contexts/__init__.py @@ -11,10 +11,6 @@ if TYPE_CHECKING: from core.workflow.entities.variable_pool import VariablePool -tenant_id: ContextVar[str] = ContextVar("tenant_id") - -workflow_variable_pool: ContextVar["VariablePool"] = ContextVar("workflow_variable_pool") - """ To avoid race-conditions caused by gunicorn thread recycling, using RecyclableContextVar to replace with """ diff --git a/api/controllers/common/fields.py b/api/controllers/common/fields.py index b1ebc444a5..3466eea1f6 100644 --- a/api/controllers/common/fields.py +++ b/api/controllers/common/fields.py @@ -1,4 +1,6 @@ -from flask_restful import fields # type: ignore +from flask_restful import fields + +from libs.helper import AppIconUrlField parameters__system_parameters = { "image_file_size_limit": fields.Integer, @@ -22,3 +24,20 @@ parameters_fields = { "file_upload": fields.Raw, "system_parameters": fields.Nested(parameters__system_parameters), } + +site_fields = { + "title": fields.String, + "chat_color_theme": fields.String, + "chat_color_theme_inverted": fields.Boolean, + "icon_type": fields.String, + "icon": fields.String, + "icon_background": fields.String, + "icon_url": AppIconUrlField, + "description": fields.String, + "copyright": fields.String, + "privacy_policy": fields.String, + "custom_disclaimer": fields.String, + "default_language": fields.String, + "show_workflow_steps": fields.Boolean, + "use_icon_as_answer_icon": fields.Boolean, +} diff --git a/api/controllers/console/admin.py b/api/controllers/console/admin.py index 6e3273f5d4..8cb7ad9f5b 100644 --- a/api/controllers/console/admin.py +++ b/api/controllers/console/admin.py @@ -1,7 +1,7 @@ from functools import wraps from flask import request -from flask_restful import Resource, reqparse # type: ignore +from flask_restful import Resource, reqparse from sqlalchemy import select from sqlalchemy.orm import Session from werkzeug.exceptions import NotFound, Unauthorized diff --git a/api/controllers/console/apikey.py b/api/controllers/console/apikey.py index eb42507c63..47c93a15c6 100644 --- a/api/controllers/console/apikey.py +++ b/api/controllers/console/apikey.py @@ -1,7 +1,7 @@ from typing import Any -import flask_restful # type: ignore -from flask_login import current_user # type: ignore +import flask_restful +from flask_login import current_user from flask_restful import Resource, fields, marshal_with from sqlalchemy import select from sqlalchemy.orm import Session diff --git a/api/controllers/console/app/advanced_prompt_template.py b/api/controllers/console/app/advanced_prompt_template.py index 8d0c5b84af..c228743fa5 100644 --- a/api/controllers/console/app/advanced_prompt_template.py +++ b/api/controllers/console/app/advanced_prompt_template.py @@ -1,4 +1,4 @@ -from flask_restful import Resource, reqparse # type: ignore +from flask_restful import Resource, reqparse from controllers.console import api from controllers.console.wraps import account_initialization_required, setup_required diff --git a/api/controllers/console/app/agent.py b/api/controllers/console/app/agent.py index 920cae0d85..d433415894 100644 --- a/api/controllers/console/app/agent.py +++ b/api/controllers/console/app/agent.py @@ -1,4 +1,4 @@ -from flask_restful import Resource, reqparse # type: ignore +from flask_restful import Resource, reqparse from controllers.console import api from controllers.console.app.wraps import get_app_model diff --git a/api/controllers/console/app/annotation.py b/api/controllers/console/app/annotation.py index fcd8ed1882..14fd4679a1 100644 --- a/api/controllers/console/app/annotation.py +++ b/api/controllers/console/app/annotation.py @@ -1,6 +1,6 @@ from flask import request -from flask_login import current_user # type: ignore -from flask_restful import Resource, marshal, marshal_with, reqparse # type: ignore +from flask_login import current_user +from flask_restful import Resource, marshal, marshal_with, reqparse from werkzeug.exceptions import Forbidden from controllers.console import api @@ -186,7 +186,7 @@ class AnnotationUpdateDeleteApi(Resource): app_id = str(app_id) annotation_id = str(annotation_id) AppAnnotationService.delete_app_annotation(app_id, annotation_id) - return {"result": "success"}, 200 + return {"result": "success"}, 204 class AnnotationBatchImportApi(Resource): @@ -208,7 +208,7 @@ class AnnotationBatchImportApi(Resource): if len(request.files) > 1: raise TooManyFilesError() # check file type - if not file.filename.endswith(".csv"): + if not file.filename or not file.filename.endswith(".csv"): raise ValueError("Invalid file type. Only CSV files are allowed") return AppAnnotationService.batch_import_app_annotations(app_id, file) diff --git a/api/controllers/console/app/app.py b/api/controllers/console/app/app.py index 3e908b76a7..860166a61a 100644 --- a/api/controllers/console/app/app.py +++ b/api/controllers/console/app/app.py @@ -1,8 +1,8 @@ import uuid from typing import cast -from flask_login import current_user # type: ignore -from flask_restful import Resource, inputs, marshal, marshal_with, reqparse # type: ignore +from flask_login import current_user +from flask_restful import Resource, inputs, marshal, marshal_with, reqparse from sqlalchemy import select from sqlalchemy.orm import Session from werkzeug.exceptions import BadRequest, Forbidden, abort @@ -17,15 +17,13 @@ from controllers.console.wraps import ( ) from core.ops.ops_trace_manager import OpsTraceManager from extensions.ext_database import db -from fields.app_fields import ( - app_detail_fields, - app_detail_fields_with_site, - app_pagination_fields, -) +from fields.app_fields import app_detail_fields, app_detail_fields_with_site, app_pagination_fields from libs.login import login_required from models import Account, App from services.app_dsl_service import AppDslService, ImportMode from services.app_service import AppService +from services.enterprise.enterprise_service import EnterpriseService +from services.feature_service import FeatureService ALLOW_CREATE_APP_MODES = ["chat", "agent-chat", "advanced-chat", "workflow", "completion"] @@ -75,7 +73,17 @@ class AppListApi(Resource): if not app_pagination: return {"data": [], "total": 0, "page": 1, "limit": 20, "has_more": False} - return marshal(app_pagination, app_pagination_fields) + if FeatureService.get_system_features().webapp_auth.enabled: + app_ids = [str(app.id) for app in app_pagination.items] + res = EnterpriseService.WebAppAuth.batch_get_app_access_mode_by_id(app_ids=app_ids) + if len(res) != len(app_ids): + raise BadRequest("Invalid app id in webapp auth") + + for app in app_pagination.items: + if str(app.id) in res: + app.access_mode = res[str(app.id)].access_mode + + return marshal(app_pagination, app_pagination_fields), 200 @setup_required @login_required @@ -119,6 +127,10 @@ class AppApi(Resource): app_model = app_service.get_app(app_model) + if FeatureService.get_system_features().webapp_auth.enabled: + app_setting = EnterpriseService.WebAppAuth.get_app_access_mode_by_id(app_id=str(app_model.id)) + app_model.access_mode = app_setting.access_mode + return app_model @setup_required diff --git a/api/controllers/console/app/app_import.py b/api/controllers/console/app/app_import.py index a159d4c5c4..5dc6515ce0 100644 --- a/api/controllers/console/app/app_import.py +++ b/api/controllers/console/app/app_import.py @@ -1,7 +1,7 @@ from typing import cast -from flask_login import current_user # type: ignore -from flask_restful import Resource, marshal_with, reqparse # type: ignore +from flask_login import current_user +from flask_restful import Resource, marshal_with, reqparse from sqlalchemy.orm import Session from werkzeug.exceptions import Forbidden diff --git a/api/controllers/console/app/audio.py b/api/controllers/console/app/audio.py index 7519ae96c0..5f2def8d8e 100644 --- a/api/controllers/console/app/audio.py +++ b/api/controllers/console/app/audio.py @@ -1,7 +1,7 @@ import logging from flask import request -from flask_restful import Resource, reqparse # type: ignore +from flask_restful import Resource, reqparse from werkzeug.exceptions import InternalServerError import services diff --git a/api/controllers/console/app/completion.py b/api/controllers/console/app/completion.py index c9820f70f7..732f5b799a 100644 --- a/api/controllers/console/app/completion.py +++ b/api/controllers/console/app/completion.py @@ -1,7 +1,7 @@ import logging -import flask_login # type: ignore -from flask_restful import Resource, reqparse # type: ignore +import flask_login +from flask_restful import Resource, reqparse from werkzeug.exceptions import InternalServerError, NotFound import services diff --git a/api/controllers/console/app/conversation.py b/api/controllers/console/app/conversation.py index 8827f129d9..70d6216497 100644 --- a/api/controllers/console/app/conversation.py +++ b/api/controllers/console/app/conversation.py @@ -1,9 +1,9 @@ from datetime import UTC, datetime import pytz # pip install pytz -from flask_login import current_user # type: ignore -from flask_restful import Resource, marshal_with, reqparse # type: ignore -from flask_restful.inputs import int_range # type: ignore +from flask_login import current_user +from flask_restful import Resource, marshal_with, reqparse +from flask_restful.inputs import int_range from sqlalchemy import func, or_ from sqlalchemy.orm import joinedload from werkzeug.exceptions import Forbidden, NotFound diff --git a/api/controllers/console/app/conversation_variables.py b/api/controllers/console/app/conversation_variables.py index c0a20b7160..d49f433ba1 100644 --- a/api/controllers/console/app/conversation_variables.py +++ b/api/controllers/console/app/conversation_variables.py @@ -1,4 +1,4 @@ -from flask_restful import Resource, marshal_with, reqparse # type: ignore +from flask_restful import Resource, marshal_with, reqparse from sqlalchemy import select from sqlalchemy.orm import Session diff --git a/api/controllers/console/app/generator.py b/api/controllers/console/app/generator.py index 4046417076..790369c052 100644 --- a/api/controllers/console/app/generator.py +++ b/api/controllers/console/app/generator.py @@ -1,7 +1,7 @@ import os -from flask_login import current_user # type: ignore -from flask_restful import Resource, reqparse # type: ignore +from flask_login import current_user +from flask_restful import Resource, reqparse from controllers.console import api from controllers.console.app.error import ( diff --git a/api/controllers/console/app/message.py b/api/controllers/console/app/message.py index b5828b6b4b..b7a4c31a15 100644 --- a/api/controllers/console/app/message.py +++ b/api/controllers/console/app/message.py @@ -1,8 +1,8 @@ import logging -from flask_login import current_user # type: ignore -from flask_restful import Resource, fields, marshal_with, reqparse # type: ignore -from flask_restful.inputs import int_range # type: ignore +from flask_login import current_user +from flask_restful import Resource, fields, marshal_with, reqparse +from flask_restful.inputs import int_range from werkzeug.exceptions import Forbidden, InternalServerError, NotFound from controllers.console import api diff --git a/api/controllers/console/app/model_config.py b/api/controllers/console/app/model_config.py index 8ecc8a9db5..f30e3e893c 100644 --- a/api/controllers/console/app/model_config.py +++ b/api/controllers/console/app/model_config.py @@ -2,8 +2,8 @@ import json from typing import cast from flask import request -from flask_login import current_user # type: ignore -from flask_restful import Resource # type: ignore +from flask_login import current_user +from flask_restful import Resource from controllers.console import api from controllers.console.app.wraps import get_app_model diff --git a/api/controllers/console/app/ops_trace.py b/api/controllers/console/app/ops_trace.py index dd25af8ebf..978c02412c 100644 --- a/api/controllers/console/app/ops_trace.py +++ b/api/controllers/console/app/ops_trace.py @@ -1,4 +1,4 @@ -from flask_restful import Resource, reqparse # type: ignore +from flask_restful import Resource, reqparse from werkzeug.exceptions import BadRequest from controllers.console import api @@ -84,7 +84,7 @@ class TraceAppConfigApi(Resource): result = OpsService.delete_tracing_app_config(app_id=app_id, tracing_provider=args["tracing_provider"]) if not result: raise TracingConfigNotExist() - return {"result": "success"} + return {"result": "success"}, 204 except Exception as e: raise BadRequest(str(e)) diff --git a/api/controllers/console/app/site.py b/api/controllers/console/app/site.py index f15f9d4dae..3c3a359eeb 100644 --- a/api/controllers/console/app/site.py +++ b/api/controllers/console/app/site.py @@ -1,7 +1,7 @@ from datetime import UTC, datetime -from flask_login import current_user # type: ignore -from flask_restful import Resource, marshal_with, reqparse # type: ignore +from flask_login import current_user +from flask_restful import Resource, marshal_with, reqparse from werkzeug.exceptions import Forbidden, NotFound from constants.languages import supported_language diff --git a/api/controllers/console/app/statistic.py b/api/controllers/console/app/statistic.py index a37d26b989..86aed77412 100644 --- a/api/controllers/console/app/statistic.py +++ b/api/controllers/console/app/statistic.py @@ -3,8 +3,8 @@ from decimal import Decimal import pytz from flask import jsonify -from flask_login import current_user # type: ignore -from flask_restful import Resource, reqparse # type: ignore +from flask_login import current_user +from flask_restful import Resource, reqparse from controllers.console import api from controllers.console.app.wraps import get_app_model diff --git a/api/controllers/console/app/workflow.py b/api/controllers/console/app/workflow.py index 2e077d2095..cbbdd324ba 100644 --- a/api/controllers/console/app/workflow.py +++ b/api/controllers/console/app/workflow.py @@ -3,7 +3,7 @@ import logging from typing import cast from flask import abort, request -from flask_restful import Resource, inputs, marshal_with, reqparse # type: ignore +from flask_restful import Resource, inputs, marshal_with, reqparse from sqlalchemy.orm import Session from werkzeug.exceptions import Forbidden, InternalServerError, NotFound @@ -81,8 +81,7 @@ class DraftWorkflowApi(Resource): parser.add_argument("graph", type=dict, required=True, nullable=False, location="json") parser.add_argument("features", type=dict, required=True, nullable=False, location="json") parser.add_argument("hash", type=str, required=False, location="json") - # TODO: set this to required=True after frontend is updated - parser.add_argument("environment_variables", type=list, required=False, location="json") + parser.add_argument("environment_variables", type=list, required=True, location="json") parser.add_argument("conversation_variables", type=list, required=False, location="json") args = parser.parse_args() elif "text/plain" in content_type: diff --git a/api/controllers/console/app/workflow_app_log.py b/api/controllers/console/app/workflow_app_log.py index d863747995..b9579e2120 100644 --- a/api/controllers/console/app/workflow_app_log.py +++ b/api/controllers/console/app/workflow_app_log.py @@ -1,17 +1,17 @@ from dateutil.parser import isoparse -from flask_restful import Resource, marshal_with, reqparse # type: ignore -from flask_restful.inputs import int_range # type: ignore +from flask_restful import Resource, marshal_with, reqparse +from flask_restful.inputs import int_range from sqlalchemy.orm import Session from controllers.console import api from controllers.console.app.wraps import get_app_model from controllers.console.wraps import account_initialization_required, setup_required +from core.workflow.entities.workflow_execution import WorkflowExecutionStatus from extensions.ext_database import db from fields.workflow_app_log_fields import workflow_app_log_pagination_fields from libs.login import login_required from models import App from models.model import AppMode -from models.workflow import WorkflowRunStatus from services.workflow_app_service import WorkflowAppService @@ -38,7 +38,7 @@ class WorkflowAppLogApi(Resource): parser.add_argument("limit", type=int_range(1, 100), default=20, location="args") args = parser.parse_args() - args.status = WorkflowRunStatus(args.status) if args.status else None + args.status = WorkflowExecutionStatus(args.status) if args.status else None if args.created_at__before: args.created_at__before = isoparse(args.created_at__before) diff --git a/api/controllers/console/app/workflow_run.py b/api/controllers/console/app/workflow_run.py index 25a99c1e15..9099700213 100644 --- a/api/controllers/console/app/workflow_run.py +++ b/api/controllers/console/app/workflow_run.py @@ -1,5 +1,8 @@ -from flask_restful import Resource, marshal_with, reqparse # type: ignore -from flask_restful.inputs import int_range # type: ignore +from typing import cast + +from flask_login import current_user +from flask_restful import Resource, marshal_with, reqparse +from flask_restful.inputs import int_range from controllers.console import api from controllers.console.app.wraps import get_app_model @@ -12,8 +15,7 @@ from fields.workflow_run_fields import ( ) from libs.helper import uuid_value from libs.login import login_required -from models import App -from models.model import AppMode +from models import Account, App, AppMode, EndUser from services.workflow_run_service import WorkflowRunService @@ -90,7 +92,12 @@ class WorkflowRunNodeExecutionListApi(Resource): run_id = str(run_id) workflow_run_service = WorkflowRunService() - node_executions = workflow_run_service.get_workflow_run_node_executions(app_model=app_model, run_id=run_id) + user = cast("Account | EndUser", current_user) + node_executions = workflow_run_service.get_workflow_run_node_executions( + app_model=app_model, + run_id=run_id, + user=user, + ) return {"data": node_executions} diff --git a/api/controllers/console/app/workflow_statistic.py b/api/controllers/console/app/workflow_statistic.py index 097bf7d188..6c7c73707b 100644 --- a/api/controllers/console/app/workflow_statistic.py +++ b/api/controllers/console/app/workflow_statistic.py @@ -3,8 +3,8 @@ from decimal import Decimal import pytz from flask import jsonify -from flask_login import current_user # type: ignore -from flask_restful import Resource, reqparse # type: ignore +from flask_login import current_user +from flask_restful import Resource, reqparse from controllers.console import api from controllers.console.app.wraps import get_app_model diff --git a/api/controllers/console/auth/activate.py b/api/controllers/console/auth/activate.py index c56f551d49..1795563ff7 100644 --- a/api/controllers/console/auth/activate.py +++ b/api/controllers/console/auth/activate.py @@ -1,7 +1,7 @@ import datetime from flask import request -from flask_restful import Resource, reqparse # type: ignore +from flask_restful import Resource, reqparse from constants.languages import supported_language from controllers.console import api diff --git a/api/controllers/console/auth/data_source_bearer_auth.py b/api/controllers/console/auth/data_source_bearer_auth.py index ea00c2b8c2..b8c3c8f012 100644 --- a/api/controllers/console/auth/data_source_bearer_auth.py +++ b/api/controllers/console/auth/data_source_bearer_auth.py @@ -1,5 +1,5 @@ -from flask_login import current_user # type: ignore -from flask_restful import Resource, reqparse # type: ignore +from flask_login import current_user +from flask_restful import Resource, reqparse from werkzeug.exceptions import Forbidden from controllers.console import api @@ -65,7 +65,7 @@ class ApiKeyAuthDataSourceBindingDelete(Resource): ApiKeyAuthService.delete_provider_auth(current_user.current_tenant_id, binding_id) - return {"result": "success"}, 200 + return {"result": "success"}, 204 api.add_resource(ApiKeyAuthDataSource, "/api-key-auth/data-source") diff --git a/api/controllers/console/auth/data_source_oauth.py b/api/controllers/console/auth/data_source_oauth.py index b4bd80fe2f..1049f864c3 100644 --- a/api/controllers/console/auth/data_source_oauth.py +++ b/api/controllers/console/auth/data_source_oauth.py @@ -2,8 +2,8 @@ import logging import requests from flask import current_app, redirect, request -from flask_login import current_user # type: ignore -from flask_restful import Resource # type: ignore +from flask_login import current_user +from flask_restful import Resource from werkzeug.exceptions import Forbidden from configs import dify_config diff --git a/api/controllers/console/auth/forgot_password.py b/api/controllers/console/auth/forgot_password.py index d4a33645ab..3bbe3177fc 100644 --- a/api/controllers/console/auth/forgot_password.py +++ b/api/controllers/console/auth/forgot_password.py @@ -2,7 +2,7 @@ import base64 import secrets from flask import request -from flask_restful import Resource, reqparse # type: ignore +from flask_restful import Resource, reqparse from sqlalchemy import select from sqlalchemy.orm import Session @@ -24,7 +24,7 @@ from libs.password import hash_password, valid_password from models.account import Account from services.account_service import AccountService, TenantService from services.errors.account import AccountRegisterError -from services.errors.workspace import WorkSpaceNotAllowedCreateError +from services.errors.workspace import WorkSpaceNotAllowedCreateError, WorkspacesLimitExceededError from services.feature_service import FeatureService @@ -168,6 +168,8 @@ class ForgotPasswordResetApi(Resource): ) except WorkSpaceNotAllowedCreateError: pass + except WorkspacesLimitExceededError: + pass except AccountRegisterError: raise AccountInFreezeError() diff --git a/api/controllers/console/auth/login.py b/api/controllers/console/auth/login.py index 16c1dcc441..5f2a24322d 100644 --- a/api/controllers/console/auth/login.py +++ b/api/controllers/console/auth/login.py @@ -1,8 +1,8 @@ from typing import cast -import flask_login # type: ignore +import flask_login from flask import request -from flask_restful import Resource, reqparse # type: ignore +from flask_restful import Resource, reqparse import services from configs import dify_config @@ -21,6 +21,7 @@ from controllers.console.error import ( AccountNotFound, EmailSendIpLimitError, NotAllowedCreateWorkspace, + WorkspacesLimitExceeded, ) from controllers.console.wraps import email_password_login_enabled, setup_required from events.tenant_event import tenant_was_created @@ -30,7 +31,7 @@ from models.account import Account from services.account_service import AccountService, RegisterService, TenantService from services.billing_service import BillingService from services.errors.account import AccountRegisterError -from services.errors.workspace import WorkSpaceNotAllowedCreateError +from services.errors.workspace import WorkSpaceNotAllowedCreateError, WorkspacesLimitExceededError from services.feature_service import FeatureService @@ -88,10 +89,15 @@ class LoginApi(Resource): # SELF_HOSTED only have one workspace tenants = TenantService.get_join_tenants(account) if len(tenants) == 0: - return { - "result": "fail", - "data": "workspace not found, please contact system admin to invite you to join in a workspace", - } + system_features = FeatureService.get_system_features() + + if system_features.is_allow_create_workspace and not system_features.license.workspaces.is_available(): + raise WorkspacesLimitExceeded() + else: + return { + "result": "fail", + "data": "workspace not found, please contact system admin to invite you to join in a workspace", + } token_pair = AccountService.login(account=account, ip_address=extract_remote_ip(request)) AccountService.reset_login_error_rate_limit(args["email"]) @@ -196,15 +202,18 @@ class EmailCodeLoginApi(Resource): except AccountRegisterError as are: raise AccountInFreezeError() if account: - tenant = TenantService.get_join_tenants(account) - if not tenant: + tenants = TenantService.get_join_tenants(account) + if not tenants: + workspaces = FeatureService.get_system_features().license.workspaces + if not workspaces.is_available(): + raise WorkspacesLimitExceeded() if not FeatureService.get_system_features().is_allow_create_workspace: raise NotAllowedCreateWorkspace() else: - tenant = TenantService.create_tenant(f"{account.name}'s Workspace") - TenantService.create_tenant_member(tenant, account, role="owner") - account.current_tenant = tenant - tenant_was_created.send(tenant) + new_tenant = TenantService.create_tenant(f"{account.name}'s Workspace") + TenantService.create_tenant_member(new_tenant, account, role="owner") + account.current_tenant = new_tenant + tenant_was_created.send(new_tenant) if account is None: try: @@ -215,6 +224,8 @@ class EmailCodeLoginApi(Resource): return NotAllowedCreateWorkspace() except AccountRegisterError as are: raise AccountInFreezeError() + except WorkspacesLimitExceededError: + raise WorkspacesLimitExceeded() token_pair = AccountService.login(account, ip_address=extract_remote_ip(request)) AccountService.reset_login_error_rate_limit(args["email"]) return {"result": "success", "data": token_pair.model_dump()} diff --git a/api/controllers/console/auth/oauth.py b/api/controllers/console/auth/oauth.py index 33bafbf463..395367c9e2 100644 --- a/api/controllers/console/auth/oauth.py +++ b/api/controllers/console/auth/oauth.py @@ -4,7 +4,7 @@ from typing import Optional import requests from flask import current_app, redirect, request -from flask_restful import Resource # type: ignore +from flask_restful import Resource from sqlalchemy import select from sqlalchemy.orm import Session from werkzeug.exceptions import Unauthorized @@ -148,15 +148,15 @@ def _generate_account(provider: str, user_info: OAuthUserInfo): account = _get_account_by_openid_or_email(provider, user_info) if account: - tenant = TenantService.get_join_tenants(account) - if not tenant: + tenants = TenantService.get_join_tenants(account) + if not tenants: if not FeatureService.get_system_features().is_allow_create_workspace: raise WorkSpaceNotAllowedCreateError() else: - tenant = TenantService.create_tenant(f"{account.name}'s Workspace") - TenantService.create_tenant_member(tenant, account, role="owner") - account.current_tenant = tenant - tenant_was_created.send(tenant) + new_tenant = TenantService.create_tenant(f"{account.name}'s Workspace") + TenantService.create_tenant_member(new_tenant, account, role="owner") + account.current_tenant = new_tenant + tenant_was_created.send(new_tenant) if not account: if not FeatureService.get_system_features().is_allow_register: diff --git a/api/controllers/console/billing/billing.py b/api/controllers/console/billing/billing.py index fd7b7bd8cb..4b0c82ae6c 100644 --- a/api/controllers/console/billing/billing.py +++ b/api/controllers/console/billing/billing.py @@ -1,5 +1,5 @@ -from flask_login import current_user # type: ignore -from flask_restful import Resource, reqparse # type: ignore +from flask_login import current_user +from flask_restful import Resource, reqparse from controllers.console import api from controllers.console.wraps import account_initialization_required, only_edition_cloud, setup_required diff --git a/api/controllers/console/billing/compliance.py b/api/controllers/console/billing/compliance.py index 6d5d668709..9679632ac7 100644 --- a/api/controllers/console/billing/compliance.py +++ b/api/controllers/console/billing/compliance.py @@ -1,6 +1,6 @@ from flask import request -from flask_login import current_user # type: ignore -from flask_restful import Resource, reqparse # type: ignore +from flask_login import current_user +from flask_restful import Resource, reqparse from libs.helper import extract_remote_ip from libs.login import login_required diff --git a/api/controllers/console/datasets/data_source.py b/api/controllers/console/datasets/data_source.py index 70bfb217eb..7b0d9373cf 100644 --- a/api/controllers/console/datasets/data_source.py +++ b/api/controllers/console/datasets/data_source.py @@ -2,8 +2,8 @@ import datetime import json from flask import request -from flask_login import current_user # type: ignore -from flask_restful import Resource, marshal_with, reqparse # type: ignore +from flask_login import current_user +from flask_restful import Resource, marshal_with, reqparse from sqlalchemy import select from sqlalchemy.orm import Session from werkzeug.exceptions import NotFound diff --git a/api/controllers/console/datasets/datasets.py b/api/controllers/console/datasets/datasets.py index 752d124735..e68273afa6 100644 --- a/api/controllers/console/datasets/datasets.py +++ b/api/controllers/console/datasets/datasets.py @@ -1,7 +1,7 @@ -import flask_restful # type: ignore +import flask_restful from flask import request -from flask_login import current_user # type: ignore # type: ignore -from flask_restful import Resource, marshal, marshal_with, reqparse # type: ignore +from flask_login import current_user +from flask_restful import Resource, marshal, marshal_with, reqparse from werkzeug.exceptions import Forbidden, NotFound import services @@ -526,17 +526,36 @@ class DatasetIndexingStatusApi(Resource): ) documents_status = [] for document in documents: - completed_segments = DocumentSegment.query.filter( - DocumentSegment.completed_at.isnot(None), - DocumentSegment.document_id == str(document.id), - DocumentSegment.status != "re_segment", - ).count() - total_segments = DocumentSegment.query.filter( - DocumentSegment.document_id == str(document.id), DocumentSegment.status != "re_segment" - ).count() - document.completed_segments = completed_segments - document.total_segments = total_segments - documents_status.append(marshal(document, document_status_fields)) + completed_segments = ( + db.session.query(DocumentSegment) + .filter( + DocumentSegment.completed_at.isnot(None), + DocumentSegment.document_id == str(document.id), + DocumentSegment.status != "re_segment", + ) + .count() + ) + total_segments = ( + db.session.query(DocumentSegment) + .filter(DocumentSegment.document_id == str(document.id), DocumentSegment.status != "re_segment") + .count() + ) + # Create a dictionary with document attributes and additional fields + document_dict = { + "id": document.id, + "indexing_status": document.indexing_status, + "processing_started_at": document.processing_started_at, + "parsing_completed_at": document.parsing_completed_at, + "cleaning_completed_at": document.cleaning_completed_at, + "splitting_completed_at": document.splitting_completed_at, + "completed_at": document.completed_at, + "paused_at": document.paused_at, + "error": document.error, + "stopped_at": document.stopped_at, + "completed_segments": completed_segments, + "total_segments": total_segments, + } + documents_status.append(marshal(document_dict, document_status_fields)) data = {"data": documents_status} return data @@ -657,6 +676,7 @@ class DatasetRetrievalSettingApi(Resource): | VectorType.ELASTICSEARCH | VectorType.ELASTICSEARCH_JA | VectorType.PGVECTOR + | VectorType.VASTBASE | VectorType.TIDB_ON_QDRANT | VectorType.LINDORM | VectorType.COUCHBASE @@ -706,6 +726,7 @@ class DatasetRetrievalSettingMockApi(Resource): | VectorType.ELASTICSEARCH_JA | VectorType.COUCHBASE | VectorType.PGVECTOR + | VectorType.VASTBASE | VectorType.LINDORM | VectorType.OPENGAUSS | VectorType.OCEANBASE diff --git a/api/controllers/console/datasets/datasets_document.py b/api/controllers/console/datasets/datasets_document.py index 0b40312368..f7c04102a9 100644 --- a/api/controllers/console/datasets/datasets_document.py +++ b/api/controllers/console/datasets/datasets_document.py @@ -4,9 +4,9 @@ from datetime import UTC, datetime from typing import cast from flask import request -from flask_login import current_user # type: ignore -from flask_restful import Resource, fields, marshal, marshal_with, reqparse # type: ignore -from sqlalchemy import asc, desc +from flask_login import current_user +from flask_restful import Resource, fields, marshal, marshal_with, reqparse +from sqlalchemy import asc, desc, select from werkzeug.exceptions import Forbidden, NotFound import services @@ -40,7 +40,7 @@ from core.indexing_runner import IndexingRunner from core.model_manager import ModelManager from core.model_runtime.entities.model_entities import ModelType from core.model_runtime.errors.invoke import InvokeAuthorizationError -from core.plugin.manager.exc import PluginDaemonClientSideError +from core.plugin.impl.exc import PluginDaemonClientSideError from core.rag.extractor.entity.extract_setting import ExtractSetting from extensions.ext_database import db from extensions.ext_redis import redis_client @@ -112,7 +112,7 @@ class GetProcessRuleApi(Resource): limits = DocumentService.DEFAULT_RULES["limits"] if document_id: # get the latest process rule - document = Document.query.get_or_404(document_id) + document = db.get_or_404(Document, document_id) dataset = DatasetService.get_dataset(document.dataset_id) @@ -175,7 +175,7 @@ class DatasetDocumentListApi(Resource): except services.errors.account.NoPermissionError as e: raise Forbidden(str(e)) - query = Document.query.filter_by(dataset_id=str(dataset_id), tenant_id=current_user.current_tenant_id) + query = select(Document).filter_by(dataset_id=str(dataset_id), tenant_id=current_user.current_tenant_id) if search: search = f"%{search}%" @@ -209,18 +209,24 @@ class DatasetDocumentListApi(Resource): desc(Document.position), ) - paginated_documents = query.paginate(page=page, per_page=limit, max_per_page=100, error_out=False) + paginated_documents = db.paginate(select=query, page=page, per_page=limit, max_per_page=100, error_out=False) documents = paginated_documents.items if fetch: for document in documents: - completed_segments = DocumentSegment.query.filter( - DocumentSegment.completed_at.isnot(None), - DocumentSegment.document_id == str(document.id), - DocumentSegment.status != "re_segment", - ).count() - total_segments = DocumentSegment.query.filter( - DocumentSegment.document_id == str(document.id), DocumentSegment.status != "re_segment" - ).count() + completed_segments = ( + db.session.query(DocumentSegment) + .filter( + DocumentSegment.completed_at.isnot(None), + DocumentSegment.document_id == str(document.id), + DocumentSegment.status != "re_segment", + ) + .count() + ) + total_segments = ( + db.session.query(DocumentSegment) + .filter(DocumentSegment.document_id == str(document.id), DocumentSegment.status != "re_segment") + .count() + ) document.completed_segments = completed_segments document.total_segments = total_segments data = marshal(documents, document_with_segments_fields) @@ -563,19 +569,36 @@ class DocumentBatchIndexingStatusApi(DocumentResource): documents = self.get_batch_documents(dataset_id, batch) documents_status = [] for document in documents: - completed_segments = DocumentSegment.query.filter( - DocumentSegment.completed_at.isnot(None), - DocumentSegment.document_id == str(document.id), - DocumentSegment.status != "re_segment", - ).count() - total_segments = DocumentSegment.query.filter( - DocumentSegment.document_id == str(document.id), DocumentSegment.status != "re_segment" - ).count() - document.completed_segments = completed_segments - document.total_segments = total_segments - if document.is_paused: - document.indexing_status = "paused" - documents_status.append(marshal(document, document_status_fields)) + completed_segments = ( + db.session.query(DocumentSegment) + .filter( + DocumentSegment.completed_at.isnot(None), + DocumentSegment.document_id == str(document.id), + DocumentSegment.status != "re_segment", + ) + .count() + ) + total_segments = ( + db.session.query(DocumentSegment) + .filter(DocumentSegment.document_id == str(document.id), DocumentSegment.status != "re_segment") + .count() + ) + # Create a dictionary with document attributes and additional fields + document_dict = { + "id": document.id, + "indexing_status": "paused" if document.is_paused else document.indexing_status, + "processing_started_at": document.processing_started_at, + "parsing_completed_at": document.parsing_completed_at, + "cleaning_completed_at": document.cleaning_completed_at, + "splitting_completed_at": document.splitting_completed_at, + "completed_at": document.completed_at, + "paused_at": document.paused_at, + "error": document.error, + "stopped_at": document.stopped_at, + "completed_segments": completed_segments, + "total_segments": total_segments, + } + documents_status.append(marshal(document_dict, document_status_fields)) data = {"data": documents_status} return data @@ -589,20 +612,37 @@ class DocumentIndexingStatusApi(DocumentResource): document_id = str(document_id) document = self.get_document(dataset_id, document_id) - completed_segments = DocumentSegment.query.filter( - DocumentSegment.completed_at.isnot(None), - DocumentSegment.document_id == str(document_id), - DocumentSegment.status != "re_segment", - ).count() - total_segments = DocumentSegment.query.filter( - DocumentSegment.document_id == str(document_id), DocumentSegment.status != "re_segment" - ).count() - - document.completed_segments = completed_segments - document.total_segments = total_segments - if document.is_paused: - document.indexing_status = "paused" - return marshal(document, document_status_fields) + completed_segments = ( + db.session.query(DocumentSegment) + .filter( + DocumentSegment.completed_at.isnot(None), + DocumentSegment.document_id == str(document_id), + DocumentSegment.status != "re_segment", + ) + .count() + ) + total_segments = ( + db.session.query(DocumentSegment) + .filter(DocumentSegment.document_id == str(document_id), DocumentSegment.status != "re_segment") + .count() + ) + + # Create a dictionary with document attributes and additional fields + document_dict = { + "id": document.id, + "indexing_status": "paused" if document.is_paused else document.indexing_status, + "processing_started_at": document.processing_started_at, + "parsing_completed_at": document.parsing_completed_at, + "cleaning_completed_at": document.cleaning_completed_at, + "splitting_completed_at": document.splitting_completed_at, + "completed_at": document.completed_at, + "paused_at": document.paused_at, + "error": document.error, + "stopped_at": document.stopped_at, + "completed_segments": completed_segments, + "total_segments": total_segments, + } + return marshal(document_dict, document_status_fields) class DocumentDetailApi(DocumentResource): diff --git a/api/controllers/console/datasets/datasets_segments.py b/api/controllers/console/datasets/datasets_segments.py index 696aaa94db..bc37907a30 100644 --- a/api/controllers/console/datasets/datasets_segments.py +++ b/api/controllers/console/datasets/datasets_segments.py @@ -2,8 +2,9 @@ import uuid import pandas as pd from flask import request -from flask_login import current_user # type: ignore -from flask_restful import Resource, marshal, reqparse # type: ignore +from flask_login import current_user +from flask_restful import Resource, marshal, reqparse +from sqlalchemy import select from werkzeug.exceptions import Forbidden, NotFound import services @@ -26,6 +27,7 @@ from controllers.console.wraps import ( from core.errors.error import LLMBadRequestError, ProviderTokenNotInitError from core.model_manager import ModelManager from core.model_runtime.entities.model_entities import ModelType +from extensions.ext_database import db from extensions.ext_redis import redis_client from fields.segment_fields import child_chunk_fields, segment_fields from libs.login import login_required @@ -74,9 +76,14 @@ class DatasetDocumentSegmentListApi(Resource): hit_count_gte = args["hit_count_gte"] keyword = args["keyword"] - query = DocumentSegment.query.filter( - DocumentSegment.document_id == str(document_id), DocumentSegment.tenant_id == current_user.current_tenant_id - ).order_by(DocumentSegment.position.asc()) + query = ( + select(DocumentSegment) + .filter( + DocumentSegment.document_id == str(document_id), + DocumentSegment.tenant_id == current_user.current_tenant_id, + ) + .order_by(DocumentSegment.position.asc()) + ) if status_list: query = query.filter(DocumentSegment.status.in_(status_list)) @@ -93,7 +100,7 @@ class DatasetDocumentSegmentListApi(Resource): elif args["enabled"].lower() == "false": query = query.filter(DocumentSegment.enabled == False) - segments = query.paginate(page=page, per_page=limit, max_per_page=100, error_out=False) + segments = db.paginate(select=query, page=page, per_page=limit, max_per_page=100, error_out=False) response = { "data": marshal(segments.items, segment_fields), @@ -131,7 +138,7 @@ class DatasetDocumentSegmentListApi(Resource): except services.errors.account.NoPermissionError as e: raise Forbidden(str(e)) SegmentService.delete_segments(segment_ids, document, dataset) - return {"result": "success"}, 200 + return {"result": "success"}, 204 class DatasetDocumentSegmentApi(Resource): @@ -276,9 +283,11 @@ class DatasetDocumentSegmentUpdateApi(Resource): raise ProviderNotInitializeError(ex.description) # check segment segment_id = str(segment_id) - segment = DocumentSegment.query.filter( - DocumentSegment.id == str(segment_id), DocumentSegment.tenant_id == current_user.current_tenant_id - ).first() + segment = ( + db.session.query(DocumentSegment) + .filter(DocumentSegment.id == str(segment_id), DocumentSegment.tenant_id == current_user.current_tenant_id) + .first() + ) if not segment: raise NotFound("Segment not found.") # The role of the current user in the ta table must be admin, owner, dataset_operator, or editor @@ -320,9 +329,11 @@ class DatasetDocumentSegmentUpdateApi(Resource): raise NotFound("Document not found.") # check segment segment_id = str(segment_id) - segment = DocumentSegment.query.filter( - DocumentSegment.id == str(segment_id), DocumentSegment.tenant_id == current_user.current_tenant_id - ).first() + segment = ( + db.session.query(DocumentSegment) + .filter(DocumentSegment.id == str(segment_id), DocumentSegment.tenant_id == current_user.current_tenant_id) + .first() + ) if not segment: raise NotFound("Segment not found.") # The role of the current user in the ta table must be admin, owner, dataset_operator, or editor @@ -333,7 +344,7 @@ class DatasetDocumentSegmentUpdateApi(Resource): except services.errors.account.NoPermissionError as e: raise Forbidden(str(e)) SegmentService.delete_segment(segment, document, dataset) - return {"result": "success"}, 200 + return {"result": "success"}, 204 class DatasetDocumentSegmentBatchImportApi(Resource): @@ -363,7 +374,7 @@ class DatasetDocumentSegmentBatchImportApi(Resource): if len(request.files) > 1: raise TooManyFilesError() # check file type - if not file.filename.endswith(".csv"): + if not file.filename or not file.filename.endswith(".csv"): raise ValueError("Invalid file type. Only CSV files are allowed") try: @@ -423,9 +434,11 @@ class ChildChunkAddApi(Resource): raise NotFound("Document not found.") # check segment segment_id = str(segment_id) - segment = DocumentSegment.query.filter( - DocumentSegment.id == str(segment_id), DocumentSegment.tenant_id == current_user.current_tenant_id - ).first() + segment = ( + db.session.query(DocumentSegment) + .filter(DocumentSegment.id == str(segment_id), DocumentSegment.tenant_id == current_user.current_tenant_id) + .first() + ) if not segment: raise NotFound("Segment not found.") if not current_user.is_dataset_editor: @@ -478,9 +491,11 @@ class ChildChunkAddApi(Resource): raise NotFound("Document not found.") # check segment segment_id = str(segment_id) - segment = DocumentSegment.query.filter( - DocumentSegment.id == str(segment_id), DocumentSegment.tenant_id == current_user.current_tenant_id - ).first() + segment = ( + db.session.query(DocumentSegment) + .filter(DocumentSegment.id == str(segment_id), DocumentSegment.tenant_id == current_user.current_tenant_id) + .first() + ) if not segment: raise NotFound("Segment not found.") parser = reqparse.RequestParser() @@ -523,9 +538,11 @@ class ChildChunkAddApi(Resource): raise NotFound("Document not found.") # check segment segment_id = str(segment_id) - segment = DocumentSegment.query.filter( - DocumentSegment.id == str(segment_id), DocumentSegment.tenant_id == current_user.current_tenant_id - ).first() + segment = ( + db.session.query(DocumentSegment) + .filter(DocumentSegment.id == str(segment_id), DocumentSegment.tenant_id == current_user.current_tenant_id) + .first() + ) if not segment: raise NotFound("Segment not found.") # The role of the current user in the ta table must be admin, owner, dataset_operator, or editor @@ -567,16 +584,20 @@ class ChildChunkUpdateApi(Resource): raise NotFound("Document not found.") # check segment segment_id = str(segment_id) - segment = DocumentSegment.query.filter( - DocumentSegment.id == str(segment_id), DocumentSegment.tenant_id == current_user.current_tenant_id - ).first() + segment = ( + db.session.query(DocumentSegment) + .filter(DocumentSegment.id == str(segment_id), DocumentSegment.tenant_id == current_user.current_tenant_id) + .first() + ) if not segment: raise NotFound("Segment not found.") # check child chunk child_chunk_id = str(child_chunk_id) - child_chunk = ChildChunk.query.filter( - ChildChunk.id == str(child_chunk_id), ChildChunk.tenant_id == current_user.current_tenant_id - ).first() + child_chunk = ( + db.session.query(ChildChunk) + .filter(ChildChunk.id == str(child_chunk_id), ChildChunk.tenant_id == current_user.current_tenant_id) + .first() + ) if not child_chunk: raise NotFound("Child chunk not found.") # The role of the current user in the ta table must be admin, owner, dataset_operator, or editor @@ -590,7 +611,7 @@ class ChildChunkUpdateApi(Resource): SegmentService.delete_child_chunk(child_chunk, dataset) except ChildChunkDeleteIndexServiceError as e: raise ChildChunkDeleteIndexError(str(e)) - return {"result": "success"}, 200 + return {"result": "success"}, 204 @setup_required @login_required @@ -612,16 +633,20 @@ class ChildChunkUpdateApi(Resource): raise NotFound("Document not found.") # check segment segment_id = str(segment_id) - segment = DocumentSegment.query.filter( - DocumentSegment.id == str(segment_id), DocumentSegment.tenant_id == current_user.current_tenant_id - ).first() + segment = ( + db.session.query(DocumentSegment) + .filter(DocumentSegment.id == str(segment_id), DocumentSegment.tenant_id == current_user.current_tenant_id) + .first() + ) if not segment: raise NotFound("Segment not found.") # check child chunk child_chunk_id = str(child_chunk_id) - child_chunk = ChildChunk.query.filter( - ChildChunk.id == str(child_chunk_id), ChildChunk.tenant_id == current_user.current_tenant_id - ).first() + child_chunk = ( + db.session.query(ChildChunk) + .filter(ChildChunk.id == str(child_chunk_id), ChildChunk.tenant_id == current_user.current_tenant_id) + .first() + ) if not child_chunk: raise NotFound("Child chunk not found.") # The role of the current user in the ta table must be admin, owner, dataset_operator, or editor diff --git a/api/controllers/console/datasets/external.py b/api/controllers/console/datasets/external.py index 2c031172bf..cf9081e154 100644 --- a/api/controllers/console/datasets/external.py +++ b/api/controllers/console/datasets/external.py @@ -1,6 +1,6 @@ from flask import request -from flask_login import current_user # type: ignore -from flask_restful import Resource, marshal, reqparse # type: ignore +from flask_login import current_user +from flask_restful import Resource, marshal, reqparse from werkzeug.exceptions import Forbidden, InternalServerError, NotFound import services @@ -135,7 +135,7 @@ class ExternalApiTemplateApi(Resource): raise Forbidden() ExternalDatasetService.delete_external_knowledge_api(current_user.current_tenant_id, external_knowledge_api_id) - return {"result": "success"}, 200 + return {"result": "success"}, 204 class ExternalApiUseCheckApi(Resource): @@ -209,6 +209,7 @@ class ExternalKnowledgeHitTestingApi(Resource): parser = reqparse.RequestParser() parser.add_argument("query", type=str, location="json") parser.add_argument("external_retrieval_model", type=dict, required=False, location="json") + parser.add_argument("metadata_filtering_conditions", type=dict, required=False, location="json") args = parser.parse_args() HitTestingService.hit_testing_args_check(args) @@ -219,6 +220,7 @@ class ExternalKnowledgeHitTestingApi(Resource): query=args["query"], account=current_user, external_retrieval_model=args["external_retrieval_model"], + metadata_filtering_conditions=args["metadata_filtering_conditions"], ) return response diff --git a/api/controllers/console/datasets/hit_testing.py b/api/controllers/console/datasets/hit_testing.py index d344e9d126..fba5d4c0f3 100644 --- a/api/controllers/console/datasets/hit_testing.py +++ b/api/controllers/console/datasets/hit_testing.py @@ -1,4 +1,4 @@ -from flask_restful import Resource # type: ignore +from flask_restful import Resource from controllers.console import api from controllers.console.datasets.hit_testing_base import DatasetsHitTestingBase diff --git a/api/controllers/console/datasets/hit_testing_base.py b/api/controllers/console/datasets/hit_testing_base.py index bd944602c1..3b4c076863 100644 --- a/api/controllers/console/datasets/hit_testing_base.py +++ b/api/controllers/console/datasets/hit_testing_base.py @@ -1,7 +1,7 @@ import logging -from flask_login import current_user # type: ignore -from flask_restful import marshal, reqparse # type: ignore +from flask_login import current_user +from flask_restful import marshal, reqparse from werkzeug.exceptions import Forbidden, InternalServerError, NotFound import services.dataset_service diff --git a/api/controllers/console/datasets/metadata.py b/api/controllers/console/datasets/metadata.py index fc9711169f..b1a83aa371 100644 --- a/api/controllers/console/datasets/metadata.py +++ b/api/controllers/console/datasets/metadata.py @@ -1,5 +1,5 @@ -from flask_login import current_user # type: ignore # type: ignore -from flask_restful import Resource, marshal_with, reqparse # type: ignore +from flask_login import current_user +from flask_restful import Resource, marshal_with, reqparse from werkzeug.exceptions import NotFound from controllers.console import api @@ -82,7 +82,7 @@ class DatasetMetadataApi(Resource): DatasetService.check_dataset_permission(dataset, current_user) MetadataService.delete_metadata(dataset_id_str, metadata_id_str) - return 200 + return {"result": "success"}, 204 class DatasetMetadataBuiltInFieldApi(Resource): diff --git a/api/controllers/console/datasets/website.py b/api/controllers/console/datasets/website.py index 33c926b4c9..4200a51709 100644 --- a/api/controllers/console/datasets/website.py +++ b/api/controllers/console/datasets/website.py @@ -1,4 +1,4 @@ -from flask_restful import Resource, reqparse # type: ignore +from flask_restful import Resource, reqparse from controllers.console import api from controllers.console.datasets.error import WebsiteCrawlError diff --git a/api/controllers/console/error.py b/api/controllers/console/error.py index b8fd1f0358..6944c56bf8 100644 --- a/api/controllers/console/error.py +++ b/api/controllers/console/error.py @@ -46,6 +46,18 @@ class NotAllowedCreateWorkspace(BaseHTTPException): code = 400 +class WorkspaceMembersLimitExceeded(BaseHTTPException): + error_code = "limit_exceeded" + description = "Unable to add member because the maximum workspace's member limit was exceeded" + code = 400 + + +class WorkspacesLimitExceeded(BaseHTTPException): + error_code = "limit_exceeded" + description = "Unable to create workspace because the maximum workspace limit was exceeded" + code = 400 + + class AccountBannedError(BaseHTTPException): error_code = "account_banned" description = "Account is banned." diff --git a/api/controllers/console/explore/audio.py b/api/controllers/console/explore/audio.py index c7f9fec326..54bc590677 100644 --- a/api/controllers/console/explore/audio.py +++ b/api/controllers/console/explore/audio.py @@ -66,7 +66,7 @@ class ChatAudioApi(InstalledAppResource): class ChatTextApi(InstalledAppResource): def post(self, installed_app): - from flask_restful import reqparse # type: ignore + from flask_restful import reqparse app_model = installed_app.app try: diff --git a/api/controllers/console/explore/completion.py b/api/controllers/console/explore/completion.py index e693a5a71b..4367da1162 100644 --- a/api/controllers/console/explore/completion.py +++ b/api/controllers/console/explore/completion.py @@ -1,8 +1,8 @@ import logging from datetime import UTC, datetime -from flask_login import current_user # type: ignore -from flask_restful import reqparse # type: ignore +from flask_login import current_user +from flask_restful import reqparse from werkzeug.exceptions import InternalServerError, NotFound import services diff --git a/api/controllers/console/explore/conversation.py b/api/controllers/console/explore/conversation.py index 600e78e09e..d7c161cc6d 100644 --- a/api/controllers/console/explore/conversation.py +++ b/api/controllers/console/explore/conversation.py @@ -1,6 +1,6 @@ -from flask_login import current_user # type: ignore -from flask_restful import marshal_with, reqparse # type: ignore -from flask_restful.inputs import int_range # type: ignore +from flask_login import current_user +from flask_restful import marshal_with, reqparse +from flask_restful.inputs import int_range from sqlalchemy.orm import Session from werkzeug.exceptions import NotFound diff --git a/api/controllers/console/explore/error.py b/api/controllers/console/explore/error.py index 18221b7797..1e05ff4206 100644 --- a/api/controllers/console/explore/error.py +++ b/api/controllers/console/explore/error.py @@ -23,3 +23,9 @@ class AppSuggestedQuestionsAfterAnswerDisabledError(BaseHTTPException): error_code = "app_suggested_questions_after_answer_disabled" description = "Function Suggested questions after answer disabled." code = 403 + + +class AppAccessDeniedError(BaseHTTPException): + error_code = "access_denied" + description = "App access denied." + code = 403 diff --git a/api/controllers/console/explore/installed_app.py b/api/controllers/console/explore/installed_app.py index 86550b2bdf..9d0c08564e 100644 --- a/api/controllers/console/explore/installed_app.py +++ b/api/controllers/console/explore/installed_app.py @@ -1,9 +1,10 @@ +import logging from datetime import UTC, datetime from typing import Any from flask import request -from flask_login import current_user # type: ignore -from flask_restful import Resource, inputs, marshal_with, reqparse # type: ignore +from flask_login import current_user +from flask_restful import Resource, inputs, marshal_with, reqparse from sqlalchemy import and_ from werkzeug.exceptions import BadRequest, Forbidden, NotFound @@ -15,6 +16,11 @@ from fields.installed_app_fields import installed_app_list_fields from libs.login import login_required from models import App, InstalledApp, RecommendedApp from services.account_service import TenantService +from services.app_service import AppService +from services.enterprise.enterprise_service import EnterpriseService +from services.feature_service import FeatureService + +logger = logging.getLogger(__name__) class InstalledAppsListApi(Resource): @@ -48,6 +54,28 @@ class InstalledAppsListApi(Resource): for installed_app in installed_apps if installed_app.app is not None ] + + # filter out apps that user doesn't have access to + if FeatureService.get_system_features().webapp_auth.enabled: + user_id = current_user.id + res = [] + app_ids = [installed_app["app"].id for installed_app in installed_app_list] + webapp_settings = EnterpriseService.WebAppAuth.batch_get_app_access_mode_by_id(app_ids) + for installed_app in installed_app_list: + webapp_setting = webapp_settings.get(installed_app["app"].id) + if not webapp_setting: + continue + if webapp_setting.access_mode == "sso_verified": + continue + app_code = AppService.get_app_code_by_id(str(installed_app["app"].id)) + if EnterpriseService.WebAppAuth.is_user_allowed_to_access_webapp( + user_id=user_id, + app_code=app_code, + ): + res.append(installed_app) + installed_app_list = res + logger.debug(f"installed_app_list: {installed_app_list}, user_id: {user_id}") + installed_app_list.sort( key=lambda app: ( -app["is_pinned"], @@ -66,7 +94,7 @@ class InstalledAppsListApi(Resource): parser.add_argument("app_id", type=str, required=True, help="Invalid app_id") args = parser.parse_args() - recommended_app = RecommendedApp.query.filter(RecommendedApp.app_id == args["app_id"]).first() + recommended_app = db.session.query(RecommendedApp).filter(RecommendedApp.app_id == args["app_id"]).first() if recommended_app is None: raise NotFound("App not found") @@ -79,9 +107,11 @@ class InstalledAppsListApi(Resource): if not app.is_public: raise Forbidden("You can't install a non-public app") - installed_app = InstalledApp.query.filter( - and_(InstalledApp.app_id == args["app_id"], InstalledApp.tenant_id == current_tenant_id) - ).first() + installed_app = ( + db.session.query(InstalledApp) + .filter(and_(InstalledApp.app_id == args["app_id"], InstalledApp.tenant_id == current_tenant_id)) + .first() + ) if installed_app is None: # todo: position @@ -113,7 +143,7 @@ class InstalledAppApi(InstalledAppResource): db.session.delete(installed_app) db.session.commit() - return {"result": "success", "message": "App uninstalled successfully"} + return {"result": "success", "message": "App uninstalled successfully"}, 204 def patch(self, installed_app): parser = reqparse.RequestParser() diff --git a/api/controllers/console/explore/message.py b/api/controllers/console/explore/message.py index ff12959a65..822777604a 100644 --- a/api/controllers/console/explore/message.py +++ b/api/controllers/console/explore/message.py @@ -1,8 +1,8 @@ import logging -from flask_login import current_user # type: ignore -from flask_restful import marshal_with, reqparse # type: ignore -from flask_restful.inputs import int_range # type: ignore +from flask_login import current_user +from flask_restful import marshal_with, reqparse +from flask_restful.inputs import int_range from werkzeug.exceptions import InternalServerError, NotFound import services diff --git a/api/controllers/console/explore/parameter.py b/api/controllers/console/explore/parameter.py index bf9f0d6b28..a1280d91d1 100644 --- a/api/controllers/console/explore/parameter.py +++ b/api/controllers/console/explore/parameter.py @@ -1,4 +1,4 @@ -from flask_restful import marshal_with # type: ignore +from flask_restful import marshal_with from controllers.common import fields from controllers.console import api diff --git a/api/controllers/console/explore/recommended_app.py b/api/controllers/console/explore/recommended_app.py index be6b1f5d21..ce85f495aa 100644 --- a/api/controllers/console/explore/recommended_app.py +++ b/api/controllers/console/explore/recommended_app.py @@ -1,5 +1,5 @@ -from flask_login import current_user # type: ignore -from flask_restful import Resource, fields, marshal_with, reqparse # type: ignore +from flask_login import current_user +from flask_restful import Resource, fields, marshal_with, reqparse from constants.languages import languages from controllers.console import api diff --git a/api/controllers/console/explore/saved_message.py b/api/controllers/console/explore/saved_message.py index 9f0c496645..339e7007a0 100644 --- a/api/controllers/console/explore/saved_message.py +++ b/api/controllers/console/explore/saved_message.py @@ -1,6 +1,6 @@ -from flask_login import current_user # type: ignore -from flask_restful import fields, marshal_with, reqparse # type: ignore -from flask_restful.inputs import int_range # type: ignore +from flask_login import current_user +from flask_restful import fields, marshal_with, reqparse +from flask_restful.inputs import int_range from werkzeug.exceptions import NotFound from controllers.console import api @@ -72,7 +72,7 @@ class SavedMessageApi(InstalledAppResource): SavedMessageService.delete(app_model, current_user, message_id) - return {"result": "success"} + return {"result": "success"}, 204 api.add_resource( diff --git a/api/controllers/console/explore/workflow.py b/api/controllers/console/explore/workflow.py index a2653a94f6..3f625e6609 100644 --- a/api/controllers/console/explore/workflow.py +++ b/api/controllers/console/explore/workflow.py @@ -1,6 +1,6 @@ import logging -from flask_restful import reqparse # type: ignore +from flask_restful import reqparse from werkzeug.exceptions import InternalServerError from controllers.console.app.error import ( diff --git a/api/controllers/console/explore/wraps.py b/api/controllers/console/explore/wraps.py index b7ba81fba2..afbd78bd5b 100644 --- a/api/controllers/console/explore/wraps.py +++ b/api/controllers/console/explore/wraps.py @@ -1,13 +1,17 @@ from functools import wraps -from flask_login import current_user # type: ignore -from flask_restful import Resource # type: ignore +from flask_login import current_user +from flask_restful import Resource from werkzeug.exceptions import NotFound +from controllers.console.explore.error import AppAccessDeniedError from controllers.console.wraps import account_initialization_required from extensions.ext_database import db from libs.login import login_required from models import InstalledApp +from services.app_service import AppService +from services.enterprise.enterprise_service import EnterpriseService +from services.feature_service import FeatureService def installed_app_required(view=None): @@ -48,6 +52,36 @@ def installed_app_required(view=None): return decorator +def user_allowed_to_access_app(view=None): + def decorator(view): + @wraps(view) + def decorated(installed_app: InstalledApp, *args, **kwargs): + feature = FeatureService.get_system_features() + if feature.webapp_auth.enabled: + app_id = installed_app.app_id + app_code = AppService.get_app_code_by_id(app_id) + res = EnterpriseService.WebAppAuth.is_user_allowed_to_access_webapp( + user_id=str(current_user.id), + app_code=app_code, + ) + if not res: + raise AppAccessDeniedError() + + return view(installed_app, *args, **kwargs) + + return decorated + + if view: + return decorator(view) + return decorator + + class InstalledAppResource(Resource): # must be reversed if there are multiple decorators - method_decorators = [installed_app_required, account_initialization_required, login_required] + + method_decorators = [ + user_allowed_to_access_app, + installed_app_required, + account_initialization_required, + login_required, + ] diff --git a/api/controllers/console/extension.py b/api/controllers/console/extension.py index ed6cedb220..07a241ef86 100644 --- a/api/controllers/console/extension.py +++ b/api/controllers/console/extension.py @@ -1,5 +1,5 @@ -from flask_login import current_user # type: ignore -from flask_restful import Resource, marshal_with, reqparse # type: ignore +from flask_login import current_user +from flask_restful import Resource, marshal_with, reqparse from constants import HIDDEN_VALUE from controllers.console import api @@ -99,7 +99,7 @@ class APIBasedExtensionDetailAPI(Resource): APIBasedExtensionService.delete(extension_data_from_db) - return {"result": "success"} + return {"result": "success"}, 204 api.add_resource(CodeBasedExtensionAPI, "/code-based-extension") diff --git a/api/controllers/console/feature.py b/api/controllers/console/feature.py index da1171412f..70ab4ff865 100644 --- a/api/controllers/console/feature.py +++ b/api/controllers/console/feature.py @@ -1,5 +1,5 @@ -from flask_login import current_user # type: ignore -from flask_restful import Resource # type: ignore +from flask_login import current_user +from flask_restful import Resource from libs.login import login_required from services.feature_service import FeatureService diff --git a/api/controllers/console/files.py b/api/controllers/console/files.py index 8cf754bbd6..66b6214f82 100644 --- a/api/controllers/console/files.py +++ b/api/controllers/console/files.py @@ -1,8 +1,8 @@ from typing import Literal from flask import request -from flask_login import current_user # type: ignore -from flask_restful import Resource, marshal_with # type: ignore +from flask_login import current_user +from flask_restful import Resource, marshal_with from werkzeug.exceptions import Forbidden import services diff --git a/api/controllers/console/init_validate.py b/api/controllers/console/init_validate.py index cfed5fe7a4..b19e331d2e 100644 --- a/api/controllers/console/init_validate.py +++ b/api/controllers/console/init_validate.py @@ -1,7 +1,7 @@ import os from flask import session -from flask_restful import Resource, reqparse # type: ignore +from flask_restful import Resource, reqparse from sqlalchemy import select from sqlalchemy.orm import Session diff --git a/api/controllers/console/ping.py b/api/controllers/console/ping.py index 2a116112a3..cd28cc946e 100644 --- a/api/controllers/console/ping.py +++ b/api/controllers/console/ping.py @@ -1,4 +1,4 @@ -from flask_restful import Resource # type: ignore +from flask_restful import Resource from controllers.console import api diff --git a/api/controllers/console/remote_files.py b/api/controllers/console/remote_files.py index 30afc930a8..b8cf019e4f 100644 --- a/api/controllers/console/remote_files.py +++ b/api/controllers/console/remote_files.py @@ -2,8 +2,8 @@ import urllib.parse from typing import cast import httpx -from flask_login import current_user # type: ignore -from flask_restful import Resource, marshal_with, reqparse # type: ignore +from flask_login import current_user +from flask_restful import Resource, marshal_with, reqparse import services from controllers.common import helpers diff --git a/api/controllers/console/setup.py b/api/controllers/console/setup.py index 3b47f8f12f..e1f19a87a3 100644 --- a/api/controllers/console/setup.py +++ b/api/controllers/console/setup.py @@ -1,5 +1,5 @@ from flask import request -from flask_restful import Resource, reqparse # type: ignore +from flask_restful import Resource, reqparse from configs import dify_config from libs.helper import StrLen, email, extract_remote_ip diff --git a/api/controllers/console/tag/tags.py b/api/controllers/console/tag/tags.py index da83f64019..cb5dedca21 100644 --- a/api/controllers/console/tag/tags.py +++ b/api/controllers/console/tag/tags.py @@ -1,6 +1,6 @@ from flask import request -from flask_login import current_user # type: ignore -from flask_restful import Resource, marshal_with, reqparse # type: ignore +from flask_login import current_user +from flask_restful import Resource, marshal_with, reqparse from werkzeug.exceptions import Forbidden from controllers.console import api @@ -86,7 +86,7 @@ class TagUpdateDeleteApi(Resource): TagService.delete_tag(tag_id) - return 200 + return 204 class TagBindingCreateApi(Resource): diff --git a/api/controllers/console/version.py b/api/controllers/console/version.py index 7773c99944..7dea8e554e 100644 --- a/api/controllers/console/version.py +++ b/api/controllers/console/version.py @@ -2,7 +2,7 @@ import json import logging import requests -from flask_restful import Resource, reqparse # type: ignore +from flask_restful import Resource, reqparse from packaging import version from configs import dify_config diff --git a/api/controllers/console/workspace/__init__.py b/api/controllers/console/workspace/__init__.py index 7af2b44a4a..072e904caf 100644 --- a/api/controllers/console/workspace/__init__.py +++ b/api/controllers/console/workspace/__init__.py @@ -1,6 +1,6 @@ from functools import wraps -from flask_login import current_user # type: ignore +from flask_login import current_user from sqlalchemy.orm import Session from werkzeug.exceptions import Forbidden diff --git a/api/controllers/console/workspace/account.py b/api/controllers/console/workspace/account.py index e9c25e6c5b..a9dbf44456 100644 --- a/api/controllers/console/workspace/account.py +++ b/api/controllers/console/workspace/account.py @@ -2,8 +2,8 @@ import datetime import pytz from flask import request -from flask_login import current_user # type: ignore -from flask_restful import Resource, fields, marshal_with, reqparse # type: ignore +from flask_login import current_user +from flask_restful import Resource, fields, marshal_with, reqparse from configs import dify_config from constants.languages import supported_language diff --git a/api/controllers/console/workspace/agent_providers.py b/api/controllers/console/workspace/agent_providers.py index a41d6c501c..88c37767e3 100644 --- a/api/controllers/console/workspace/agent_providers.py +++ b/api/controllers/console/workspace/agent_providers.py @@ -1,5 +1,5 @@ -from flask_login import current_user # type: ignore -from flask_restful import Resource # type: ignore +from flask_login import current_user +from flask_restful import Resource from controllers.console import api from controllers.console.wraps import account_initialization_required, setup_required diff --git a/api/controllers/console/workspace/endpoint.py b/api/controllers/console/workspace/endpoint.py index 46dee20f8b..eb53dcb16e 100644 --- a/api/controllers/console/workspace/endpoint.py +++ b/api/controllers/console/workspace/endpoint.py @@ -1,11 +1,11 @@ -from flask_login import current_user # type: ignore -from flask_restful import Resource, reqparse # type: ignore +from flask_login import current_user +from flask_restful import Resource, reqparse from werkzeug.exceptions import Forbidden from controllers.console import api from controllers.console.wraps import account_initialization_required, setup_required from core.model_runtime.utils.encoders import jsonable_encoder -from core.plugin.manager.exc import PluginPermissionDeniedError +from core.plugin.impl.exc import PluginPermissionDeniedError from libs.login import login_required from services.plugin.endpoint_service import EndpointService diff --git a/api/controllers/console/workspace/load_balancing_config.py b/api/controllers/console/workspace/load_balancing_config.py index 6e1d87cb12..ba74e2c074 100644 --- a/api/controllers/console/workspace/load_balancing_config.py +++ b/api/controllers/console/workspace/load_balancing_config.py @@ -1,4 +1,4 @@ -from flask_restful import Resource, reqparse # type: ignore +from flask_restful import Resource, reqparse from werkzeug.exceptions import Forbidden from controllers.console import api diff --git a/api/controllers/console/workspace/members.py b/api/controllers/console/workspace/members.py index a2b41c1d38..db49da7840 100644 --- a/api/controllers/console/workspace/members.py +++ b/api/controllers/console/workspace/members.py @@ -1,11 +1,12 @@ from urllib import parse -from flask_login import current_user # type: ignore -from flask_restful import Resource, abort, marshal_with, reqparse # type: ignore +from flask_login import current_user +from flask_restful import Resource, abort, marshal_with, reqparse import services from configs import dify_config from controllers.console import api +from controllers.console.error import WorkspaceMembersLimitExceeded from controllers.console.wraps import ( account_initialization_required, cloud_edition_billing_resource_check, @@ -17,6 +18,7 @@ from libs.login import login_required from models.account import Account, TenantAccountRole from services.account_service import RegisterService, TenantService from services.errors.account import AccountAlreadyInTenantError +from services.feature_service import FeatureService class MemberListApi(Resource): @@ -54,6 +56,12 @@ class MemberInviteEmailApi(Resource): inviter = current_user invitation_results = [] console_web_url = dify_config.CONSOLE_WEB_URL + + workspace_members = FeatureService.get_features(tenant_id=inviter.current_tenant.id).workspace_members + + if not workspace_members.is_available(len(invitee_emails)): + raise WorkspaceMembersLimitExceeded() + for invitee_email in invitee_emails: try: token = RegisterService.invite_new_member( @@ -71,7 +79,6 @@ class MemberInviteEmailApi(Resource): invitation_results.append( {"status": "success", "email": invitee_email, "url": f"{console_web_url}/signin"} ) - break except Exception as e: invitation_results.append({"status": "failed", "email": invitee_email, "message": str(e)}) diff --git a/api/controllers/console/workspace/model_providers.py b/api/controllers/console/workspace/model_providers.py index d7d1cc8d00..ff0fcbda6e 100644 --- a/api/controllers/console/workspace/model_providers.py +++ b/api/controllers/console/workspace/model_providers.py @@ -1,8 +1,8 @@ import io from flask import send_file -from flask_login import current_user # type: ignore -from flask_restful import Resource, reqparse # type: ignore +from flask_login import current_user +from flask_restful import Resource, reqparse from werkzeug.exceptions import Forbidden from controllers.console import api diff --git a/api/controllers/console/workspace/models.py b/api/controllers/console/workspace/models.py index 8b72a1ea3d..37d0f6c764 100644 --- a/api/controllers/console/workspace/models.py +++ b/api/controllers/console/workspace/models.py @@ -1,7 +1,7 @@ import logging -from flask_login import current_user # type: ignore -from flask_restful import Resource, reqparse # type: ignore +from flask_login import current_user +from flask_restful import Resource, reqparse from werkzeug.exceptions import Forbidden from controllers.console import api diff --git a/api/controllers/console/workspace/plugin.py b/api/controllers/console/workspace/plugin.py index e9c1884c60..9bddbb4b4b 100644 --- a/api/controllers/console/workspace/plugin.py +++ b/api/controllers/console/workspace/plugin.py @@ -1,8 +1,8 @@ import io from flask import request, send_file -from flask_login import current_user # type: ignore -from flask_restful import Resource, reqparse # type: ignore +from flask_login import current_user +from flask_restful import Resource, reqparse from werkzeug.exceptions import Forbidden from configs import dify_config @@ -10,7 +10,7 @@ from controllers.console import api from controllers.console.workspace import plugin_permission_required from controllers.console.wraps import account_initialization_required, setup_required from core.model_runtime.utils.encoders import jsonable_encoder -from core.plugin.manager.exc import PluginDaemonClientSideError +from core.plugin.impl.exc import PluginDaemonClientSideError from libs.login import login_required from models.account import TenantPluginPermission from services.plugin.plugin_permission_service import PluginPermissionService @@ -41,12 +41,16 @@ class PluginListApi(Resource): @account_initialization_required def get(self): tenant_id = current_user.current_tenant_id + parser = reqparse.RequestParser() + parser.add_argument("page", type=int, required=False, location="args", default=1) + parser.add_argument("page_size", type=int, required=False, location="args", default=256) + args = parser.parse_args() try: - plugins = PluginService.list(tenant_id) + plugins_with_total = PluginService.list_with_total(tenant_id, args["page"], args["page_size"]) except PluginDaemonClientSideError as e: raise ValueError(e) - return jsonable_encoder({"plugins": plugins}) + return jsonable_encoder({"plugins": plugins_with_total.list, "total": plugins_with_total.total}) class PluginListLatestVersionsApi(Resource): diff --git a/api/controllers/console/workspace/tool_providers.py b/api/controllers/console/workspace/tool_providers.py index 39ab454922..2b1379bfb2 100644 --- a/api/controllers/console/workspace/tool_providers.py +++ b/api/controllers/console/workspace/tool_providers.py @@ -1,8 +1,8 @@ import io from flask import send_file -from flask_login import current_user # type: ignore -from flask_restful import Resource, reqparse # type: ignore +from flask_login import current_user +from flask_restful import Resource, reqparse from sqlalchemy.orm import Session from werkzeug.exceptions import Forbidden diff --git a/api/controllers/console/workspace/workspace.py b/api/controllers/console/workspace/workspace.py index 332ed00222..19999e7361 100644 --- a/api/controllers/console/workspace/workspace.py +++ b/api/controllers/console/workspace/workspace.py @@ -1,8 +1,9 @@ import logging from flask import request -from flask_login import current_user # type: ignore -from flask_restful import Resource, fields, inputs, marshal, marshal_with, reqparse # type: ignore +from flask_login import current_user +from flask_restful import Resource, fields, inputs, marshal, marshal_with, reqparse +from sqlalchemy import select from werkzeug.exceptions import Unauthorized import services @@ -67,16 +68,24 @@ class TenantListApi(Resource): @account_initialization_required def get(self): tenants = TenantService.get_join_tenants(current_user) + tenant_dicts = [] for tenant in tenants: features = FeatureService.get_features(tenant.id) - if features.billing.enabled: - tenant.plan = features.billing.subscription.plan - else: - tenant.plan = "sandbox" - if tenant.id == current_user.current_tenant_id: - tenant.current = True # Set current=True for current tenant - return {"workspaces": marshal(tenants, tenants_fields)}, 200 + + # Create a dictionary with tenant attributes + tenant_dict = { + "id": tenant.id, + "name": tenant.name, + "status": tenant.status, + "created_at": tenant.created_at, + "plan": features.billing.subscription.plan if features.billing.enabled else "sandbox", + "current": tenant.id == current_user.current_tenant_id, + } + + tenant_dicts.append(tenant_dict) + + return {"workspaces": marshal(tenant_dicts, tenants_fields)}, 200 class WorkspaceListApi(Resource): @@ -88,9 +97,8 @@ class WorkspaceListApi(Resource): parser.add_argument("limit", type=inputs.int_range(1, 100), required=False, default=20, location="args") args = parser.parse_args() - tenants = Tenant.query.order_by(Tenant.created_at.desc()).paginate( - page=args["page"], per_page=args["limit"], error_out=False - ) + stmt = select(Tenant).order_by(Tenant.created_at.desc()) + tenants = db.paginate(select=stmt, page=args["page"], per_page=args["limit"], error_out=False) has_more = False if tenants.has_next: @@ -162,7 +170,7 @@ class CustomConfigWorkspaceApi(Resource): parser.add_argument("replace_webapp_logo", type=str, location="json") args = parser.parse_args() - tenant = Tenant.query.filter(Tenant.id == current_user.current_tenant_id).one_or_404() + tenant = db.get_or_404(Tenant, current_user.current_tenant_id) custom_config_dict = { "remove_webapp_brand": args["remove_webapp_brand"], @@ -226,7 +234,7 @@ class WorkspaceInfoApi(Resource): parser.add_argument("name", type=str, required=True, location="json") args = parser.parse_args() - tenant = Tenant.query.filter(Tenant.id == current_user.current_tenant_id).one_or_404() + tenant = db.get_or_404(Tenant, current_user.current_tenant_id) tenant.name = args["name"] db.session.commit() diff --git a/api/controllers/console/wraps.py b/api/controllers/console/wraps.py index e5e8038ad7..ca122772de 100644 --- a/api/controllers/console/wraps.py +++ b/api/controllers/console/wraps.py @@ -4,12 +4,13 @@ import time from functools import wraps from flask import abort, request -from flask_login import current_user # type: ignore +from flask_login import current_user from configs import dify_config from controllers.console.workspace.error import AccountNotInitializedError from extensions.ext_database import db from extensions.ext_redis import redis_client +from models.account import AccountStatus from models.dataset import RateLimitLog from models.model import DifySetup from services.feature_service import FeatureService, LicenseStatus @@ -24,7 +25,7 @@ def account_initialization_required(view): # check account initialization account = current_user - if account.status == "uninitialized": + if account.status == AccountStatus.UNINITIALIZED: raise AccountNotInitializedError() return view(*args, **kwargs) @@ -43,6 +44,17 @@ def only_edition_cloud(view): return decorated +def only_edition_enterprise(view): + @wraps(view) + def decorated(*args, **kwargs): + if not dify_config.ENTERPRISE_ENABLED: + abort(404) + + return view(*args, **kwargs) + + return decorated + + def only_edition_self_hosted(view): @wraps(view) def decorated(*args, **kwargs): diff --git a/api/controllers/files/image_preview.py b/api/controllers/files/image_preview.py index 5adfe16a79..46c19e1fbb 100644 --- a/api/controllers/files/image_preview.py +++ b/api/controllers/files/image_preview.py @@ -1,7 +1,7 @@ from urllib.parse import quote from flask import Response, request -from flask_restful import Resource, reqparse # type: ignore +from flask_restful import Resource, reqparse from werkzeug.exceptions import NotFound import services @@ -70,12 +70,26 @@ class FilePreviewApi(Resource): direct_passthrough=True, headers={}, ) + # add Accept-Ranges header for audio/video files + if upload_file.mime_type in [ + "audio/mpeg", + "audio/wav", + "audio/mp4", + "audio/ogg", + "audio/flac", + "audio/aac", + "video/mp4", + "video/webm", + "video/quicktime", + "audio/x-m4a", + ]: + response.headers["Accept-Ranges"] = "bytes" if upload_file.size > 0: response.headers["Content-Length"] = str(upload_file.size) if args["as_attachment"]: encoded_filename = quote(upload_file.name) response.headers["Content-Disposition"] = f"attachment; filename*=UTF-8''{encoded_filename}" - response.headers["Content-Type"] = "application/octet-stream" + response.headers["Content-Type"] = "application/octet-stream" return response diff --git a/api/controllers/files/tool_files.py b/api/controllers/files/tool_files.py index cfcce81247..1c3430ef4f 100644 --- a/api/controllers/files/tool_files.py +++ b/api/controllers/files/tool_files.py @@ -1,10 +1,14 @@ +from urllib.parse import quote + from flask import Response -from flask_restful import Resource, reqparse # type: ignore +from flask_restful import Resource, reqparse from werkzeug.exceptions import Forbidden, NotFound from controllers.files import api from controllers.files.error import UnsupportedFileTypeError +from core.tools.signature import verify_tool_file_signature from core.tools.tool_file_manager import ToolFileManager +from models import db as global_db class ToolFilePreviewApi(Resource): @@ -19,17 +23,14 @@ class ToolFilePreviewApi(Resource): parser.add_argument("as_attachment", type=bool, required=False, default=False, location="args") args = parser.parse_args() - - if not ToolFileManager.verify_file( - file_id=file_id, - timestamp=args["timestamp"], - nonce=args["nonce"], - sign=args["sign"], + if not verify_tool_file_signature( + file_id=file_id, timestamp=args["timestamp"], nonce=args["nonce"], sign=args["sign"] ): raise Forbidden("Invalid request.") try: - stream, tool_file = ToolFileManager.get_file_generator_by_tool_file_id( + tool_file_manager = ToolFileManager(engine=global_db.engine) + stream, tool_file = tool_file_manager.get_file_generator_by_tool_file_id( file_id, ) @@ -47,7 +48,8 @@ class ToolFilePreviewApi(Resource): if tool_file.size > 0: response.headers["Content-Length"] = str(tool_file.size) if args["as_attachment"]: - response.headers["Content-Disposition"] = f"attachment; filename={tool_file.name}" + encoded_filename = quote(tool_file.name) + response.headers["Content-Disposition"] = f"attachment; filename*=UTF-8''{encoded_filename}" return response diff --git a/api/controllers/files/upload.py b/api/controllers/files/upload.py index 28ee0eecf4..f1a15793c7 100644 --- a/api/controllers/files/upload.py +++ b/api/controllers/files/upload.py @@ -1,7 +1,7 @@ from mimetypes import guess_extension from flask import request -from flask_restful import Resource, marshal_with # type: ignore +from flask_restful import Resource, marshal_with from werkzeug.exceptions import Forbidden import services @@ -53,7 +53,7 @@ class PluginUploadFileApi(Resource): raise Forbidden("Invalid request.") try: - tool_file = ToolFileManager.create_file_by_raw( + tool_file = ToolFileManager().create_file_by_raw( user_id=user.id, tenant_id=tenant_id, file_binary=file.read(), @@ -64,9 +64,24 @@ class PluginUploadFileApi(Resource): extension = guess_extension(tool_file.mimetype) or ".bin" preview_url = ToolFileManager.sign_file(tool_file_id=tool_file.id, extension=extension) - tool_file.mime_type = mimetype - tool_file.extension = extension - tool_file.preview_url = preview_url + + # Create a dictionary with all the necessary attributes + result = { + "id": tool_file.id, + "user_id": tool_file.user_id, + "tenant_id": tool_file.tenant_id, + "conversation_id": tool_file.conversation_id, + "file_key": tool_file.file_key, + "mimetype": tool_file.mimetype, + "original_url": tool_file.original_url, + "name": tool_file.name, + "size": tool_file.size, + "mime_type": mimetype, + "extension": extension, + "preview_url": preview_url, + } + + return result, 201 except services.errors.file.FileTooLargeError as file_too_large_error: raise FileTooLargeError(file_too_large_error.description) except services.errors.file.UnsupportedFileTypeError: diff --git a/api/controllers/inner_api/__init__.py b/api/controllers/inner_api/__init__.py index f147a3453f..d51db4322a 100644 --- a/api/controllers/inner_api/__init__.py +++ b/api/controllers/inner_api/__init__.py @@ -5,5 +5,6 @@ from libs.external_api import ExternalApi bp = Blueprint("inner_api", __name__, url_prefix="/inner/api") api = ExternalApi(bp) +from . import mail from .plugin import plugin from .workspace import workspace diff --git a/api/controllers/inner_api/mail.py b/api/controllers/inner_api/mail.py new file mode 100644 index 0000000000..ce3373d65c --- /dev/null +++ b/api/controllers/inner_api/mail.py @@ -0,0 +1,27 @@ +from flask_restful import ( + Resource, # type: ignore + reqparse, +) + +from controllers.console.wraps import setup_required +from controllers.inner_api import api +from controllers.inner_api.wraps import enterprise_inner_api_only +from services.enterprise.mail_service import DifyMail, EnterpriseMailService + + +class EnterpriseMail(Resource): + @setup_required + @enterprise_inner_api_only + def post(self): + parser = reqparse.RequestParser() + parser.add_argument("to", type=str, action="append", required=True) + parser.add_argument("subject", type=str, required=True) + parser.add_argument("body", type=str, required=True) + parser.add_argument("substitutions", type=dict, required=False) + args = parser.parse_args() + + EnterpriseMailService.send_mail(DifyMail(**args)) + return {"message": "success"}, 200 + + +api.add_resource(EnterpriseMail, "/enterprise/mail") diff --git a/api/controllers/inner_api/plugin/plugin.py b/api/controllers/inner_api/plugin/plugin.py index 061ad62a4a..41063b35a5 100644 --- a/api/controllers/inner_api/plugin/plugin.py +++ b/api/controllers/inner_api/plugin/plugin.py @@ -1,4 +1,4 @@ -from flask_restful import Resource # type: ignore +from flask_restful import Resource from controllers.console.wraps import setup_required from controllers.inner_api import api @@ -29,7 +29,7 @@ from core.plugin.entities.request import ( RequestRequestUploadFile, ) from core.tools.entities.tool_entities import ToolProviderType -from libs.helper import compact_generate_response +from libs.helper import length_prefixed_response from models.account import Account, Tenant from models.model import EndUser @@ -44,7 +44,7 @@ class PluginInvokeLLMApi(Resource): response = PluginModelBackwardsInvocation.invoke_llm(user_model.id, tenant_model, payload) return PluginModelBackwardsInvocation.convert_to_event_stream(response) - return compact_generate_response(generator()) + return length_prefixed_response(0xF, generator()) class PluginInvokeTextEmbeddingApi(Resource): @@ -101,7 +101,7 @@ class PluginInvokeTTSApi(Resource): ) return PluginModelBackwardsInvocation.convert_to_event_stream(response) - return compact_generate_response(generator()) + return length_prefixed_response(0xF, generator()) class PluginInvokeSpeech2TextApi(Resource): @@ -162,7 +162,7 @@ class PluginInvokeToolApi(Resource): ), ) - return compact_generate_response(generator()) + return length_prefixed_response(0xF, generator()) class PluginInvokeParameterExtractorNodeApi(Resource): @@ -228,7 +228,7 @@ class PluginInvokeAppApi(Resource): files=payload.files, ) - return compact_generate_response(PluginAppBackwardsInvocation.convert_to_event_stream(response)) + return length_prefixed_response(0xF, PluginAppBackwardsInvocation.convert_to_event_stream(response)) class PluginInvokeEncryptApi(Resource): diff --git a/api/controllers/inner_api/plugin/wraps.py b/api/controllers/inner_api/plugin/wraps.py index c31f9d22ed..50408e0929 100644 --- a/api/controllers/inner_api/plugin/wraps.py +++ b/api/controllers/inner_api/plugin/wraps.py @@ -2,12 +2,14 @@ from collections.abc import Callable from functools import wraps from typing import Optional -from flask import request -from flask_restful import reqparse # type: ignore +from flask import current_app, request +from flask_login import user_logged_in +from flask_restful import reqparse from pydantic import BaseModel from sqlalchemy.orm import Session from extensions.ext_database import db +from libs.login import _get_user from models.account import Account, Tenant from models.model import EndUser from services.account_service import AccountService @@ -30,6 +32,7 @@ def get_user(tenant_id: str, user_id: str | None) -> Account | EndUser: ) session.add(user_model) session.commit() + session.refresh(user_model) else: user_model = AccountService.load_user(user_id) if not user_model: @@ -80,7 +83,12 @@ def get_user_tenant(view: Optional[Callable] = None): raise ValueError("tenant not found") kwargs["tenant_model"] = tenant_model - kwargs["user_model"] = get_user(tenant_id, user_id) + + user = get_user(tenant_id, user_id) + kwargs["user_model"] = user + + current_app.login_manager._update_request_context_with_user(user) # type: ignore + user_logged_in.send(current_app._get_current_object(), user=_get_user()) # type: ignore return view_func(*args, **kwargs) diff --git a/api/controllers/inner_api/workspace/workspace.py b/api/controllers/inner_api/workspace/workspace.py index 9dfa5d23c3..a2fc2d4675 100644 --- a/api/controllers/inner_api/workspace/workspace.py +++ b/api/controllers/inner_api/workspace/workspace.py @@ -1,6 +1,6 @@ import json -from flask_restful import Resource, reqparse # type: ignore +from flask_restful import Resource, reqparse from controllers.console.wraps import setup_required from controllers.inner_api import api diff --git a/api/controllers/inner_api/wraps.py b/api/controllers/inner_api/wraps.py index 86d3ad3dc5..f3a9312dd0 100644 --- a/api/controllers/inner_api/wraps.py +++ b/api/controllers/inner_api/wraps.py @@ -18,7 +18,7 @@ def enterprise_inner_api_only(view): # get header 'X-Inner-Api-Key' inner_api_key = request.headers.get("X-Inner-Api-Key") - if not inner_api_key or inner_api_key != dify_config.INNER_API_KEY_FOR_PLUGIN: + if not inner_api_key or inner_api_key != dify_config.INNER_API_KEY: abort(401) return view(*args, **kwargs) diff --git a/api/controllers/service_api/__init__.py b/api/controllers/service_api/__init__.py index d97074e8b9..d964e27819 100644 --- a/api/controllers/service_api/__init__.py +++ b/api/controllers/service_api/__init__.py @@ -6,6 +6,6 @@ bp = Blueprint("service_api", __name__, url_prefix="/v1") api = ExternalApi(bp) from . import index -from .app import annotation, app, audio, completion, conversation, file, message, workflow +from .app import annotation, app, audio, completion, conversation, file, message, site, workflow from .dataset import dataset, document, hit_testing, metadata, segment, upload_file from .workspace import models diff --git a/api/controllers/service_api/app/annotation.py b/api/controllers/service_api/app/annotation.py index cffa3665b1..595ae118ef 100644 --- a/api/controllers/service_api/app/annotation.py +++ b/api/controllers/service_api/app/annotation.py @@ -1,21 +1,21 @@ from flask import request -from flask_restful import Resource, marshal, marshal_with, reqparse # type: ignore +from flask_restful import Resource, marshal, marshal_with, reqparse from werkzeug.exceptions import Forbidden from controllers.service_api import api -from controllers.service_api.wraps import FetchUserArg, WhereisUserArg, validate_app_token +from controllers.service_api.wraps import validate_app_token from extensions.ext_redis import redis_client from fields.annotation_fields import ( annotation_fields, ) from libs.login import current_user -from models.model import App, EndUser +from models.model import App from services.annotation_service import AppAnnotationService class AnnotationReplyActionApi(Resource): - @validate_app_token(fetch_user_arg=FetchUserArg(fetch_from=WhereisUserArg.JSON)) - def post(self, app_model: App, end_user: EndUser, action): + @validate_app_token + def post(self, app_model: App, action): parser = reqparse.RequestParser() parser.add_argument("score_threshold", required=True, type=float, location="json") parser.add_argument("embedding_provider_name", required=True, type=str, location="json") @@ -31,8 +31,8 @@ class AnnotationReplyActionApi(Resource): class AnnotationReplyActionStatusApi(Resource): - @validate_app_token(fetch_user_arg=FetchUserArg(fetch_from=WhereisUserArg.QUERY)) - def get(self, app_model: App, end_user: EndUser, job_id, action): + @validate_app_token + def get(self, app_model: App, job_id, action): job_id = str(job_id) app_annotation_job_key = "{}_app_annotation_job_{}".format(action, str(job_id)) cache_result = redis_client.get(app_annotation_job_key) @@ -49,8 +49,8 @@ class AnnotationReplyActionStatusApi(Resource): class AnnotationListApi(Resource): - @validate_app_token(fetch_user_arg=FetchUserArg(fetch_from=WhereisUserArg.QUERY)) - def get(self, app_model: App, end_user: EndUser): + @validate_app_token + def get(self, app_model: App): page = request.args.get("page", default=1, type=int) limit = request.args.get("limit", default=20, type=int) keyword = request.args.get("keyword", default="", type=str) @@ -65,9 +65,9 @@ class AnnotationListApi(Resource): } return response, 200 - @validate_app_token(fetch_user_arg=FetchUserArg(fetch_from=WhereisUserArg.JSON)) + @validate_app_token @marshal_with(annotation_fields) - def post(self, app_model: App, end_user: EndUser): + def post(self, app_model: App): parser = reqparse.RequestParser() parser.add_argument("question", required=True, type=str, location="json") parser.add_argument("answer", required=True, type=str, location="json") @@ -77,9 +77,9 @@ class AnnotationListApi(Resource): class AnnotationUpdateDeleteApi(Resource): - @validate_app_token(fetch_user_arg=FetchUserArg(fetch_from=WhereisUserArg.JSON)) + @validate_app_token @marshal_with(annotation_fields) - def post(self, app_model: App, end_user: EndUser, annotation_id): + def put(self, app_model: App, annotation_id): if not current_user.is_editor: raise Forbidden() @@ -91,14 +91,14 @@ class AnnotationUpdateDeleteApi(Resource): annotation = AppAnnotationService.update_app_annotation_directly(args, app_model.id, annotation_id) return annotation - @validate_app_token(fetch_user_arg=FetchUserArg(fetch_from=WhereisUserArg.QUERY)) - def delete(self, app_model: App, end_user: EndUser, annotation_id): + @validate_app_token + def delete(self, app_model: App, annotation_id): if not current_user.is_editor: raise Forbidden() annotation_id = str(annotation_id) AppAnnotationService.delete_app_annotation(app_model.id, annotation_id) - return {"result": "success"}, 200 + return {"result": "success"}, 204 api.add_resource(AnnotationReplyActionApi, "/apps/annotation-reply/") diff --git a/api/controllers/service_api/app/app.py b/api/controllers/service_api/app/app.py index 7131e8a310..89222d5e83 100644 --- a/api/controllers/service_api/app/app.py +++ b/api/controllers/service_api/app/app.py @@ -1,4 +1,4 @@ -from flask_restful import Resource, marshal_with # type: ignore +from flask_restful import Resource, marshal_with from controllers.common import fields from controllers.service_api import api @@ -47,7 +47,13 @@ class AppInfoApi(Resource): def get(self, app_model: App): """Get app information""" tags = [tag.name for tag in app_model.tags] - return {"name": app_model.name, "description": app_model.description, "tags": tags} + return { + "name": app_model.name, + "description": app_model.description, + "tags": tags, + "mode": app_model.mode, + "author_name": app_model.author_name, + } api.add_resource(AppParameterApi, "/parameters") diff --git a/api/controllers/service_api/app/audio.py b/api/controllers/service_api/app/audio.py index e6bcc0bfd2..2682c2e7f1 100644 --- a/api/controllers/service_api/app/audio.py +++ b/api/controllers/service_api/app/audio.py @@ -1,7 +1,7 @@ import logging from flask import request -from flask_restful import Resource, reqparse # type: ignore +from flask_restful import Resource, reqparse from werkzeug.exceptions import InternalServerError import services diff --git a/api/controllers/service_api/app/completion.py b/api/controllers/service_api/app/completion.py index 38a65b7a90..1d9890199d 100644 --- a/api/controllers/service_api/app/completion.py +++ b/api/controllers/service_api/app/completion.py @@ -1,6 +1,6 @@ import logging -from flask_restful import Resource, reqparse # type: ignore +from flask_restful import Resource, reqparse from werkzeug.exceptions import InternalServerError, NotFound import services diff --git a/api/controllers/service_api/app/conversation.py b/api/controllers/service_api/app/conversation.py index 334f2c5620..36a7905572 100644 --- a/api/controllers/service_api/app/conversation.py +++ b/api/controllers/service_api/app/conversation.py @@ -1,5 +1,5 @@ -from flask_restful import Resource, marshal_with, reqparse # type: ignore -from flask_restful.inputs import int_range # type: ignore +from flask_restful import Resource, marshal_with, reqparse +from flask_restful.inputs import int_range from sqlalchemy.orm import Session from werkzeug.exceptions import NotFound @@ -14,6 +14,9 @@ from fields.conversation_fields import ( conversation_infinite_scroll_pagination_fields, simple_conversation_fields, ) +from fields.conversation_variable_fields import ( + conversation_variable_infinite_scroll_pagination_fields, +) from libs.helper import uuid_value from models.model import App, AppMode, EndUser from services.conversation_service import ConversationService @@ -69,7 +72,7 @@ class ConversationDetailApi(Resource): ConversationService.delete(app_model, conversation_id, end_user) except services.errors.conversation.ConversationNotExistsError: raise NotFound("Conversation Not Exists.") - return {"result": "success"}, 200 + return {"result": "success"}, 204 class ConversationRenameApi(Resource): @@ -93,6 +96,31 @@ class ConversationRenameApi(Resource): raise NotFound("Conversation Not Exists.") +class ConversationVariablesApi(Resource): + @validate_app_token(fetch_user_arg=FetchUserArg(fetch_from=WhereisUserArg.QUERY)) + @marshal_with(conversation_variable_infinite_scroll_pagination_fields) + def get(self, app_model: App, end_user: EndUser, c_id): + # conversational variable only for chat app + app_mode = AppMode.value_of(app_model.mode) + if app_mode not in {AppMode.CHAT, AppMode.AGENT_CHAT, AppMode.ADVANCED_CHAT}: + raise NotChatAppError() + + conversation_id = str(c_id) + + parser = reqparse.RequestParser() + parser.add_argument("last_id", type=uuid_value, location="args") + parser.add_argument("limit", type=int_range(1, 100), required=False, default=20, location="args") + args = parser.parse_args() + + try: + return ConversationService.get_conversational_variable( + app_model, conversation_id, end_user, args["limit"], args["last_id"] + ) + except services.errors.conversation.ConversationNotExistsError: + raise NotFound("Conversation Not Exists.") + + api.add_resource(ConversationRenameApi, "/conversations//name", endpoint="conversation_name") api.add_resource(ConversationApi, "/conversations") api.add_resource(ConversationDetailApi, "/conversations/", endpoint="conversation_detail") +api.add_resource(ConversationVariablesApi, "/conversations//variables", endpoint="conversation_variables") diff --git a/api/controllers/service_api/app/file.py b/api/controllers/service_api/app/file.py index 27b21b9f50..b0fd8e65ef 100644 --- a/api/controllers/service_api/app/file.py +++ b/api/controllers/service_api/app/file.py @@ -1,5 +1,5 @@ from flask import request -from flask_restful import Resource, marshal_with # type: ignore +from flask_restful import Resource, marshal_with import services from controllers.common.errors import FilenameNotExistsError diff --git a/api/controllers/service_api/app/message.py b/api/controllers/service_api/app/message.py index 95e538f4c7..d90fa2081f 100644 --- a/api/controllers/service_api/app/message.py +++ b/api/controllers/service_api/app/message.py @@ -1,8 +1,8 @@ import json import logging -from flask_restful import Resource, fields, marshal_with, reqparse # type: ignore -from flask_restful.inputs import int_range # type: ignore +from flask_restful import Resource, fields, marshal_with, reqparse +from flask_restful.inputs import int_range from werkzeug.exceptions import BadRequest, InternalServerError, NotFound import services @@ -93,6 +93,18 @@ class MessageFeedbackApi(Resource): return {"result": "success"} +class AppGetFeedbacksApi(Resource): + @validate_app_token + def get(self, app_model: App): + """Get All Feedbacks of an app""" + parser = reqparse.RequestParser() + parser.add_argument("page", type=int, default=1, location="args") + parser.add_argument("limit", type=int_range(1, 101), required=False, default=20, location="args") + args = parser.parse_args() + feedbacks = MessageService.get_all_messages_feedbacks(app_model, page=args["page"], limit=args["limit"]) + return {"data": feedbacks} + + class MessageSuggestedApi(Resource): @validate_app_token(fetch_user_arg=FetchUserArg(fetch_from=WhereisUserArg.QUERY, required=True)) def get(self, app_model: App, end_user: EndUser, message_id): @@ -119,3 +131,4 @@ class MessageSuggestedApi(Resource): api.add_resource(MessageListApi, "/messages") api.add_resource(MessageFeedbackApi, "/messages//feedbacks") api.add_resource(MessageSuggestedApi, "/messages//suggested") +api.add_resource(AppGetFeedbacksApi, "/app/feedbacks") diff --git a/api/controllers/service_api/app/site.py b/api/controllers/service_api/app/site.py new file mode 100644 index 0000000000..e752dfee30 --- /dev/null +++ b/api/controllers/service_api/app/site.py @@ -0,0 +1,30 @@ +from flask_restful import Resource, marshal_with +from werkzeug.exceptions import Forbidden + +from controllers.common import fields +from controllers.service_api import api +from controllers.service_api.wraps import validate_app_token +from extensions.ext_database import db +from models.account import TenantStatus +from models.model import App, Site + + +class AppSiteApi(Resource): + """Resource for app sites.""" + + @validate_app_token + @marshal_with(fields.site_fields) + def get(self, app_model: App): + """Retrieve app site info.""" + site = db.session.query(Site).filter(Site.app_id == app_model.id).first() + + if not site: + raise Forbidden() + + if app_model.tenant.status == TenantStatus.ARCHIVE: + raise Forbidden() + + return site + + +api.add_resource(AppSiteApi, "/site") diff --git a/api/controllers/service_api/app/workflow.py b/api/controllers/service_api/app/workflow.py index 8b10a028f3..df52b49424 100644 --- a/api/controllers/service_api/app/workflow.py +++ b/api/controllers/service_api/app/workflow.py @@ -1,8 +1,8 @@ import logging from dateutil.parser import isoparse -from flask_restful import Resource, fields, marshal_with, reqparse # type: ignore -from flask_restful.inputs import int_range # type: ignore +from flask_restful import Resource, fields, marshal_with, reqparse +from flask_restful.inputs import int_range from sqlalchemy.orm import Session from werkzeug.exceptions import InternalServerError @@ -24,12 +24,13 @@ from core.errors.error import ( QuotaExceededError, ) from core.model_runtime.errors.invoke import InvokeError +from core.workflow.entities.workflow_execution import WorkflowExecutionStatus from extensions.ext_database import db from fields.workflow_app_log_fields import workflow_app_log_pagination_fields from libs import helper from libs.helper import TimestampField from models.model import App, AppMode, EndUser -from models.workflow import WorkflowRun, WorkflowRunStatus +from models.workflow import WorkflowRun from services.app_generate_service import AppGenerateService from services.errors.llm import InvokeRateLimitError from services.workflow_app_service import WorkflowAppService @@ -59,7 +60,7 @@ class WorkflowRunDetailApi(Resource): Get a workflow task running detail """ app_mode = AppMode.value_of(app_model.mode) - if app_mode != AppMode.WORKFLOW: + if app_mode not in [AppMode.WORKFLOW, AppMode.ADVANCED_CHAT]: raise NotWorkflowAppError() workflow_run = db.session.query(WorkflowRun).filter(WorkflowRun.id == workflow_run_id).first() @@ -138,7 +139,7 @@ class WorkflowAppLogApi(Resource): parser.add_argument("limit", type=int_range(1, 100), default=20, location="args") args = parser.parse_args() - args.status = WorkflowRunStatus(args.status) if args.status else None + args.status = WorkflowExecutionStatus(args.status) if args.status else None if args.created_at__before: args.created_at__before = isoparse(args.created_at__before) diff --git a/api/controllers/service_api/dataset/dataset.py b/api/controllers/service_api/dataset/dataset.py index e1e6f3168f..27e8dd3fa6 100644 --- a/api/controllers/service_api/dataset/dataset.py +++ b/api/controllers/service_api/dataset/dataset.py @@ -1,19 +1,21 @@ from flask import request -from flask_restful import marshal, reqparse # type: ignore +from flask_restful import marshal, marshal_with, reqparse from werkzeug.exceptions import Forbidden, NotFound import services.dataset_service from controllers.service_api import api from controllers.service_api.dataset.error import DatasetInUseError, DatasetNameDuplicateError -from controllers.service_api.wraps import DatasetApiResource +from controllers.service_api.wraps import DatasetApiResource, validate_dataset_token from core.model_runtime.entities.model_entities import ModelType from core.plugin.entities.plugin import ModelProviderID from core.provider_manager import ProviderManager from fields.dataset_fields import dataset_detail_fields +from fields.tag_fields import tag_fields from libs.login import current_user from models.dataset import Dataset, DatasetPermissionEnum from services.dataset_service import DatasetPermissionService, DatasetService from services.entities.knowledge_entities.knowledge_entities import RetrievalModel +from services.tag_service import TagService def _validate_name(name): @@ -313,12 +315,142 @@ class DatasetApi(DatasetApiResource): try: if DatasetService.delete_dataset(dataset_id_str, current_user): DatasetPermissionService.clear_partial_member_list(dataset_id_str) - return {"result": "success"}, 204 + return 204 else: raise NotFound("Dataset not found.") except services.errors.dataset.DatasetInUseError: raise DatasetInUseError() +class DatasetTagsApi(DatasetApiResource): + @validate_dataset_token + @marshal_with(tag_fields) + def get(self, _, dataset_id): + """Get all knowledge type tags.""" + tags = TagService.get_tags("knowledge", current_user.current_tenant_id) + + return tags, 200 + + @validate_dataset_token + def post(self, _, dataset_id): + """Add a knowledge type tag.""" + if not (current_user.is_editor or current_user.is_dataset_editor): + raise Forbidden() + + parser = reqparse.RequestParser() + parser.add_argument( + "name", + nullable=False, + required=True, + help="Name must be between 1 to 50 characters.", + type=DatasetTagsApi._validate_tag_name, + ) + + args = parser.parse_args() + args["type"] = "knowledge" + tag = TagService.save_tags(args) + + response = {"id": tag.id, "name": tag.name, "type": tag.type, "binding_count": 0} + + return response, 200 + + @validate_dataset_token + def patch(self, _, dataset_id): + if not (current_user.is_editor or current_user.is_dataset_editor): + raise Forbidden() + + parser = reqparse.RequestParser() + parser.add_argument( + "name", + nullable=False, + required=True, + help="Name must be between 1 to 50 characters.", + type=DatasetTagsApi._validate_tag_name, + ) + parser.add_argument("tag_id", nullable=False, required=True, help="Id of a tag.", type=str) + args = parser.parse_args() + args["type"] = "knowledge" + tag = TagService.update_tags(args, args.get("tag_id")) + + binding_count = TagService.get_tag_binding_count(args.get("tag_id")) + + response = {"id": tag.id, "name": tag.name, "type": tag.type, "binding_count": binding_count} + + return response, 200 + + @validate_dataset_token + def delete(self, _, dataset_id): + """Delete a knowledge type tag.""" + if not current_user.is_editor: + raise Forbidden() + parser = reqparse.RequestParser() + parser.add_argument("tag_id", nullable=False, required=True, help="Id of a tag.", type=str) + args = parser.parse_args() + TagService.delete_tag(args.get("tag_id")) + + return 204 + + @staticmethod + def _validate_tag_name(name): + if not name or len(name) < 1 or len(name) > 50: + raise ValueError("Name must be between 1 to 50 characters.") + return name + + +class DatasetTagBindingApi(DatasetApiResource): + @validate_dataset_token + def post(self, _, dataset_id): + # The role of the current user in the ta table must be admin, owner, editor, or dataset_operator + if not (current_user.is_editor or current_user.is_dataset_editor): + raise Forbidden() + + parser = reqparse.RequestParser() + parser.add_argument( + "tag_ids", type=list, nullable=False, required=True, location="json", help="Tag IDs is required." + ) + parser.add_argument( + "target_id", type=str, nullable=False, required=True, location="json", help="Target Dataset ID is required." + ) + + args = parser.parse_args() + args["type"] = "knowledge" + TagService.save_tag_binding(args) + + return 204 + + +class DatasetTagUnbindingApi(DatasetApiResource): + @validate_dataset_token + def post(self, _, dataset_id): + # The role of the current user in the ta table must be admin, owner, editor, or dataset_operator + if not (current_user.is_editor or current_user.is_dataset_editor): + raise Forbidden() + + parser = reqparse.RequestParser() + parser.add_argument("tag_id", type=str, nullable=False, required=True, help="Tag ID is required.") + parser.add_argument("target_id", type=str, nullable=False, required=True, help="Target ID is required.") + + args = parser.parse_args() + args["type"] = "knowledge" + TagService.delete_tag_binding(args) + + return 204 + + +class DatasetTagsBindingStatusApi(DatasetApiResource): + @validate_dataset_token + def get(self, _, *args, **kwargs): + """Get all knowledge type tags.""" + dataset_id = kwargs.get("dataset_id") + tags = TagService.get_tags_by_target_id("knowledge", current_user.current_tenant_id, str(dataset_id)) + tags_list = [{"id": tag.id, "name": tag.name} for tag in tags] + response = {"data": tags_list, "total": len(tags)} + return response, 200 + + api.add_resource(DatasetListApi, "/datasets") api.add_resource(DatasetApi, "/datasets/") +api.add_resource(DatasetTagsApi, "/datasets/tags") +api.add_resource(DatasetTagBindingApi, "/datasets/tags/binding") +api.add_resource(DatasetTagUnbindingApi, "/datasets/tags/unbinding") +api.add_resource(DatasetTagsBindingStatusApi, "/datasets//tags") diff --git a/api/controllers/service_api/dataset/document.py b/api/controllers/service_api/dataset/document.py index eec6afc9ef..ab7ab4dcf0 100644 --- a/api/controllers/service_api/dataset/document.py +++ b/api/controllers/service_api/dataset/document.py @@ -1,11 +1,11 @@ import json from flask import request -from flask_restful import marshal, reqparse # type: ignore -from sqlalchemy import desc +from flask_restful import marshal, reqparse +from sqlalchemy import desc, select from werkzeug.exceptions import NotFound -import services.dataset_service +import services from controllers.common.errors import FilenameNotExistsError from controllers.service_api import api from controllers.service_api.app.error import ( @@ -175,8 +175,11 @@ class DocumentAddByFileApi(DatasetApiResource): if not dataset: raise ValueError("Dataset does not exist.") - if not dataset.indexing_technique and not args.get("indexing_technique"): + + indexing_technique = args.get("indexing_technique") or dataset.indexing_technique + if not indexing_technique: raise ValueError("indexing_technique is required.") + args["indexing_technique"] = indexing_technique # save file info file = request.files["file"] @@ -206,12 +209,16 @@ class DocumentAddByFileApi(DatasetApiResource): knowledge_config = KnowledgeConfig(**args) DocumentService.document_create_args_validate(knowledge_config) + dataset_process_rule = dataset.latest_process_rule if "process_rule" not in args else None + if not knowledge_config.original_document_id and not dataset_process_rule and not knowledge_config.process_rule: + raise ValueError("process_rule is required.") + try: documents, batch = DocumentService.save_document_with_dataset_id( dataset=dataset, knowledge_config=knowledge_config, account=dataset.created_by_account, - dataset_process_rule=dataset.latest_process_rule if "process_rule" not in args else None, + dataset_process_rule=dataset_process_rule, created_from="api", ) except ProviderTokenNotInitError as ex: @@ -323,7 +330,7 @@ class DocumentDeleteApi(DatasetApiResource): except services.errors.document.DocumentIndexingError: raise DocumentIndexingError("Cannot delete document during indexing.") - return {"result": "success"}, 200 + return 204 class DocumentListApi(DatasetApiResource): @@ -337,7 +344,7 @@ class DocumentListApi(DatasetApiResource): if not dataset: raise NotFound("Dataset not found.") - query = Document.query.filter_by(dataset_id=str(dataset_id), tenant_id=tenant_id) + query = select(Document).filter_by(dataset_id=str(dataset_id), tenant_id=tenant_id) if search: search = f"%{search}%" @@ -345,7 +352,7 @@ class DocumentListApi(DatasetApiResource): query = query.order_by(desc(Document.created_at), desc(Document.position)) - paginated_documents = query.paginate(page=page, per_page=limit, max_per_page=100, error_out=False) + paginated_documents = db.paginate(select=query, page=page, per_page=limit, max_per_page=100, error_out=False) documents = paginated_documents.items response = { @@ -374,19 +381,36 @@ class DocumentIndexingStatusApi(DatasetApiResource): raise NotFound("Documents not found.") documents_status = [] for document in documents: - completed_segments = DocumentSegment.query.filter( - DocumentSegment.completed_at.isnot(None), - DocumentSegment.document_id == str(document.id), - DocumentSegment.status != "re_segment", - ).count() - total_segments = DocumentSegment.query.filter( - DocumentSegment.document_id == str(document.id), DocumentSegment.status != "re_segment" - ).count() - document.completed_segments = completed_segments - document.total_segments = total_segments - if document.is_paused: - document.indexing_status = "paused" - documents_status.append(marshal(document, document_status_fields)) + completed_segments = ( + db.session.query(DocumentSegment) + .filter( + DocumentSegment.completed_at.isnot(None), + DocumentSegment.document_id == str(document.id), + DocumentSegment.status != "re_segment", + ) + .count() + ) + total_segments = ( + db.session.query(DocumentSegment) + .filter(DocumentSegment.document_id == str(document.id), DocumentSegment.status != "re_segment") + .count() + ) + # Create a dictionary with document attributes and additional fields + document_dict = { + "id": document.id, + "indexing_status": "paused" if document.is_paused else document.indexing_status, + "processing_started_at": document.processing_started_at, + "parsing_completed_at": document.parsing_completed_at, + "cleaning_completed_at": document.cleaning_completed_at, + "splitting_completed_at": document.splitting_completed_at, + "completed_at": document.completed_at, + "paused_at": document.paused_at, + "error": document.error, + "stopped_at": document.stopped_at, + "completed_segments": completed_segments, + "total_segments": total_segments, + } + documents_status.append(marshal(document_dict, document_status_fields)) data = {"data": documents_status} return data diff --git a/api/controllers/service_api/dataset/metadata.py b/api/controllers/service_api/dataset/metadata.py index 298c8a8df8..35582feea0 100644 --- a/api/controllers/service_api/dataset/metadata.py +++ b/api/controllers/service_api/dataset/metadata.py @@ -1,5 +1,5 @@ -from flask_login import current_user # type: ignore # type: ignore -from flask_restful import marshal, reqparse # type: ignore +from flask_login import current_user # type: ignore +from flask_restful import marshal, reqparse from werkzeug.exceptions import NotFound from controllers.service_api import api @@ -63,7 +63,7 @@ class DatasetMetadataServiceApi(DatasetApiResource): DatasetService.check_dataset_permission(dataset, current_user) MetadataService.delete_metadata(dataset_id_str, metadata_id_str) - return 200 + return 204 class DatasetMetadataBuiltInFieldServiceApi(DatasetApiResource): diff --git a/api/controllers/service_api/dataset/segment.py b/api/controllers/service_api/dataset/segment.py index 2a79e15cc5..337752275a 100644 --- a/api/controllers/service_api/dataset/segment.py +++ b/api/controllers/service_api/dataset/segment.py @@ -1,6 +1,6 @@ from flask import request -from flask_login import current_user # type: ignore -from flask_restful import marshal, reqparse # type: ignore +from flask_login import current_user +from flask_restful import marshal, reqparse from werkzeug.exceptions import NotFound from controllers.service_api import api @@ -159,7 +159,7 @@ class DatasetSegmentApi(DatasetApiResource): if not segment: raise NotFound("Segment not found.") SegmentService.delete_segment(segment, document, dataset) - return {"result": "success"}, 200 + return 204 @cloud_edition_billing_resource_check("vector_space", "dataset") def post(self, tenant_id, dataset_id, document_id, segment_id): @@ -208,6 +208,28 @@ class DatasetSegmentApi(DatasetApiResource): ) return {"data": marshal(updated_segment, segment_fields), "doc_form": document.doc_form}, 200 + def get(self, tenant_id, dataset_id, document_id, segment_id): + # check dataset + dataset_id = str(dataset_id) + tenant_id = str(tenant_id) + dataset = db.session.query(Dataset).filter(Dataset.tenant_id == tenant_id, Dataset.id == dataset_id).first() + if not dataset: + raise NotFound("Dataset not found.") + # check user's model setting + DatasetService.check_dataset_model_setting(dataset) + # check document + document_id = str(document_id) + document = DocumentService.get_document(dataset_id, document_id) + if not document: + raise NotFound("Document not found.") + # check segment + segment_id = str(segment_id) + segment = SegmentService.get_segment_by_id(segment_id=segment_id, tenant_id=current_user.current_tenant_id) + if not segment: + raise NotFound("Segment not found.") + + return {"data": marshal(segment, segment_fields), "doc_form": document.doc_form}, 200 + class ChildChunkApi(DatasetApiResource): """Resource for child chunks.""" @@ -344,7 +366,7 @@ class DatasetChildChunkApi(DatasetApiResource): except ChildChunkDeleteIndexServiceError as e: raise ChildChunkDeleteIndexError(str(e)) - return {"result": "success"}, 200 + return 204 @cloud_edition_billing_resource_check("vector_space", "dataset") @cloud_edition_billing_knowledge_limit_check("add_segment", "dataset") diff --git a/api/controllers/service_api/index.py b/api/controllers/service_api/index.py index 75d9141a6d..d24c4597e2 100644 --- a/api/controllers/service_api/index.py +++ b/api/controllers/service_api/index.py @@ -1,4 +1,4 @@ -from flask_restful import Resource # type: ignore +from flask_restful import Resource from configs import dify_config from controllers.service_api import api diff --git a/api/controllers/service_api/workspace/models.py b/api/controllers/service_api/workspace/models.py index 373f8019f9..3f18474674 100644 --- a/api/controllers/service_api/workspace/models.py +++ b/api/controllers/service_api/workspace/models.py @@ -1,5 +1,5 @@ -from flask_login import current_user # type: ignore -from flask_restful import Resource # type: ignore +from flask_login import current_user +from flask_restful import Resource from controllers.service_api import api from controllers.service_api.wraps import validate_dataset_token diff --git a/api/controllers/service_api/wraps.py b/api/controllers/service_api/wraps.py index 7facb03358..d3316a5159 100644 --- a/api/controllers/service_api/wraps.py +++ b/api/controllers/service_api/wraps.py @@ -7,7 +7,7 @@ from typing import Optional from flask import current_app, request from flask_login import user_logged_in # type: ignore -from flask_restful import Resource # type: ignore +from flask_restful import Resource from pydantic import BaseModel from sqlalchemy import select, update from sqlalchemy.orm import Session @@ -99,7 +99,12 @@ def validate_app_token(view: Optional[Callable] = None, *, fetch_user_arg: Optio if user_id: user_id = str(user_id) - kwargs["end_user"] = create_or_update_end_user_for_user_id(app_model, user_id) + end_user = create_or_update_end_user_for_user_id(app_model, user_id) + kwargs["end_user"] = end_user + + # Set EndUser as current logged-in user for flask_login.current_user + current_app.login_manager._update_request_context_with_user(end_user) # type: ignore + user_logged_in.send(current_app._get_current_object(), user=end_user) # type: ignore return view_func(*args, **kwargs) diff --git a/api/controllers/web/__init__.py b/api/controllers/web/__init__.py index 50a04a6254..56749a0e25 100644 --- a/api/controllers/web/__init__.py +++ b/api/controllers/web/__init__.py @@ -15,4 +15,17 @@ api.add_resource(FileApi, "/files/upload") api.add_resource(RemoteFileInfoApi, "/remote-files/") api.add_resource(RemoteFileUploadApi, "/remote-files/upload") -from . import app, audio, completion, conversation, feature, message, passport, saved_message, site, workflow +from . import ( + app, + audio, + completion, + conversation, + feature, + forgot_password, + login, + message, + passport, + saved_message, + site, + workflow, +) diff --git a/api/controllers/web/app.py b/api/controllers/web/app.py index a84b846112..94a525a75d 100644 --- a/api/controllers/web/app.py +++ b/api/controllers/web/app.py @@ -1,12 +1,17 @@ -from flask_restful import marshal_with # type: ignore +from flask import request +from flask_restful import Resource, marshal_with, reqparse from controllers.common import fields from controllers.web import api from controllers.web.error import AppUnavailableError from controllers.web.wraps import WebApiResource from core.app.app_config.common.parameters_mapping import get_parameters_from_feature_dict +from libs.passport import PassportService from models.model import App, AppMode from services.app_service import AppService +from services.enterprise.enterprise_service import EnterpriseService +from services.feature_service import FeatureService +from services.webapp_auth_service import WebAppAuthService class AppParameterApi(WebApiResource): @@ -40,5 +45,69 @@ class AppMeta(WebApiResource): return AppService().get_app_meta(app_model) +class AppAccessMode(Resource): + def get(self): + parser = reqparse.RequestParser() + parser.add_argument("appId", type=str, required=False, location="args") + parser.add_argument("appCode", type=str, required=False, location="args") + args = parser.parse_args() + + features = FeatureService.get_system_features() + if not features.webapp_auth.enabled: + return {"accessMode": "public"} + + app_id = args.get("appId") + if args.get("appCode"): + app_code = args["appCode"] + app_id = AppService.get_app_id_by_code(app_code) + + if not app_id: + raise ValueError("appId or appCode must be provided") + + res = EnterpriseService.WebAppAuth.get_app_access_mode_by_id(app_id) + + return {"accessMode": res.access_mode} + + +class AppWebAuthPermission(Resource): + def get(self): + user_id = "visitor" + try: + auth_header = request.headers.get("Authorization") + if auth_header is None: + raise + if " " not in auth_header: + raise + + auth_scheme, tk = auth_header.split(None, 1) + auth_scheme = auth_scheme.lower() + if auth_scheme != "bearer": + raise + + decoded = PassportService().verify(tk) + user_id = decoded.get("user_id", "visitor") + except Exception as e: + pass + + features = FeatureService.get_system_features() + if not features.webapp_auth.enabled: + return {"result": True} + + parser = reqparse.RequestParser() + parser.add_argument("appId", type=str, required=True, location="args") + args = parser.parse_args() + + app_id = args["appId"] + app_code = AppService.get_app_code_by_id(app_id) + + res = True + if WebAppAuthService.is_app_require_permission_check(app_id=app_id): + res = EnterpriseService.WebAppAuth.is_user_allowed_to_access_webapp(str(user_id), app_code) + return {"result": res} + + api.add_resource(AppParameterApi, "/parameters") api.add_resource(AppMeta, "/meta") +# webapp auth apis +api.add_resource(AppAccessMode, "/webapp/access-mode") +api.add_resource(AppWebAuthPermission, "/webapp/permission") diff --git a/api/controllers/web/audio.py b/api/controllers/web/audio.py index 97d980d07c..06d9ad7564 100644 --- a/api/controllers/web/audio.py +++ b/api/controllers/web/audio.py @@ -65,7 +65,7 @@ class AudioApi(WebApiResource): class TextApi(WebApiResource): def post(self, app_model: App, end_user): - from flask_restful import reqparse # type: ignore + from flask_restful import reqparse try: parser = reqparse.RequestParser() diff --git a/api/controllers/web/completion.py b/api/controllers/web/completion.py index 9677401490..fd3b9aa804 100644 --- a/api/controllers/web/completion.py +++ b/api/controllers/web/completion.py @@ -1,6 +1,6 @@ import logging -from flask_restful import reqparse # type: ignore +from flask_restful import reqparse from werkzeug.exceptions import InternalServerError, NotFound import services diff --git a/api/controllers/web/conversation.py b/api/controllers/web/conversation.py index 419247ea14..98cea3974f 100644 --- a/api/controllers/web/conversation.py +++ b/api/controllers/web/conversation.py @@ -1,5 +1,5 @@ -from flask_restful import marshal_with, reqparse # type: ignore -from flask_restful.inputs import int_range # type: ignore +from flask_restful import marshal_with, reqparse +from flask_restful.inputs import int_range from sqlalchemy.orm import Session from werkzeug.exceptions import NotFound diff --git a/api/controllers/web/error.py b/api/controllers/web/error.py index 9fe5d08d54..4371e679db 100644 --- a/api/controllers/web/error.py +++ b/api/controllers/web/error.py @@ -121,9 +121,15 @@ class UnsupportedFileTypeError(BaseHTTPException): code = 415 -class WebSSOAuthRequiredError(BaseHTTPException): +class WebAppAuthRequiredError(BaseHTTPException): error_code = "web_sso_auth_required" - description = "Web SSO authentication required." + description = "Web app authentication required." + code = 401 + + +class WebAppAuthAccessDeniedError(BaseHTTPException): + error_code = "web_app_access_denied" + description = "You do not have permission to access this web app." code = 401 diff --git a/api/controllers/web/feature.py b/api/controllers/web/feature.py index ce841a8814..0563ed2238 100644 --- a/api/controllers/web/feature.py +++ b/api/controllers/web/feature.py @@ -1,4 +1,4 @@ -from flask_restful import Resource # type: ignore +from flask_restful import Resource from controllers.web import api from services.feature_service import FeatureService diff --git a/api/controllers/web/files.py b/api/controllers/web/files.py index 1d4474015a..df06a73a85 100644 --- a/api/controllers/web/files.py +++ b/api/controllers/web/files.py @@ -1,5 +1,5 @@ from flask import request -from flask_restful import marshal_with # type: ignore +from flask_restful import marshal_with import services from controllers.common.errors import FilenameNotExistsError diff --git a/api/controllers/web/forgot_password.py b/api/controllers/web/forgot_password.py new file mode 100644 index 0000000000..0da8d65efc --- /dev/null +++ b/api/controllers/web/forgot_password.py @@ -0,0 +1,147 @@ +import base64 +import secrets + +from flask import request +from flask_restful import Resource, reqparse +from sqlalchemy import select +from sqlalchemy.orm import Session + +from controllers.console.auth.error import ( + EmailCodeError, + EmailPasswordResetLimitError, + InvalidEmailError, + InvalidTokenError, + PasswordMismatchError, +) +from controllers.console.error import AccountNotFound, EmailSendIpLimitError +from controllers.console.wraps import email_password_login_enabled, only_edition_enterprise, setup_required +from controllers.web import api +from extensions.ext_database import db +from libs.helper import email, extract_remote_ip +from libs.password import hash_password, valid_password +from models.account import Account +from services.account_service import AccountService + + +class ForgotPasswordSendEmailApi(Resource): + @only_edition_enterprise + @setup_required + @email_password_login_enabled + def post(self): + parser = reqparse.RequestParser() + parser.add_argument("email", type=email, required=True, location="json") + parser.add_argument("language", type=str, required=False, location="json") + args = parser.parse_args() + + ip_address = extract_remote_ip(request) + if AccountService.is_email_send_ip_limit(ip_address): + raise EmailSendIpLimitError() + + if args["language"] is not None and args["language"] == "zh-Hans": + language = "zh-Hans" + else: + language = "en-US" + + with Session(db.engine) as session: + account = session.execute(select(Account).filter_by(email=args["email"])).scalar_one_or_none() + token = None + if account is None: + raise AccountNotFound() + else: + token = AccountService.send_reset_password_email(account=account, email=args["email"], language=language) + + return {"result": "success", "data": token} + + +class ForgotPasswordCheckApi(Resource): + @only_edition_enterprise + @setup_required + @email_password_login_enabled + def post(self): + parser = reqparse.RequestParser() + parser.add_argument("email", type=str, required=True, location="json") + parser.add_argument("code", type=str, required=True, location="json") + parser.add_argument("token", type=str, required=True, nullable=False, location="json") + args = parser.parse_args() + + user_email = args["email"] + + is_forgot_password_error_rate_limit = AccountService.is_forgot_password_error_rate_limit(args["email"]) + if is_forgot_password_error_rate_limit: + raise EmailPasswordResetLimitError() + + token_data = AccountService.get_reset_password_data(args["token"]) + if token_data is None: + raise InvalidTokenError() + + if user_email != token_data.get("email"): + raise InvalidEmailError() + + if args["code"] != token_data.get("code"): + AccountService.add_forgot_password_error_rate_limit(args["email"]) + raise EmailCodeError() + + # Verified, revoke the first token + AccountService.revoke_reset_password_token(args["token"]) + + # Refresh token data by generating a new token + _, new_token = AccountService.generate_reset_password_token( + user_email, code=args["code"], additional_data={"phase": "reset"} + ) + + AccountService.reset_forgot_password_error_rate_limit(args["email"]) + return {"is_valid": True, "email": token_data.get("email"), "token": new_token} + + +class ForgotPasswordResetApi(Resource): + @only_edition_enterprise + @setup_required + @email_password_login_enabled + def post(self): + parser = reqparse.RequestParser() + parser.add_argument("token", type=str, required=True, nullable=False, location="json") + parser.add_argument("new_password", type=valid_password, required=True, nullable=False, location="json") + parser.add_argument("password_confirm", type=valid_password, required=True, nullable=False, location="json") + args = parser.parse_args() + + # Validate passwords match + if args["new_password"] != args["password_confirm"]: + raise PasswordMismatchError() + + # Validate token and get reset data + reset_data = AccountService.get_reset_password_data(args["token"]) + if not reset_data: + raise InvalidTokenError() + # Must use token in reset phase + if reset_data.get("phase", "") != "reset": + raise InvalidTokenError() + + # Revoke token to prevent reuse + AccountService.revoke_reset_password_token(args["token"]) + + # Generate secure salt and hash password + salt = secrets.token_bytes(16) + password_hashed = hash_password(args["new_password"], salt) + + email = reset_data.get("email", "") + + with Session(db.engine) as session: + account = session.execute(select(Account).filter_by(email=email)).scalar_one_or_none() + + if account: + self._update_existing_account(account, password_hashed, salt, session) + else: + raise AccountNotFound() + + return {"result": "success"} + + def _update_existing_account(self, account, password_hashed, salt, session): + # Update existing account credentials + account.password = base64.b64encode(password_hashed).decode() + account.password_salt = base64.b64encode(salt).decode() + session.commit() + + +api.add_resource(ForgotPasswordSendEmailApi, "/forgot-password") +api.add_resource(ForgotPasswordCheckApi, "/forgot-password/validity") +api.add_resource(ForgotPasswordResetApi, "/forgot-password/resets") diff --git a/api/controllers/web/login.py b/api/controllers/web/login.py new file mode 100644 index 0000000000..01c4f4a262 --- /dev/null +++ b/api/controllers/web/login.py @@ -0,0 +1,108 @@ +from flask_restful import Resource, reqparse +from jwt import InvalidTokenError # type: ignore + +import services +from controllers.console.auth.error import EmailCodeError, EmailOrPasswordMismatchError, InvalidEmailError +from controllers.console.error import AccountBannedError, AccountNotFound +from controllers.console.wraps import only_edition_enterprise, setup_required +from controllers.web import api +from libs.helper import email +from libs.password import valid_password +from services.account_service import AccountService +from services.webapp_auth_service import WebAppAuthService + + +class LoginApi(Resource): + """Resource for web app email/password login.""" + + @setup_required + @only_edition_enterprise + def post(self): + """Authenticate user and login.""" + parser = reqparse.RequestParser() + parser.add_argument("email", type=email, required=True, location="json") + parser.add_argument("password", type=valid_password, required=True, location="json") + args = parser.parse_args() + + try: + account = WebAppAuthService.authenticate(args["email"], args["password"]) + except services.errors.account.AccountLoginError: + raise AccountBannedError() + except services.errors.account.AccountPasswordError: + raise EmailOrPasswordMismatchError() + except services.errors.account.AccountNotFoundError: + raise AccountNotFound() + + token = WebAppAuthService.login(account=account) + return {"result": "success", "data": {"access_token": token}} + + +# class LogoutApi(Resource): +# @setup_required +# def get(self): +# account = cast(Account, flask_login.current_user) +# if isinstance(account, flask_login.AnonymousUserMixin): +# return {"result": "success"} +# flask_login.logout_user() +# return {"result": "success"} + + +class EmailCodeLoginSendEmailApi(Resource): + @setup_required + @only_edition_enterprise + def post(self): + parser = reqparse.RequestParser() + parser.add_argument("email", type=email, required=True, location="json") + parser.add_argument("language", type=str, required=False, location="json") + args = parser.parse_args() + + if args["language"] is not None and args["language"] == "zh-Hans": + language = "zh-Hans" + else: + language = "en-US" + + account = WebAppAuthService.get_user_through_email(args["email"]) + if account is None: + raise AccountNotFound() + else: + token = WebAppAuthService.send_email_code_login_email(account=account, language=language) + + return {"result": "success", "data": token} + + +class EmailCodeLoginApi(Resource): + @setup_required + @only_edition_enterprise + def post(self): + parser = reqparse.RequestParser() + parser.add_argument("email", type=str, required=True, location="json") + parser.add_argument("code", type=str, required=True, location="json") + parser.add_argument("token", type=str, required=True, location="json") + args = parser.parse_args() + + user_email = args["email"] + + token_data = WebAppAuthService.get_email_code_login_data(args["token"]) + if token_data is None: + raise InvalidTokenError() + + if token_data["email"] != args["email"]: + raise InvalidEmailError() + + if token_data["code"] != args["code"]: + raise EmailCodeError() + + WebAppAuthService.revoke_email_code_login_token(args["token"]) + account = WebAppAuthService.get_user_through_email(user_email) + if not account: + raise AccountNotFound() + + token = WebAppAuthService.login(account=account) + AccountService.reset_login_error_rate_limit(args["email"]) + return {"result": "success", "data": {"access_token": token}} + + +api.add_resource(LoginApi, "/login") +# api.add_resource(LogoutApi, "/logout") +api.add_resource(EmailCodeLoginSendEmailApi, "/email-code-login") +api.add_resource(EmailCodeLoginApi, "/email-code-login/validity") diff --git a/api/controllers/web/message.py b/api/controllers/web/message.py index 17e9a3990f..f2e1873601 100644 --- a/api/controllers/web/message.py +++ b/api/controllers/web/message.py @@ -1,7 +1,7 @@ import logging -from flask_restful import fields, marshal_with, reqparse # type: ignore -from flask_restful.inputs import int_range # type: ignore +from flask_restful import fields, marshal_with, reqparse +from flask_restful.inputs import int_range from werkzeug.exceptions import InternalServerError, NotFound import services diff --git a/api/controllers/web/passport.py b/api/controllers/web/passport.py index e30998c803..9d229185f3 100644 --- a/api/controllers/web/passport.py +++ b/api/controllers/web/passport.py @@ -1,16 +1,19 @@ import uuid +from datetime import UTC, datetime, timedelta from flask import request -from flask_restful import Resource # type: ignore +from flask_restful import Resource from werkzeug.exceptions import NotFound, Unauthorized +from configs import dify_config from controllers.web import api -from controllers.web.error import WebSSOAuthRequiredError +from controllers.web.error import WebAppAuthRequiredError from extensions.ext_database import db from libs.passport import PassportService from models.model import App, EndUser, Site from services.enterprise.enterprise_service import EnterpriseService from services.feature_service import FeatureService +from services.webapp_auth_service import WebAppAuthService, WebAppAuthType class PassportResource(Resource): @@ -20,14 +23,23 @@ class PassportResource(Resource): system_features = FeatureService.get_system_features() app_code = request.headers.get("X-App-Code") user_id = request.args.get("user_id") + web_app_access_token = request.args.get("web_app_access_token") if app_code is None: raise Unauthorized("X-App-Code header is missing.") - if system_features.sso_enforced_for_web: - app_web_sso_enabled = EnterpriseService.get_app_web_sso_enabled(app_code).get("enabled", False) - if app_web_sso_enabled: - raise WebSSOAuthRequiredError() + # exchange token for enterprise logined web user + enterprise_user_decoded = decode_enterprise_webapp_user_id(web_app_access_token) + if enterprise_user_decoded: + # a web user has already logged in, exchange a token for this app without redirecting to the login page + return exchange_token_for_existing_web_user( + app_code=app_code, enterprise_user_decoded=enterprise_user_decoded + ) + + if system_features.webapp_auth.enabled: + app_settings = EnterpriseService.WebAppAuth.get_app_access_mode_by_code(app_code=app_code) + if not app_settings or not app_settings.access_mode == "public": + raise WebAppAuthRequiredError() # get site from db and check if it is normal site = db.session.query(Site).filter(Site.code == app_code, Site.status == "normal").first() @@ -84,6 +96,128 @@ class PassportResource(Resource): api.add_resource(PassportResource, "/passport") +def decode_enterprise_webapp_user_id(jwt_token: str | None): + """ + Decode the enterprise user session from the Authorization header. + """ + if not jwt_token: + return None + + decoded = PassportService().verify(jwt_token) + source = decoded.get("token_source") + if not source or source != "webapp_login_token": + raise Unauthorized("Invalid token source. Expected 'webapp_login_token'.") + return decoded + + +def exchange_token_for_existing_web_user(app_code: str, enterprise_user_decoded: dict): + """ + Exchange a token for an existing web user session. + """ + user_id = enterprise_user_decoded.get("user_id") + end_user_id = enterprise_user_decoded.get("end_user_id") + session_id = enterprise_user_decoded.get("session_id") + user_auth_type = enterprise_user_decoded.get("auth_type") + if not user_auth_type: + raise Unauthorized("Missing auth_type in the token.") + + site = db.session.query(Site).filter(Site.code == app_code, Site.status == "normal").first() + if not site: + raise NotFound() + + app_model = db.session.query(App).filter(App.id == site.app_id).first() + if not app_model or app_model.status != "normal" or not app_model.enable_site: + raise NotFound() + + app_auth_type = WebAppAuthService.get_app_auth_type(app_code=app_code) + + if app_auth_type == WebAppAuthType.PUBLIC: + return _exchange_for_public_app_token(app_model, site, enterprise_user_decoded) + elif app_auth_type == WebAppAuthType.EXTERNAL and user_auth_type != "external": + raise WebAppAuthRequiredError("Please login as external user.") + elif app_auth_type == WebAppAuthType.INTERNAL and user_auth_type != "internal": + raise WebAppAuthRequiredError("Please login as internal user.") + + end_user = None + if end_user_id: + end_user = db.session.query(EndUser).filter(EndUser.id == end_user_id).first() + if session_id: + end_user = ( + db.session.query(EndUser) + .filter( + EndUser.session_id == session_id, + EndUser.tenant_id == app_model.tenant_id, + EndUser.app_id == app_model.id, + ) + .first() + ) + if not end_user: + if not session_id: + raise NotFound("Missing session_id for existing web user.") + end_user = EndUser( + tenant_id=app_model.tenant_id, + app_id=app_model.id, + type="browser", + is_anonymous=True, + session_id=session_id, + ) + db.session.add(end_user) + db.session.commit() + exp_dt = datetime.now(UTC) + timedelta(hours=dify_config.ACCESS_TOKEN_EXPIRE_MINUTES * 24) + exp = int(exp_dt.timestamp()) + payload = { + "iss": site.id, + "sub": "Web API Passport", + "app_id": site.app_id, + "app_code": site.code, + "user_id": user_id, + "end_user_id": end_user.id, + "auth_type": user_auth_type, + "granted_at": int(datetime.now(UTC).timestamp()), + "token_source": "webapp", + "exp": exp, + } + token: str = PassportService().issue(payload) + return { + "access_token": token, + } + + +def _exchange_for_public_app_token(app_model, site, token_decoded): + user_id = token_decoded.get("user_id") + end_user = None + if user_id: + end_user = ( + db.session.query(EndUser).filter(EndUser.app_id == app_model.id, EndUser.session_id == user_id).first() + ) + + if not end_user: + end_user = EndUser( + tenant_id=app_model.tenant_id, + app_id=app_model.id, + type="browser", + is_anonymous=True, + session_id=generate_session_id(), + ) + + db.session.add(end_user) + db.session.commit() + + payload = { + "iss": site.app_id, + "sub": "Web API Passport", + "app_id": site.app_id, + "app_code": site.code, + "end_user_id": end_user.id, + } + + tk = PassportService().issue(payload) + + return { + "access_token": tk, + } + + def generate_session_id(): """ Generate a unique session ID. diff --git a/api/controllers/web/remote_files.py b/api/controllers/web/remote_files.py index d559ab8e07..ae68df6bdc 100644 --- a/api/controllers/web/remote_files.py +++ b/api/controllers/web/remote_files.py @@ -1,7 +1,7 @@ import urllib.parse import httpx -from flask_restful import marshal_with, reqparse # type: ignore +from flask_restful import marshal_with, reqparse import services from controllers.common import helpers diff --git a/api/controllers/web/saved_message.py b/api/controllers/web/saved_message.py index 6a9b818907..d7188ef0b3 100644 --- a/api/controllers/web/saved_message.py +++ b/api/controllers/web/saved_message.py @@ -1,5 +1,5 @@ -from flask_restful import fields, marshal_with, reqparse # type: ignore -from flask_restful.inputs import int_range # type: ignore +from flask_restful import fields, marshal_with, reqparse +from flask_restful.inputs import int_range from werkzeug.exceptions import NotFound from controllers.web import api @@ -67,7 +67,7 @@ class SavedMessageApi(WebApiResource): SavedMessageService.delete(app_model, end_user, message_id) - return {"result": "success"} + return {"result": "success"}, 204 api.add_resource(SavedMessageListApi, "/saved-messages") diff --git a/api/controllers/web/site.py b/api/controllers/web/site.py index e68dc7aa4a..0564b15ea3 100644 --- a/api/controllers/web/site.py +++ b/api/controllers/web/site.py @@ -1,4 +1,4 @@ -from flask_restful import fields, marshal_with # type: ignore +from flask_restful import fields, marshal_with from werkzeug.exceptions import Forbidden from configs import dify_config diff --git a/api/controllers/web/workflow.py b/api/controllers/web/workflow.py index d2e183be78..590fd3f2c7 100644 --- a/api/controllers/web/workflow.py +++ b/api/controllers/web/workflow.py @@ -1,6 +1,6 @@ import logging -from flask_restful import reqparse # type: ignore +from flask_restful import reqparse from werkzeug.exceptions import InternalServerError from controllers.web import api diff --git a/api/controllers/web/wraps.py b/api/controllers/web/wraps.py index 1b4d263bee..154bddfc5c 100644 --- a/api/controllers/web/wraps.py +++ b/api/controllers/web/wraps.py @@ -1,15 +1,17 @@ +from datetime import UTC, datetime from functools import wraps from flask import request -from flask_restful import Resource # type: ignore +from flask_restful import Resource from werkzeug.exceptions import BadRequest, NotFound, Unauthorized -from controllers.web.error import WebSSOAuthRequiredError +from controllers.web.error import WebAppAuthAccessDeniedError, WebAppAuthRequiredError from extensions.ext_database import db from libs.passport import PassportService from models.model import App, EndUser, Site -from services.enterprise.enterprise_service import EnterpriseService +from services.enterprise.enterprise_service import EnterpriseService, WebAppSettings from services.feature_service import FeatureService +from services.webapp_auth_service import WebAppAuthService def validate_jwt_token(view=None): @@ -29,7 +31,7 @@ def validate_jwt_token(view=None): def decode_jwt_token(): system_features = FeatureService.get_system_features() - app_code = request.headers.get("X-App-Code") + app_code = str(request.headers.get("X-App-Code")) try: auth_header = request.headers.get("Authorization") if auth_header is None: @@ -45,7 +47,8 @@ def decode_jwt_token(): raise Unauthorized("Invalid Authorization header format. Expected 'Bearer ' format.") decoded = PassportService().verify(tk) app_code = decoded.get("app_code") - app_model = db.session.query(App).filter(App.id == decoded["app_id"]).first() + app_id = decoded.get("app_id") + app_model = db.session.query(App).filter(App.id == app_id).first() site = db.session.query(Site).filter(Site.code == app_code).first() if not app_model: raise NotFound() @@ -53,39 +56,90 @@ def decode_jwt_token(): raise BadRequest("Site URL is no longer valid.") if app_model.enable_site is False: raise BadRequest("Site is disabled.") - end_user = db.session.query(EndUser).filter(EndUser.id == decoded["end_user_id"]).first() + end_user_id = decoded.get("end_user_id") + end_user = db.session.query(EndUser).filter(EndUser.id == end_user_id).first() if not end_user: raise NotFound() - _validate_web_sso_token(decoded, system_features, app_code) + # for enterprise webapp auth + app_web_auth_enabled = False + webapp_settings = None + if system_features.webapp_auth.enabled: + webapp_settings = EnterpriseService.WebAppAuth.get_app_access_mode_by_code(app_code=app_code) + if not webapp_settings: + raise NotFound("Web app settings not found.") + app_web_auth_enabled = webapp_settings.access_mode != "public" + + _validate_webapp_token(decoded, app_web_auth_enabled, system_features.webapp_auth.enabled) + _validate_user_accessibility( + decoded, app_code, app_web_auth_enabled, system_features.webapp_auth.enabled, webapp_settings + ) return app_model, end_user except Unauthorized as e: - if system_features.sso_enforced_for_web: - app_web_sso_enabled = EnterpriseService.get_app_web_sso_enabled(app_code).get("enabled", False) - if app_web_sso_enabled: - raise WebSSOAuthRequiredError() + if system_features.webapp_auth.enabled: + if not app_code: + raise Unauthorized("Please re-login to access the web app.") + app_web_auth_enabled = ( + EnterpriseService.WebAppAuth.get_app_access_mode_by_code(app_code=str(app_code)).access_mode != "public" + ) + if app_web_auth_enabled: + raise WebAppAuthRequiredError() raise Unauthorized(e.description) -def _validate_web_sso_token(decoded, system_features, app_code): - app_web_sso_enabled = False - - # Check if SSO is enforced for web, and if the token source is not SSO, raise an error and redirect to SSO login - if system_features.sso_enforced_for_web: - app_web_sso_enabled = EnterpriseService.get_app_web_sso_enabled(app_code).get("enabled", False) - if app_web_sso_enabled: - source = decoded.get("token_source") - if not source or source != "sso": - raise WebSSOAuthRequiredError() +def _validate_webapp_token(decoded, app_web_auth_enabled: bool, system_webapp_auth_enabled: bool): + # Check if authentication is enforced for web app, and if the token source is not webapp, + # raise an error and redirect to login + if system_webapp_auth_enabled and app_web_auth_enabled: + source = decoded.get("token_source") + if not source or source != "webapp": + raise WebAppAuthRequiredError() - # Check if SSO is not enforced for web, and if the token source is SSO, + # Check if authentication is not enforced for web, and if the token source is webapp, # raise an error and redirect to normal passport login - if not system_features.sso_enforced_for_web or not app_web_sso_enabled: + if not system_webapp_auth_enabled or not app_web_auth_enabled: source = decoded.get("token_source") - if source and source == "sso": - raise Unauthorized("sso token expired.") + if source and source == "webapp": + raise Unauthorized("webapp token expired.") + + +def _validate_user_accessibility( + decoded, + app_code, + app_web_auth_enabled: bool, + system_webapp_auth_enabled: bool, + webapp_settings: WebAppSettings | None, +): + if system_webapp_auth_enabled and app_web_auth_enabled: + # Check if the user is allowed to access the web app + user_id = decoded.get("user_id") + if not user_id: + raise WebAppAuthRequiredError() + + if not webapp_settings: + raise WebAppAuthRequiredError("Web app settings not found.") + + if WebAppAuthService.is_app_require_permission_check(access_mode=webapp_settings.access_mode): + if not EnterpriseService.WebAppAuth.is_user_allowed_to_access_webapp(user_id, app_code=app_code): + raise WebAppAuthAccessDeniedError() + + auth_type = decoded.get("auth_type") + granted_at = decoded.get("granted_at") + if not auth_type: + raise WebAppAuthAccessDeniedError("Missing auth_type in the token.") + if not granted_at: + raise WebAppAuthAccessDeniedError("Missing granted_at in the token.") + # check if sso has been updated + if auth_type == "external": + last_update_time = EnterpriseService.get_app_sso_settings_last_update_time() + if granted_at and datetime.fromtimestamp(granted_at, tz=UTC) < last_update_time: + raise WebAppAuthAccessDeniedError("SSO settings have been updated. Please re-login.") + elif auth_type == "internal": + last_update_time = EnterpriseService.get_workspace_sso_settings_last_update_time() + if granted_at and datetime.fromtimestamp(granted_at, tz=UTC) < last_update_time: + raise WebAppAuthAccessDeniedError("SSO settings have been updated. Please re-login.") class WebApiResource(Resource): diff --git a/api/core/agent/base_agent_runner.py b/api/core/agent/base_agent_runner.py index e648613605..6998e4d29a 100644 --- a/api/core/agent/base_agent_runner.py +++ b/api/core/agent/base_agent_runner.py @@ -91,6 +91,8 @@ class BaseAgentRunner(AppRunner): return_resource=app_config.additional_features.show_retrieve_source, invoke_from=application_generate_entity.invoke_from, hit_callback=hit_callback, + user_id=user_id, + inputs=cast(dict, application_generate_entity.inputs), ) # get how many agent thoughts have been created self.agent_thought_count = ( diff --git a/api/core/agent/cot_agent_runner.py b/api/core/agent/cot_agent_runner.py index feb8abf6ef..4979f63432 100644 --- a/api/core/agent/cot_agent_runner.py +++ b/api/core/agent/cot_agent_runner.py @@ -63,7 +63,7 @@ class CotAgentRunner(BaseAgentRunner, ABC): self._instruction = self._fill_in_inputs_from_external_data_tools(instruction, inputs) iteration_step = 1 - max_iteration_steps = min(app_config.agent.max_iteration if app_config.agent else 5, 5) + 1 + max_iteration_steps = min(app_config.agent.max_iteration, 99) + 1 # convert tools into ModelRuntime Tool format tool_instances, prompt_messages_tools = self._init_prompt_tools() @@ -80,6 +80,7 @@ class CotAgentRunner(BaseAgentRunner, ABC): llm_usage = final_llm_usage_dict["usage"] llm_usage.prompt_tokens += usage.prompt_tokens llm_usage.completion_tokens += usage.completion_tokens + llm_usage.total_tokens += usage.total_tokens llm_usage.prompt_price += usage.prompt_price llm_usage.completion_price += usage.completion_price llm_usage.total_price += usage.total_price diff --git a/api/core/agent/entities.py b/api/core/agent/entities.py index e68b4f2356..143a3a51aa 100644 --- a/api/core/agent/entities.py +++ b/api/core/agent/entities.py @@ -82,7 +82,7 @@ class AgentEntity(BaseModel): strategy: Strategy prompt: Optional[AgentPromptEntity] = None tools: Optional[list[AgentToolEntity]] = None - max_iteration: int = 5 + max_iteration: int = 10 class AgentInvokeMessage(ToolInvokeMessage): diff --git a/api/core/agent/fc_agent_runner.py b/api/core/agent/fc_agent_runner.py index a1110e7709..5491689ece 100644 --- a/api/core/agent/fc_agent_runner.py +++ b/api/core/agent/fc_agent_runner.py @@ -48,7 +48,7 @@ class FunctionCallAgentRunner(BaseAgentRunner): assert app_config.agent iteration_step = 1 - max_iteration_steps = min(app_config.agent.max_iteration, 5) + 1 + max_iteration_steps = min(app_config.agent.max_iteration, 99) + 1 # continue to run until there is not any tool call function_call_state = True @@ -65,6 +65,7 @@ class FunctionCallAgentRunner(BaseAgentRunner): llm_usage = final_llm_usage_dict["usage"] llm_usage.prompt_tokens += usage.prompt_tokens llm_usage.completion_tokens += usage.completion_tokens + llm_usage.total_tokens += usage.total_tokens llm_usage.prompt_price += usage.prompt_price llm_usage.completion_price += usage.completion_price llm_usage.total_price += usage.total_price diff --git a/api/core/agent/prompt/template.py b/api/core/agent/prompt/template.py index ef64fd29fc..f5ba2119f4 100644 --- a/api/core/agent/prompt/template.py +++ b/api/core/agent/prompt/template.py @@ -1,4 +1,4 @@ -ENGLISH_REACT_COMPLETION_PROMPT_TEMPLATES = """Respond to the human as helpfully and accurately as possible. +ENGLISH_REACT_COMPLETION_PROMPT_TEMPLATES = """Respond to the human as helpfully and accurately as possible. {{instruction}} @@ -47,7 +47,7 @@ Thought:""" # noqa: E501 ENGLISH_REACT_COMPLETION_AGENT_SCRATCHPAD_TEMPLATES = """Observation: {{observation}} Thought:""" -ENGLISH_REACT_CHAT_PROMPT_TEMPLATES = """Respond to the human as helpfully and accurately as possible. +ENGLISH_REACT_CHAT_PROMPT_TEMPLATES = """Respond to the human as helpfully and accurately as possible. {{instruction}} diff --git a/api/core/agent/strategy/plugin.py b/api/core/agent/strategy/plugin.py index a4b25f46e6..79b074cf95 100644 --- a/api/core/agent/strategy/plugin.py +++ b/api/core/agent/strategy/plugin.py @@ -4,7 +4,7 @@ from typing import Any, Optional from core.agent.entities import AgentInvokeMessage from core.agent.plugin_entities import AgentStrategyEntity, AgentStrategyParameter from core.agent.strategy.base import BaseAgentStrategy -from core.plugin.manager.agent import PluginAgentManager +from core.plugin.impl.agent import PluginAgentClient from core.plugin.utils.converter import convert_parameters_to_plugin_format @@ -42,7 +42,7 @@ class PluginAgentStrategy(BaseAgentStrategy): """ Invoke the agent strategy. """ - manager = PluginAgentManager() + manager = PluginAgentClient() initialized_params = self.initialize_parameters(params) params = convert_parameters_to_plugin_format(initialized_params) diff --git a/api/core/app/app_config/easy_ui_based_app/agent/manager.py b/api/core/app/app_config/easy_ui_based_app/agent/manager.py index f503543d7b..590b944c0d 100644 --- a/api/core/app/app_config/easy_ui_based_app/agent/manager.py +++ b/api/core/app/app_config/easy_ui_based_app/agent/manager.py @@ -75,7 +75,7 @@ class AgentConfigManager: strategy=strategy, prompt=agent_prompt_entity, tools=agent_tools, - max_iteration=agent_dict.get("max_iteration", 5), + max_iteration=agent_dict.get("max_iteration", 10), ) return None diff --git a/api/core/app/app_config/easy_ui_based_app/model_config/converter.py b/api/core/app/app_config/easy_ui_based_app/model_config/converter.py index 5beb09c2aa..5b5eefe315 100644 --- a/api/core/app/app_config/easy_ui_based_app/model_config/converter.py +++ b/api/core/app/app_config/easy_ui_based_app/model_config/converter.py @@ -70,7 +70,7 @@ class ModelConfigConverter: if not model_mode: model_mode = LLMMode.CHAT.value if model_schema and model_schema.model_properties.get(ModelPropertyKey.MODE): - model_mode = LLMMode.value_of(model_schema.model_properties[ModelPropertyKey.MODE]).value + model_mode = LLMMode(model_schema.model_properties[ModelPropertyKey.MODE]).value if not model_schema: raise ValueError(f"Model {model_name} not exist.") diff --git a/api/core/app/app_config/entities.py b/api/core/app/app_config/entities.py index 8ae52131f2..3f31b1c3d5 100644 --- a/api/core/app/app_config/entities.py +++ b/api/core/app/app_config/entities.py @@ -109,6 +109,7 @@ class VariableEntity(BaseModel): description: str = "" type: VariableEntityType required: bool = False + hide: bool = False max_length: Optional[int] = None options: Sequence[str] = Field(default_factory=list) allowed_file_types: Sequence[FileType] = Field(default_factory=list) diff --git a/api/core/app/apps/advanced_chat/app_generator.py b/api/core/app/apps/advanced_chat/app_generator.py index ef582d28e0..8c85f91d7e 100644 --- a/api/core/app/apps/advanced_chat/app_generator.py +++ b/api/core/app/apps/advanced_chat/app_generator.py @@ -5,8 +5,9 @@ import uuid from collections.abc import Generator, Mapping from typing import Any, Literal, Optional, Union, overload -from flask import Flask, current_app +from flask import Flask, copy_current_request_context, current_app, has_request_context from pydantic import ValidationError +from sqlalchemy.orm import sessionmaker import contexts from configs import dify_config @@ -24,11 +25,14 @@ from core.app.entities.task_entities import ChatbotAppBlockingResponse, ChatbotA from core.model_runtime.errors.invoke import InvokeAuthorizationError from core.ops.ops_trace_manager import TraceQueueManager from core.prompt.utils.get_thread_messages_length import get_thread_messages_length +from core.repositories import SQLAlchemyWorkflowNodeExecutionRepository +from core.repositories.sqlalchemy_workflow_execution_repository import SQLAlchemyWorkflowExecutionRepository +from core.workflow.repositories.workflow_execution_repository import WorkflowExecutionRepository +from core.workflow.repositories.workflow_node_execution_repository import WorkflowNodeExecutionRepository from extensions.ext_database import db from factories import file_factory -from models.account import Account -from models.model import App, Conversation, EndUser, Message -from models.workflow import Workflow +from models import Account, App, Conversation, EndUser, Message, Workflow, WorkflowNodeExecutionTriggeredFrom +from models.enums import WorkflowRunTriggeredFrom from services.conversation_service import ConversationService from services.errors.message import MessageNotExistsError @@ -154,15 +158,39 @@ class AdvancedChatAppGenerator(MessageBasedAppGenerator): trace_manager=trace_manager, workflow_run_id=workflow_run_id, ) - contexts.tenant_id.set(application_generate_entity.app_config.tenant_id) contexts.plugin_tool_providers.set({}) contexts.plugin_tool_providers_lock.set(threading.Lock()) + # Create repositories + # + # Create session factory + session_factory = sessionmaker(bind=db.engine, expire_on_commit=False) + # Create workflow execution(aka workflow run) repository + if invoke_from == InvokeFrom.DEBUGGER: + workflow_triggered_from = WorkflowRunTriggeredFrom.DEBUGGING + else: + workflow_triggered_from = WorkflowRunTriggeredFrom.APP_RUN + workflow_execution_repository = SQLAlchemyWorkflowExecutionRepository( + session_factory=session_factory, + user=user, + app_id=application_generate_entity.app_config.app_id, + triggered_from=workflow_triggered_from, + ) + # Create workflow node execution repository + workflow_node_execution_repository = SQLAlchemyWorkflowNodeExecutionRepository( + session_factory=session_factory, + user=user, + app_id=application_generate_entity.app_config.app_id, + triggered_from=WorkflowNodeExecutionTriggeredFrom.WORKFLOW_RUN, + ) + return self._generate( workflow=workflow, user=user, invoke_from=invoke_from, application_generate_entity=application_generate_entity, + workflow_execution_repository=workflow_execution_repository, + workflow_node_execution_repository=workflow_node_execution_repository, conversation=conversation, stream=streaming, ) @@ -211,15 +239,35 @@ class AdvancedChatAppGenerator(MessageBasedAppGenerator): node_id=node_id, inputs=args["inputs"] ), ) - contexts.tenant_id.set(application_generate_entity.app_config.tenant_id) contexts.plugin_tool_providers.set({}) contexts.plugin_tool_providers_lock.set(threading.Lock()) + # Create repositories + # + # Create session factory + session_factory = sessionmaker(bind=db.engine, expire_on_commit=False) + # Create workflow execution(aka workflow run) repository + workflow_execution_repository = SQLAlchemyWorkflowExecutionRepository( + session_factory=session_factory, + user=user, + app_id=application_generate_entity.app_config.app_id, + triggered_from=WorkflowRunTriggeredFrom.DEBUGGING, + ) + # Create workflow node execution repository + workflow_node_execution_repository = SQLAlchemyWorkflowNodeExecutionRepository( + session_factory=session_factory, + user=user, + app_id=application_generate_entity.app_config.app_id, + triggered_from=WorkflowNodeExecutionTriggeredFrom.SINGLE_STEP, + ) + return self._generate( workflow=workflow, user=user, invoke_from=InvokeFrom.DEBUGGER, application_generate_entity=application_generate_entity, + workflow_execution_repository=workflow_execution_repository, + workflow_node_execution_repository=workflow_node_execution_repository, conversation=None, stream=streaming, ) @@ -266,15 +314,35 @@ class AdvancedChatAppGenerator(MessageBasedAppGenerator): extras={"auto_generate_conversation_name": False}, single_loop_run=AdvancedChatAppGenerateEntity.SingleLoopRunEntity(node_id=node_id, inputs=args["inputs"]), ) - contexts.tenant_id.set(application_generate_entity.app_config.tenant_id) contexts.plugin_tool_providers.set({}) contexts.plugin_tool_providers_lock.set(threading.Lock()) + # Create repositories + # + # Create session factory + session_factory = sessionmaker(bind=db.engine, expire_on_commit=False) + # Create workflow execution(aka workflow run) repository + workflow_execution_repository = SQLAlchemyWorkflowExecutionRepository( + session_factory=session_factory, + user=user, + app_id=application_generate_entity.app_config.app_id, + triggered_from=WorkflowRunTriggeredFrom.DEBUGGING, + ) + # Create workflow node execution repository + workflow_node_execution_repository = SQLAlchemyWorkflowNodeExecutionRepository( + session_factory=session_factory, + user=user, + app_id=application_generate_entity.app_config.app_id, + triggered_from=WorkflowNodeExecutionTriggeredFrom.SINGLE_STEP, + ) + return self._generate( workflow=workflow, user=user, invoke_from=InvokeFrom.DEBUGGER, application_generate_entity=application_generate_entity, + workflow_execution_repository=workflow_execution_repository, + workflow_node_execution_repository=workflow_node_execution_repository, conversation=None, stream=streaming, ) @@ -286,6 +354,8 @@ class AdvancedChatAppGenerator(MessageBasedAppGenerator): user: Union[Account, EndUser], invoke_from: InvokeFrom, application_generate_entity: AdvancedChatAppGenerateEntity, + workflow_execution_repository: WorkflowExecutionRepository, + workflow_node_execution_repository: WorkflowNodeExecutionRepository, conversation: Optional[Conversation] = None, stream: bool = True, ) -> Mapping[str, Any] | Generator[str | Mapping[str, Any], Any, None]: @@ -296,6 +366,7 @@ class AdvancedChatAppGenerator(MessageBasedAppGenerator): :param user: account or end user :param invoke_from: invoke from source :param application_generate_entity: application generate entity + :param workflow_node_execution_repository: repository for workflow node execution :param conversation: conversation :param stream: is stream """ @@ -325,18 +396,23 @@ class AdvancedChatAppGenerator(MessageBasedAppGenerator): message_id=message.id, ) - # new thread - worker_thread = threading.Thread( - target=self._generate_worker, - kwargs={ - "flask_app": current_app._get_current_object(), # type: ignore - "application_generate_entity": application_generate_entity, - "queue_manager": queue_manager, - "conversation_id": conversation.id, - "message_id": message.id, - "context": contextvars.copy_context(), - }, - ) + # new thread with request context and contextvars + context = contextvars.copy_context() + + @copy_current_request_context + def worker_with_context(): + # Run the worker within the copied context + return context.run( + self._generate_worker, + flask_app=current_app._get_current_object(), # type: ignore + application_generate_entity=application_generate_entity, + queue_manager=queue_manager, + conversation_id=conversation.id, + message_id=message.id, + context=context, + ) + + worker_thread = threading.Thread(target=worker_with_context) worker_thread.start() @@ -348,6 +424,8 @@ class AdvancedChatAppGenerator(MessageBasedAppGenerator): conversation=conversation, message=message, user=user, + workflow_execution_repository=workflow_execution_repository, + workflow_node_execution_repository=workflow_node_execution_repository, stream=stream, ) @@ -373,8 +451,22 @@ class AdvancedChatAppGenerator(MessageBasedAppGenerator): """ for var, val in context.items(): var.set(val) + + # FIXME(-LAN-): Save current user before entering new app context + from flask import g + + saved_user = None + if has_request_context() and hasattr(g, "_login_user"): + saved_user = g._login_user + with flask_app.app_context(): try: + # Restore user in new app context + if saved_user is not None: + from flask import g + + g._login_user = saved_user + # get conversation and message conversation = self._get_conversation(conversation_id) message = self._get_message(message_id) @@ -419,6 +511,8 @@ class AdvancedChatAppGenerator(MessageBasedAppGenerator): conversation: Conversation, message: Message, user: Union[Account, EndUser], + workflow_execution_repository: WorkflowExecutionRepository, + workflow_node_execution_repository: WorkflowNodeExecutionRepository, stream: bool = False, ) -> Union[ChatbotAppBlockingResponse, Generator[ChatbotAppStreamResponse, None, None]]: """ @@ -430,6 +524,7 @@ class AdvancedChatAppGenerator(MessageBasedAppGenerator): :param message: message :param user: account or end user :param stream: is stream + :param workflow_node_execution_repository: optional repository for workflow node execution :return: """ # init generate task pipeline @@ -440,8 +535,10 @@ class AdvancedChatAppGenerator(MessageBasedAppGenerator): conversation=conversation, message=message, user=user, - stream=stream, dialogue_count=self._dialogue_count, + workflow_execution_repository=workflow_execution_repository, + workflow_node_execution_repository=workflow_node_execution_repository, + stream=stream, ) try: diff --git a/api/core/app/apps/advanced_chat/app_runner.py b/api/core/app/apps/advanced_chat/app_runner.py index c83e06bf15..d9b3833862 100644 --- a/api/core/app/apps/advanced_chat/app_runner.py +++ b/api/core/app/apps/advanced_chat/app_runner.py @@ -140,7 +140,7 @@ class AdvancedChatAppRunner(WorkflowBasedAppRunner): SystemVariableKey.DIALOGUE_COUNT: self._dialogue_count, SystemVariableKey.APP_ID: app_config.app_id, SystemVariableKey.WORKFLOW_ID: app_config.workflow_id, - SystemVariableKey.WORKFLOW_RUN_ID: self.application_generate_entity.workflow_run_id, + SystemVariableKey.WORKFLOW_EXECUTION_ID: self.application_generate_entity.workflow_run_id, } # init variable pool diff --git a/api/core/app/apps/advanced_chat/generate_task_pipeline.py b/api/core/app/apps/advanced_chat/generate_task_pipeline.py index 3bf6c330db..8c5645bbb7 100644 --- a/api/core/app/apps/advanced_chat/generate_task_pipeline.py +++ b/api/core/app/apps/advanced_chat/generate_task_pipeline.py @@ -1,4 +1,3 @@ -import json import logging import time from collections.abc import Generator, Mapping @@ -9,8 +8,8 @@ from sqlalchemy import select from sqlalchemy.orm import Session from constants.tts_auto_play_timeout import TTS_AUTO_PLAY_TIMEOUT, TTS_AUTO_PLAY_YIELD_CPU_TIME -from core.app.apps.advanced_chat.app_generator_tts_publisher import AppGeneratorTTSPublisher, AudioTrunk from core.app.apps.base_app_queue_manager import AppQueueManager, PublishFrom +from core.app.apps.common.workflow_response_converter import WorkflowResponseConverter from core.app.entities.app_invoke_entities import ( AdvancedChatAppGenerateEntity, InvokeFrom, @@ -57,23 +56,23 @@ from core.app.entities.task_entities import ( WorkflowTaskState, ) from core.app.task_pipeline.based_generate_task_pipeline import BasedGenerateTaskPipeline -from core.app.task_pipeline.message_cycle_manage import MessageCycleManage -from core.app.task_pipeline.workflow_cycle_manage import WorkflowCycleManage +from core.app.task_pipeline.message_cycle_manager import MessageCycleManager +from core.base.tts import AppGeneratorTTSPublisher, AudioTrunk from core.model_runtime.entities.llm_entities import LLMUsage -from core.model_runtime.utils.encoders import jsonable_encoder from core.ops.ops_trace_manager import TraceQueueManager +from core.workflow.entities.workflow_execution import WorkflowExecutionStatus, WorkflowType from core.workflow.enums import SystemVariableKey from core.workflow.graph_engine.entities.graph_runtime_state import GraphRuntimeState from core.workflow.nodes import NodeType +from core.workflow.repositories.workflow_execution_repository import WorkflowExecutionRepository +from core.workflow.repositories.workflow_node_execution_repository import WorkflowNodeExecutionRepository +from core.workflow.workflow_cycle_manager import CycleManagerWorkflowInfo, WorkflowCycleManager from events.message_event import message_was_created from extensions.ext_database import db from models import Conversation, EndUser, Message, MessageFile from models.account import Account -from models.enums import CreatedByRole -from models.workflow import ( - Workflow, - WorkflowRunStatus, -) +from models.enums import CreatorUserRole +from models.workflow import Workflow logger = logging.getLogger(__name__) @@ -93,6 +92,8 @@ class AdvancedChatAppGenerateTaskPipeline: user: Union[Account, EndUser], stream: bool, dialogue_count: int, + workflow_execution_repository: WorkflowExecutionRepository, + workflow_node_execution_repository: WorkflowNodeExecutionRepository, ) -> None: self._base_task_pipeline = BasedGenerateTaskPipeline( application_generate_entity=application_generate_entity, @@ -103,15 +104,15 @@ class AdvancedChatAppGenerateTaskPipeline: if isinstance(user, EndUser): self._user_id = user.id user_session_id = user.session_id - self._created_by_role = CreatedByRole.END_USER + self._created_by_role = CreatorUserRole.END_USER elif isinstance(user, Account): self._user_id = user.id user_session_id = user.id - self._created_by_role = CreatedByRole.ACCOUNT + self._created_by_role = CreatorUserRole.ACCOUNT else: raise NotImplementedError(f"User type not supported: {type(user)}") - self._workflow_cycle_manager = WorkflowCycleManage( + self._workflow_cycle_manager = WorkflowCycleManager( application_generate_entity=application_generate_entity, workflow_system_variables={ SystemVariableKey.QUERY: message.query, @@ -121,12 +122,24 @@ class AdvancedChatAppGenerateTaskPipeline: SystemVariableKey.DIALOGUE_COUNT: dialogue_count, SystemVariableKey.APP_ID: application_generate_entity.app_config.app_id, SystemVariableKey.WORKFLOW_ID: workflow.id, - SystemVariableKey.WORKFLOW_RUN_ID: application_generate_entity.workflow_run_id, + SystemVariableKey.WORKFLOW_EXECUTION_ID: application_generate_entity.workflow_run_id, }, + workflow_info=CycleManagerWorkflowInfo( + workflow_id=workflow.id, + workflow_type=WorkflowType(workflow.type), + version=workflow.version, + graph_data=workflow.graph_dict, + ), + workflow_execution_repository=workflow_execution_repository, + workflow_node_execution_repository=workflow_node_execution_repository, + ) + + self._workflow_response_converter = WorkflowResponseConverter( + application_generate_entity=application_generate_entity, ) self._task_state = WorkflowTaskState() - self._message_cycle_manager = MessageCycleManage( + self._message_cycle_manager = MessageCycleManager( application_generate_entity=application_generate_entity, task_state=self._task_state ) @@ -147,7 +160,7 @@ class AdvancedChatAppGenerateTaskPipeline: :return: """ # start generate conversation name thread - self._conversation_name_generate_thread = self._message_cycle_manager._generate_conversation_name( + self._conversation_name_generate_thread = self._message_cycle_manager.generate_conversation_name( conversation_id=self._conversation_id, query=self._application_generate_entity.query ) @@ -291,19 +304,15 @@ class AdvancedChatAppGenerateTaskPipeline: with Session(db.engine, expire_on_commit=False) as session: # init workflow run - workflow_run = self._workflow_cycle_manager._handle_workflow_run_start( - session=session, - workflow_id=self._workflow_id, - user_id=self._user_id, - created_by_role=self._created_by_role, - ) - self._workflow_run_id = workflow_run.id + workflow_execution = self._workflow_cycle_manager.handle_workflow_run_start() + self._workflow_run_id = workflow_execution.id_ message = self._get_message(session=session) if not message: raise ValueError(f"Message not found: {self._message_id}") - message.workflow_run_id = workflow_run.id - workflow_start_resp = self._workflow_cycle_manager._workflow_start_to_stream_response( - session=session, task_id=self._application_generate_entity.task_id, workflow_run=workflow_run + message.workflow_run_id = workflow_execution.id_ + workflow_start_resp = self._workflow_response_converter.workflow_start_to_stream_response( + task_id=self._application_generate_entity.task_id, + workflow_execution=workflow_execution, ) session.commit() @@ -316,13 +325,10 @@ class AdvancedChatAppGenerateTaskPipeline: raise ValueError("workflow run not initialized.") with Session(db.engine, expire_on_commit=False) as session: - workflow_run = self._workflow_cycle_manager._get_workflow_run( - session=session, workflow_run_id=self._workflow_run_id + workflow_node_execution = self._workflow_cycle_manager.handle_workflow_node_execution_retried( + workflow_execution_id=self._workflow_run_id, event=event ) - workflow_node_execution = self._workflow_cycle_manager._handle_workflow_node_execution_retried( - workflow_run=workflow_run, event=event - ) - node_retry_resp = self._workflow_cycle_manager._workflow_node_retry_to_stream_response( + node_retry_resp = self._workflow_response_converter.workflow_node_retry_to_stream_response( event=event, task_id=self._application_generate_entity.task_id, workflow_node_execution=workflow_node_execution, @@ -335,20 +341,15 @@ class AdvancedChatAppGenerateTaskPipeline: if not self._workflow_run_id: raise ValueError("workflow run not initialized.") - with Session(db.engine, expire_on_commit=False) as session: - workflow_run = self._workflow_cycle_manager._get_workflow_run( - session=session, workflow_run_id=self._workflow_run_id - ) - workflow_node_execution = self._workflow_cycle_manager._handle_node_execution_start( - workflow_run=workflow_run, event=event - ) + workflow_node_execution = self._workflow_cycle_manager.handle_node_execution_start( + workflow_execution_id=self._workflow_run_id, event=event + ) - node_start_resp = self._workflow_cycle_manager._workflow_node_start_to_stream_response( - event=event, - task_id=self._application_generate_entity.task_id, - workflow_node_execution=workflow_node_execution, - ) - session.commit() + node_start_resp = self._workflow_response_converter.workflow_node_start_to_stream_response( + event=event, + task_id=self._application_generate_entity.task_id, + workflow_node_execution=workflow_node_execution, + ) if node_start_resp: yield node_start_resp @@ -356,15 +357,15 @@ class AdvancedChatAppGenerateTaskPipeline: # Record files if it's an answer node or end node if event.node_type in [NodeType.ANSWER, NodeType.END]: self._recorded_files.extend( - self._workflow_cycle_manager._fetch_files_from_node_outputs(event.outputs or {}) + self._workflow_response_converter.fetch_files_from_node_outputs(event.outputs or {}) ) with Session(db.engine, expire_on_commit=False) as session: - workflow_node_execution = self._workflow_cycle_manager._handle_workflow_node_execution_success( + workflow_node_execution = self._workflow_cycle_manager.handle_workflow_node_execution_success( event=event ) - node_finish_resp = self._workflow_cycle_manager._workflow_node_finish_to_stream_response( + node_finish_resp = self._workflow_response_converter.workflow_node_finish_to_stream_response( event=event, task_id=self._application_generate_entity.task_id, workflow_node_execution=workflow_node_execution, @@ -380,11 +381,11 @@ class AdvancedChatAppGenerateTaskPipeline: | QueueNodeInLoopFailedEvent | QueueNodeExceptionEvent, ): - workflow_node_execution = self._workflow_cycle_manager._handle_workflow_node_execution_failed( + workflow_node_execution = self._workflow_cycle_manager.handle_workflow_node_execution_failed( event=event ) - node_finish_resp = self._workflow_cycle_manager._workflow_node_finish_to_stream_response( + node_finish_resp = self._workflow_response_converter.workflow_node_finish_to_stream_response( event=event, task_id=self._application_generate_entity.task_id, workflow_node_execution=workflow_node_execution, @@ -396,132 +397,92 @@ class AdvancedChatAppGenerateTaskPipeline: if not self._workflow_run_id: raise ValueError("workflow run not initialized.") - with Session(db.engine, expire_on_commit=False) as session: - workflow_run = self._workflow_cycle_manager._get_workflow_run( - session=session, workflow_run_id=self._workflow_run_id - ) - parallel_start_resp = ( - self._workflow_cycle_manager._workflow_parallel_branch_start_to_stream_response( - session=session, - task_id=self._application_generate_entity.task_id, - workflow_run=workflow_run, - event=event, - ) + parallel_start_resp = ( + self._workflow_response_converter.workflow_parallel_branch_start_to_stream_response( + task_id=self._application_generate_entity.task_id, + workflow_execution_id=self._workflow_run_id, + event=event, ) + ) yield parallel_start_resp elif isinstance(event, QueueParallelBranchRunSucceededEvent | QueueParallelBranchRunFailedEvent): if not self._workflow_run_id: raise ValueError("workflow run not initialized.") - with Session(db.engine, expire_on_commit=False) as session: - workflow_run = self._workflow_cycle_manager._get_workflow_run( - session=session, workflow_run_id=self._workflow_run_id - ) - parallel_finish_resp = ( - self._workflow_cycle_manager._workflow_parallel_branch_finished_to_stream_response( - session=session, - task_id=self._application_generate_entity.task_id, - workflow_run=workflow_run, - event=event, - ) + parallel_finish_resp = ( + self._workflow_response_converter.workflow_parallel_branch_finished_to_stream_response( + task_id=self._application_generate_entity.task_id, + workflow_execution_id=self._workflow_run_id, + event=event, ) + ) yield parallel_finish_resp elif isinstance(event, QueueIterationStartEvent): if not self._workflow_run_id: raise ValueError("workflow run not initialized.") - with Session(db.engine, expire_on_commit=False) as session: - workflow_run = self._workflow_cycle_manager._get_workflow_run( - session=session, workflow_run_id=self._workflow_run_id - ) - iter_start_resp = self._workflow_cycle_manager._workflow_iteration_start_to_stream_response( - session=session, - task_id=self._application_generate_entity.task_id, - workflow_run=workflow_run, - event=event, - ) + iter_start_resp = self._workflow_response_converter.workflow_iteration_start_to_stream_response( + task_id=self._application_generate_entity.task_id, + workflow_execution_id=self._workflow_run_id, + event=event, + ) yield iter_start_resp elif isinstance(event, QueueIterationNextEvent): if not self._workflow_run_id: raise ValueError("workflow run not initialized.") - with Session(db.engine, expire_on_commit=False) as session: - workflow_run = self._workflow_cycle_manager._get_workflow_run( - session=session, workflow_run_id=self._workflow_run_id - ) - iter_next_resp = self._workflow_cycle_manager._workflow_iteration_next_to_stream_response( - session=session, - task_id=self._application_generate_entity.task_id, - workflow_run=workflow_run, - event=event, - ) + iter_next_resp = self._workflow_response_converter.workflow_iteration_next_to_stream_response( + task_id=self._application_generate_entity.task_id, + workflow_execution_id=self._workflow_run_id, + event=event, + ) yield iter_next_resp elif isinstance(event, QueueIterationCompletedEvent): if not self._workflow_run_id: raise ValueError("workflow run not initialized.") - with Session(db.engine, expire_on_commit=False) as session: - workflow_run = self._workflow_cycle_manager._get_workflow_run( - session=session, workflow_run_id=self._workflow_run_id - ) - iter_finish_resp = self._workflow_cycle_manager._workflow_iteration_completed_to_stream_response( - session=session, - task_id=self._application_generate_entity.task_id, - workflow_run=workflow_run, - event=event, - ) + iter_finish_resp = self._workflow_response_converter.workflow_iteration_completed_to_stream_response( + task_id=self._application_generate_entity.task_id, + workflow_execution_id=self._workflow_run_id, + event=event, + ) yield iter_finish_resp elif isinstance(event, QueueLoopStartEvent): if not self._workflow_run_id: raise ValueError("workflow run not initialized.") - with Session(db.engine, expire_on_commit=False) as session: - workflow_run = self._workflow_cycle_manager._get_workflow_run( - session=session, workflow_run_id=self._workflow_run_id - ) - loop_start_resp = self._workflow_cycle_manager._workflow_loop_start_to_stream_response( - session=session, - task_id=self._application_generate_entity.task_id, - workflow_run=workflow_run, - event=event, - ) + loop_start_resp = self._workflow_response_converter.workflow_loop_start_to_stream_response( + task_id=self._application_generate_entity.task_id, + workflow_execution_id=self._workflow_run_id, + event=event, + ) yield loop_start_resp elif isinstance(event, QueueLoopNextEvent): if not self._workflow_run_id: raise ValueError("workflow run not initialized.") - with Session(db.engine, expire_on_commit=False) as session: - workflow_run = self._workflow_cycle_manager._get_workflow_run( - session=session, workflow_run_id=self._workflow_run_id - ) - loop_next_resp = self._workflow_cycle_manager._workflow_loop_next_to_stream_response( - session=session, - task_id=self._application_generate_entity.task_id, - workflow_run=workflow_run, - event=event, - ) + loop_next_resp = self._workflow_response_converter.workflow_loop_next_to_stream_response( + task_id=self._application_generate_entity.task_id, + workflow_execution_id=self._workflow_run_id, + event=event, + ) yield loop_next_resp elif isinstance(event, QueueLoopCompletedEvent): if not self._workflow_run_id: raise ValueError("workflow run not initialized.") - with Session(db.engine, expire_on_commit=False) as session: - workflow_run = self._workflow_cycle_manager._get_workflow_run( - session=session, workflow_run_id=self._workflow_run_id - ) - loop_finish_resp = self._workflow_cycle_manager._workflow_loop_completed_to_stream_response( - session=session, - task_id=self._application_generate_entity.task_id, - workflow_run=workflow_run, - event=event, - ) + loop_finish_resp = self._workflow_response_converter.workflow_loop_completed_to_stream_response( + task_id=self._application_generate_entity.task_id, + workflow_execution_id=self._workflow_run_id, + event=event, + ) yield loop_finish_resp elif isinstance(event, QueueWorkflowSucceededEvent): @@ -532,10 +493,8 @@ class AdvancedChatAppGenerateTaskPipeline: raise ValueError("workflow run not initialized.") with Session(db.engine, expire_on_commit=False) as session: - workflow_run = self._workflow_cycle_manager._handle_workflow_run_success( - session=session, + workflow_execution = self._workflow_cycle_manager.handle_workflow_run_success( workflow_run_id=self._workflow_run_id, - start_at=graph_runtime_state.start_at, total_tokens=graph_runtime_state.total_tokens, total_steps=graph_runtime_state.node_run_steps, outputs=event.outputs, @@ -543,10 +502,11 @@ class AdvancedChatAppGenerateTaskPipeline: trace_manager=trace_manager, ) - workflow_finish_resp = self._workflow_cycle_manager._workflow_finish_to_stream_response( - session=session, task_id=self._application_generate_entity.task_id, workflow_run=workflow_run + workflow_finish_resp = self._workflow_response_converter.workflow_finish_to_stream_response( + session=session, + task_id=self._application_generate_entity.task_id, + workflow_execution=workflow_execution, ) - session.commit() yield workflow_finish_resp self._base_task_pipeline._queue_manager.publish( @@ -559,10 +519,8 @@ class AdvancedChatAppGenerateTaskPipeline: raise ValueError("graph runtime state not initialized.") with Session(db.engine, expire_on_commit=False) as session: - workflow_run = self._workflow_cycle_manager._handle_workflow_run_partial_success( - session=session, + workflow_execution = self._workflow_cycle_manager.handle_workflow_run_partial_success( workflow_run_id=self._workflow_run_id, - start_at=graph_runtime_state.start_at, total_tokens=graph_runtime_state.total_tokens, total_steps=graph_runtime_state.node_run_steps, outputs=event.outputs, @@ -570,10 +528,11 @@ class AdvancedChatAppGenerateTaskPipeline: conversation_id=None, trace_manager=trace_manager, ) - workflow_finish_resp = self._workflow_cycle_manager._workflow_finish_to_stream_response( - session=session, task_id=self._application_generate_entity.task_id, workflow_run=workflow_run + workflow_finish_resp = self._workflow_response_converter.workflow_finish_to_stream_response( + session=session, + task_id=self._application_generate_entity.task_id, + workflow_execution=workflow_execution, ) - session.commit() yield workflow_finish_resp self._base_task_pipeline._queue_manager.publish( @@ -586,26 +545,25 @@ class AdvancedChatAppGenerateTaskPipeline: raise ValueError("graph runtime state not initialized.") with Session(db.engine, expire_on_commit=False) as session: - workflow_run = self._workflow_cycle_manager._handle_workflow_run_failed( - session=session, + workflow_execution = self._workflow_cycle_manager.handle_workflow_run_failed( workflow_run_id=self._workflow_run_id, - start_at=graph_runtime_state.start_at, total_tokens=graph_runtime_state.total_tokens, total_steps=graph_runtime_state.node_run_steps, - status=WorkflowRunStatus.FAILED, - error=event.error, + status=WorkflowExecutionStatus.FAILED, + error_message=event.error, conversation_id=self._conversation_id, trace_manager=trace_manager, exceptions_count=event.exceptions_count, ) - workflow_finish_resp = self._workflow_cycle_manager._workflow_finish_to_stream_response( - session=session, task_id=self._application_generate_entity.task_id, workflow_run=workflow_run + workflow_finish_resp = self._workflow_response_converter.workflow_finish_to_stream_response( + session=session, + task_id=self._application_generate_entity.task_id, + workflow_execution=workflow_execution, ) - err_event = QueueErrorEvent(error=ValueError(f"Run failed: {workflow_run.error}")) + err_event = QueueErrorEvent(error=ValueError(f"Run failed: {workflow_execution.error_message}")) err = self._base_task_pipeline._handle_error( event=err_event, session=session, message_id=self._message_id ) - session.commit() yield workflow_finish_resp yield self._base_task_pipeline._error_to_stream_response(err) @@ -613,21 +571,19 @@ class AdvancedChatAppGenerateTaskPipeline: elif isinstance(event, QueueStopEvent): if self._workflow_run_id and graph_runtime_state: with Session(db.engine, expire_on_commit=False) as session: - workflow_run = self._workflow_cycle_manager._handle_workflow_run_failed( - session=session, + workflow_execution = self._workflow_cycle_manager.handle_workflow_run_failed( workflow_run_id=self._workflow_run_id, - start_at=graph_runtime_state.start_at, total_tokens=graph_runtime_state.total_tokens, total_steps=graph_runtime_state.node_run_steps, - status=WorkflowRunStatus.STOPPED, - error=event.get_stop_reason(), + status=WorkflowExecutionStatus.STOPPED, + error_message=event.get_stop_reason(), conversation_id=self._conversation_id, trace_manager=trace_manager, ) - workflow_finish_resp = self._workflow_cycle_manager._workflow_finish_to_stream_response( + workflow_finish_resp = self._workflow_response_converter.workflow_finish_to_stream_response( session=session, task_id=self._application_generate_entity.task_id, - workflow_run=workflow_run, + workflow_execution=workflow_execution, ) # Save message self._save_message(session=session, graph_runtime_state=graph_runtime_state) @@ -647,22 +603,18 @@ class AdvancedChatAppGenerateTaskPipeline: yield self._message_end_to_stream_response() break elif isinstance(event, QueueRetrieverResourcesEvent): - self._message_cycle_manager._handle_retriever_resources(event) + self._message_cycle_manager.handle_retriever_resources(event) with Session(db.engine, expire_on_commit=False) as session: message = self._get_message(session=session) - message.message_metadata = ( - json.dumps(jsonable_encoder(self._task_state.metadata)) if self._task_state.metadata else None - ) + message.message_metadata = self._task_state.metadata.model_dump_json() session.commit() elif isinstance(event, QueueAnnotationReplyEvent): - self._message_cycle_manager._handle_annotation_reply(event) + self._message_cycle_manager.handle_annotation_reply(event) with Session(db.engine, expire_on_commit=False) as session: message = self._get_message(session=session) - message.message_metadata = ( - json.dumps(jsonable_encoder(self._task_state.metadata)) if self._task_state.metadata else None - ) + message.message_metadata = self._task_state.metadata.model_dump_json() session.commit() elif isinstance(event, QueueTextChunkEvent): delta_text = event.text @@ -679,12 +631,14 @@ class AdvancedChatAppGenerateTaskPipeline: tts_publisher.publish(queue_message) self._task_state.answer += delta_text - yield self._message_cycle_manager._message_to_stream_response( + yield self._message_cycle_manager.message_to_stream_response( answer=delta_text, message_id=self._message_id, from_variable_selector=event.from_variable_selector ) elif isinstance(event, QueueMessageReplaceEvent): # published by moderation - yield self._message_cycle_manager._message_replace_to_stream_response(answer=event.text) + yield self._message_cycle_manager.message_replace_to_stream_response( + answer=event.text, reason=event.reason + ) elif isinstance(event, QueueAdvancedChatMessageEndEvent): if not graph_runtime_state: raise ValueError("graph runtime state not initialized.") @@ -694,8 +648,9 @@ class AdvancedChatAppGenerateTaskPipeline: ) if output_moderation_answer: self._task_state.answer = output_moderation_answer - yield self._message_cycle_manager._message_replace_to_stream_response( - answer=output_moderation_answer + yield self._message_cycle_manager.message_replace_to_stream_response( + answer=output_moderation_answer, + reason=QueueMessageReplaceEvent.MessageReplaceReason.OUTPUT_MODERATION, ) # Save message @@ -705,7 +660,7 @@ class AdvancedChatAppGenerateTaskPipeline: yield self._message_end_to_stream_response() elif isinstance(event, QueueAgentLogEvent): - yield self._workflow_cycle_manager._handle_agent_log( + yield self._workflow_response_converter.handle_agent_log( task_id=self._application_generate_entity.task_id, event=event ) else: @@ -722,9 +677,7 @@ class AdvancedChatAppGenerateTaskPipeline: message = self._get_message(session=session) message.answer = self._task_state.answer message.provider_response_latency = time.perf_counter() - self._base_task_pipeline._start_at - message.message_metadata = ( - json.dumps(jsonable_encoder(self._task_state.metadata)) if self._task_state.metadata else None - ) + message.message_metadata = self._task_state.metadata.model_dump_json() message_files = [ MessageFile( message_id=message.id, @@ -733,9 +686,9 @@ class AdvancedChatAppGenerateTaskPipeline: url=file["remote_url"], belongs_to="assistant", upload_file_id=file["related_id"], - created_by_role=CreatedByRole.ACCOUNT + created_by_role=CreatorUserRole.ACCOUNT if message.invoke_from in {InvokeFrom.EXPLORE, InvokeFrom.DEBUGGER} - else CreatedByRole.END_USER, + else CreatorUserRole.END_USER, created_by=message.from_account_id or message.from_end_user_id or "", ) for file in self._recorded_files @@ -752,9 +705,9 @@ class AdvancedChatAppGenerateTaskPipeline: message.answer_price_unit = usage.completion_price_unit message.total_price = usage.total_price message.currency = usage.currency - self._task_state.metadata["usage"] = jsonable_encoder(usage) + self._task_state.metadata.usage = usage else: - self._task_state.metadata["usage"] = jsonable_encoder(LLMUsage.empty_usage()) + self._task_state.metadata.usage = LLMUsage.empty_usage() message_was_created.send( message, application_generate_entity=self._application_generate_entity, @@ -765,18 +718,16 @@ class AdvancedChatAppGenerateTaskPipeline: Message end to stream response. :return: """ - extras = {} - if self._task_state.metadata: - extras["metadata"] = self._task_state.metadata.copy() + extras = self._task_state.metadata.model_dump() - if "annotation_reply" in extras["metadata"]: - del extras["metadata"]["annotation_reply"] + if self._task_state.metadata.annotation_reply: + del extras["annotation_reply"] return MessageEndStreamResponse( task_id=self._application_generate_entity.task_id, id=self._message_id, files=self._recorded_files, - metadata=extras.get("metadata", {}), + metadata=extras, ) def _handle_output_moderation_chunk(self, text: str) -> bool: diff --git a/api/core/app/apps/agent_chat/app_generator.py b/api/core/app/apps/agent_chat/app_generator.py index 3ed436c07a..158196f24d 100644 --- a/api/core/app/apps/agent_chat/app_generator.py +++ b/api/core/app/apps/agent_chat/app_generator.py @@ -5,7 +5,7 @@ import uuid from collections.abc import Generator, Mapping from typing import Any, Literal, Union, overload -from flask import Flask, current_app +from flask import Flask, copy_current_request_context, current_app, has_request_context from pydantic import ValidationError from configs import dify_config @@ -179,18 +179,23 @@ class AgentChatAppGenerator(MessageBasedAppGenerator): message_id=message.id, ) - # new thread - worker_thread = threading.Thread( - target=self._generate_worker, - kwargs={ - "flask_app": current_app._get_current_object(), # type: ignore - "context": contextvars.copy_context(), - "application_generate_entity": application_generate_entity, - "queue_manager": queue_manager, - "conversation_id": conversation.id, - "message_id": message.id, - }, - ) + # new thread with request context and contextvars + context = contextvars.copy_context() + + @copy_current_request_context + def worker_with_context(): + # Run the worker within the copied context + return context.run( + self._generate_worker, + flask_app=current_app._get_current_object(), # type: ignore + context=context, + application_generate_entity=application_generate_entity, + queue_manager=queue_manager, + conversation_id=conversation.id, + message_id=message.id, + ) + + worker_thread = threading.Thread(target=worker_with_context) worker_thread.start() @@ -227,8 +232,21 @@ class AgentChatAppGenerator(MessageBasedAppGenerator): for var, val in context.items(): var.set(val) + # FIXME(-LAN-): Save current user before entering new app context + from flask import g + + saved_user = None + if has_request_context() and hasattr(g, "_login_user"): + saved_user = g._login_user + with flask_app.app_context(): try: + # Restore user in new app context + if saved_user is not None: + from flask import g + + g._login_user = saved_user + # get conversation and message conversation = self._get_conversation(conversation_id) message = self._get_message(message_id) diff --git a/api/core/app/apps/base_app_runner.py b/api/core/app/apps/base_app_runner.py index c813dbb9d1..a3f0cf7f9f 100644 --- a/api/core/app/apps/base_app_runner.py +++ b/api/core/app/apps/base_app_runner.py @@ -1,3 +1,4 @@ +import logging import time from collections.abc import Generator, Mapping, Sequence from typing import TYPE_CHECKING, Any, Optional, Union @@ -33,6 +34,8 @@ from models.model import App, AppMode, Message, MessageAnnotation if TYPE_CHECKING: from core.file.models import File +_logger = logging.getLogger(__name__) + class AppRunner: def get_pre_calculate_rest_tokens( @@ -298,7 +301,7 @@ class AppRunner: ) def _handle_invoke_result_stream( - self, invoke_result: Generator, queue_manager: AppQueueManager, agent: bool + self, invoke_result: Generator[LLMResultChunk, None, None], queue_manager: AppQueueManager, agent: bool ) -> None: """ Handle invoke result @@ -317,18 +320,28 @@ class AppRunner: else: queue_manager.publish(QueueAgentMessageEvent(chunk=result), PublishFrom.APPLICATION_MANAGER) - text += result.delta.message.content + message = result.delta.message + if isinstance(message.content, str): + text += message.content + elif isinstance(message.content, list): + for content in message.content: + if not isinstance(content, str): + # TODO(QuantumGhost): Add multimodal output support for easy ui. + _logger.warning("received multimodal output, type=%s", type(content)) + text += content.data + else: + text += content # failback to str if not model: model = result.model if not prompt_messages: - prompt_messages = result.prompt_messages + prompt_messages = list(result.prompt_messages) if result.delta.usage: usage = result.delta.usage - if not usage: + if usage is None: usage = LLMUsage.empty_usage() llm_result = LLMResult( diff --git a/api/core/app/apps/chat/app_generator.py b/api/core/app/apps/chat/app_generator.py index 2d865795d8..a1329cb938 100644 --- a/api/core/app/apps/chat/app_generator.py +++ b/api/core/app/apps/chat/app_generator.py @@ -4,7 +4,7 @@ import uuid from collections.abc import Generator, Mapping from typing import Any, Literal, Union, overload -from flask import Flask, current_app +from flask import Flask, copy_current_request_context, current_app from pydantic import ValidationError from configs import dify_config @@ -170,17 +170,18 @@ class ChatAppGenerator(MessageBasedAppGenerator): message_id=message.id, ) - # new thread - worker_thread = threading.Thread( - target=self._generate_worker, - kwargs={ - "flask_app": current_app._get_current_object(), # type: ignore - "application_generate_entity": application_generate_entity, - "queue_manager": queue_manager, - "conversation_id": conversation.id, - "message_id": message.id, - }, - ) + # new thread with request context + @copy_current_request_context + def worker_with_context(): + return self._generate_worker( + flask_app=current_app._get_current_object(), # type: ignore + application_generate_entity=application_generate_entity, + queue_manager=queue_manager, + conversation_id=conversation.id, + message_id=message.id, + ) + + worker_thread = threading.Thread(target=worker_with_context) worker_thread.start() diff --git a/api/core/app/apps/common/__init__.py b/api/core/app/apps/common/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/api/core/app/apps/common/workflow_response_converter.py b/api/core/app/apps/common/workflow_response_converter.py new file mode 100644 index 0000000000..6f524a5872 --- /dev/null +++ b/api/core/app/apps/common/workflow_response_converter.py @@ -0,0 +1,561 @@ +import time +from collections.abc import Mapping, Sequence +from datetime import UTC, datetime +from typing import Any, Optional, Union, cast + +from sqlalchemy import select +from sqlalchemy.orm import Session + +from core.app.entities.app_invoke_entities import AdvancedChatAppGenerateEntity, WorkflowAppGenerateEntity +from core.app.entities.queue_entities import ( + QueueAgentLogEvent, + QueueIterationCompletedEvent, + QueueIterationNextEvent, + QueueIterationStartEvent, + QueueLoopCompletedEvent, + QueueLoopNextEvent, + QueueLoopStartEvent, + QueueNodeExceptionEvent, + QueueNodeFailedEvent, + QueueNodeInIterationFailedEvent, + QueueNodeInLoopFailedEvent, + QueueNodeRetryEvent, + QueueNodeStartedEvent, + QueueNodeSucceededEvent, + QueueParallelBranchRunFailedEvent, + QueueParallelBranchRunStartedEvent, + QueueParallelBranchRunSucceededEvent, +) +from core.app.entities.task_entities import ( + AgentLogStreamResponse, + IterationNodeCompletedStreamResponse, + IterationNodeNextStreamResponse, + IterationNodeStartStreamResponse, + LoopNodeCompletedStreamResponse, + LoopNodeNextStreamResponse, + LoopNodeStartStreamResponse, + NodeFinishStreamResponse, + NodeRetryStreamResponse, + NodeStartStreamResponse, + ParallelBranchFinishedStreamResponse, + ParallelBranchStartStreamResponse, + WorkflowFinishStreamResponse, + WorkflowStartStreamResponse, +) +from core.file import FILE_MODEL_IDENTITY, File +from core.tools.tool_manager import ToolManager +from core.workflow.entities.workflow_execution import WorkflowExecution +from core.workflow.entities.workflow_node_execution import WorkflowNodeExecution, WorkflowNodeExecutionStatus +from core.workflow.nodes import NodeType +from core.workflow.nodes.tool.entities import ToolNodeData +from models import ( + Account, + CreatorUserRole, + EndUser, + WorkflowRun, +) + + +class WorkflowResponseConverter: + def __init__( + self, + *, + application_generate_entity: Union[AdvancedChatAppGenerateEntity, WorkflowAppGenerateEntity], + ) -> None: + self._application_generate_entity = application_generate_entity + + def workflow_start_to_stream_response( + self, + *, + task_id: str, + workflow_execution: WorkflowExecution, + ) -> WorkflowStartStreamResponse: + return WorkflowStartStreamResponse( + task_id=task_id, + workflow_run_id=workflow_execution.id_, + data=WorkflowStartStreamResponse.Data( + id=workflow_execution.id_, + workflow_id=workflow_execution.workflow_id, + inputs=workflow_execution.inputs, + created_at=int(workflow_execution.started_at.timestamp()), + ), + ) + + def workflow_finish_to_stream_response( + self, + *, + session: Session, + task_id: str, + workflow_execution: WorkflowExecution, + ) -> WorkflowFinishStreamResponse: + created_by = None + workflow_run = session.scalar(select(WorkflowRun).where(WorkflowRun.id == workflow_execution.id_)) + assert workflow_run is not None + if workflow_run.created_by_role == CreatorUserRole.ACCOUNT: + stmt = select(Account).where(Account.id == workflow_run.created_by) + account = session.scalar(stmt) + if account: + created_by = { + "id": account.id, + "name": account.name, + "email": account.email, + } + elif workflow_run.created_by_role == CreatorUserRole.END_USER: + stmt = select(EndUser).where(EndUser.id == workflow_run.created_by) + end_user = session.scalar(stmt) + if end_user: + created_by = { + "id": end_user.id, + "user": end_user.session_id, + } + else: + raise NotImplementedError(f"unknown created_by_role: {workflow_run.created_by_role}") + + # Handle the case where finished_at is None by using current time as default + finished_at_timestamp = ( + int(workflow_execution.finished_at.timestamp()) + if workflow_execution.finished_at + else int(datetime.now(UTC).timestamp()) + ) + + return WorkflowFinishStreamResponse( + task_id=task_id, + workflow_run_id=workflow_execution.id_, + data=WorkflowFinishStreamResponse.Data( + id=workflow_execution.id_, + workflow_id=workflow_execution.workflow_id, + status=workflow_execution.status, + outputs=workflow_execution.outputs, + error=workflow_execution.error_message, + elapsed_time=workflow_execution.elapsed_time, + total_tokens=workflow_execution.total_tokens, + total_steps=workflow_execution.total_steps, + created_by=created_by, + created_at=int(workflow_execution.started_at.timestamp()), + finished_at=finished_at_timestamp, + files=self.fetch_files_from_node_outputs(workflow_execution.outputs), + exceptions_count=workflow_execution.exceptions_count, + ), + ) + + def workflow_node_start_to_stream_response( + self, + *, + event: QueueNodeStartedEvent, + task_id: str, + workflow_node_execution: WorkflowNodeExecution, + ) -> Optional[NodeStartStreamResponse]: + if workflow_node_execution.node_type in {NodeType.ITERATION, NodeType.LOOP}: + return None + if not workflow_node_execution.workflow_execution_id: + return None + + response = NodeStartStreamResponse( + task_id=task_id, + workflow_run_id=workflow_node_execution.workflow_execution_id, + data=NodeStartStreamResponse.Data( + id=workflow_node_execution.id, + node_id=workflow_node_execution.node_id, + node_type=workflow_node_execution.node_type, + title=workflow_node_execution.title, + index=workflow_node_execution.index, + predecessor_node_id=workflow_node_execution.predecessor_node_id, + inputs=workflow_node_execution.inputs, + created_at=int(workflow_node_execution.created_at.timestamp()), + parallel_id=event.parallel_id, + parallel_start_node_id=event.parallel_start_node_id, + parent_parallel_id=event.parent_parallel_id, + parent_parallel_start_node_id=event.parent_parallel_start_node_id, + iteration_id=event.in_iteration_id, + loop_id=event.in_loop_id, + parallel_run_id=event.parallel_mode_run_id, + agent_strategy=event.agent_strategy, + ), + ) + + # extras logic + if event.node_type == NodeType.TOOL: + node_data = cast(ToolNodeData, event.node_data) + response.data.extras["icon"] = ToolManager.get_tool_icon( + tenant_id=self._application_generate_entity.app_config.tenant_id, + provider_type=node_data.provider_type, + provider_id=node_data.provider_id, + ) + + return response + + def workflow_node_finish_to_stream_response( + self, + *, + event: QueueNodeSucceededEvent + | QueueNodeFailedEvent + | QueueNodeInIterationFailedEvent + | QueueNodeInLoopFailedEvent + | QueueNodeExceptionEvent, + task_id: str, + workflow_node_execution: WorkflowNodeExecution, + ) -> Optional[NodeFinishStreamResponse]: + if workflow_node_execution.node_type in {NodeType.ITERATION, NodeType.LOOP}: + return None + if not workflow_node_execution.workflow_execution_id: + return None + if not workflow_node_execution.finished_at: + return None + + return NodeFinishStreamResponse( + task_id=task_id, + workflow_run_id=workflow_node_execution.workflow_execution_id, + data=NodeFinishStreamResponse.Data( + id=workflow_node_execution.id, + node_id=workflow_node_execution.node_id, + node_type=workflow_node_execution.node_type, + index=workflow_node_execution.index, + title=workflow_node_execution.title, + predecessor_node_id=workflow_node_execution.predecessor_node_id, + inputs=workflow_node_execution.inputs, + process_data=workflow_node_execution.process_data, + outputs=workflow_node_execution.outputs, + status=workflow_node_execution.status, + error=workflow_node_execution.error, + elapsed_time=workflow_node_execution.elapsed_time, + execution_metadata=workflow_node_execution.metadata, + created_at=int(workflow_node_execution.created_at.timestamp()), + finished_at=int(workflow_node_execution.finished_at.timestamp()), + files=self.fetch_files_from_node_outputs(workflow_node_execution.outputs or {}), + parallel_id=event.parallel_id, + parallel_start_node_id=event.parallel_start_node_id, + parent_parallel_id=event.parent_parallel_id, + parent_parallel_start_node_id=event.parent_parallel_start_node_id, + iteration_id=event.in_iteration_id, + loop_id=event.in_loop_id, + ), + ) + + def workflow_node_retry_to_stream_response( + self, + *, + event: QueueNodeRetryEvent, + task_id: str, + workflow_node_execution: WorkflowNodeExecution, + ) -> Optional[Union[NodeRetryStreamResponse, NodeFinishStreamResponse]]: + if workflow_node_execution.node_type in {NodeType.ITERATION, NodeType.LOOP}: + return None + if not workflow_node_execution.workflow_execution_id: + return None + if not workflow_node_execution.finished_at: + return None + + return NodeRetryStreamResponse( + task_id=task_id, + workflow_run_id=workflow_node_execution.workflow_execution_id, + data=NodeRetryStreamResponse.Data( + id=workflow_node_execution.id, + node_id=workflow_node_execution.node_id, + node_type=workflow_node_execution.node_type, + index=workflow_node_execution.index, + title=workflow_node_execution.title, + predecessor_node_id=workflow_node_execution.predecessor_node_id, + inputs=workflow_node_execution.inputs, + process_data=workflow_node_execution.process_data, + outputs=workflow_node_execution.outputs, + status=workflow_node_execution.status, + error=workflow_node_execution.error, + elapsed_time=workflow_node_execution.elapsed_time, + execution_metadata=workflow_node_execution.metadata, + created_at=int(workflow_node_execution.created_at.timestamp()), + finished_at=int(workflow_node_execution.finished_at.timestamp()), + files=self.fetch_files_from_node_outputs(workflow_node_execution.outputs or {}), + parallel_id=event.parallel_id, + parallel_start_node_id=event.parallel_start_node_id, + parent_parallel_id=event.parent_parallel_id, + parent_parallel_start_node_id=event.parent_parallel_start_node_id, + iteration_id=event.in_iteration_id, + loop_id=event.in_loop_id, + retry_index=event.retry_index, + ), + ) + + def workflow_parallel_branch_start_to_stream_response( + self, + *, + task_id: str, + workflow_execution_id: str, + event: QueueParallelBranchRunStartedEvent, + ) -> ParallelBranchStartStreamResponse: + return ParallelBranchStartStreamResponse( + task_id=task_id, + workflow_run_id=workflow_execution_id, + data=ParallelBranchStartStreamResponse.Data( + parallel_id=event.parallel_id, + parallel_branch_id=event.parallel_start_node_id, + parent_parallel_id=event.parent_parallel_id, + parent_parallel_start_node_id=event.parent_parallel_start_node_id, + iteration_id=event.in_iteration_id, + loop_id=event.in_loop_id, + created_at=int(time.time()), + ), + ) + + def workflow_parallel_branch_finished_to_stream_response( + self, + *, + task_id: str, + workflow_execution_id: str, + event: QueueParallelBranchRunSucceededEvent | QueueParallelBranchRunFailedEvent, + ) -> ParallelBranchFinishedStreamResponse: + return ParallelBranchFinishedStreamResponse( + task_id=task_id, + workflow_run_id=workflow_execution_id, + data=ParallelBranchFinishedStreamResponse.Data( + parallel_id=event.parallel_id, + parallel_branch_id=event.parallel_start_node_id, + parent_parallel_id=event.parent_parallel_id, + parent_parallel_start_node_id=event.parent_parallel_start_node_id, + iteration_id=event.in_iteration_id, + loop_id=event.in_loop_id, + status="succeeded" if isinstance(event, QueueParallelBranchRunSucceededEvent) else "failed", + error=event.error if isinstance(event, QueueParallelBranchRunFailedEvent) else None, + created_at=int(time.time()), + ), + ) + + def workflow_iteration_start_to_stream_response( + self, + *, + task_id: str, + workflow_execution_id: str, + event: QueueIterationStartEvent, + ) -> IterationNodeStartStreamResponse: + return IterationNodeStartStreamResponse( + task_id=task_id, + workflow_run_id=workflow_execution_id, + data=IterationNodeStartStreamResponse.Data( + id=event.node_id, + node_id=event.node_id, + node_type=event.node_type.value, + title=event.node_data.title, + created_at=int(time.time()), + extras={}, + inputs=event.inputs or {}, + metadata=event.metadata or {}, + parallel_id=event.parallel_id, + parallel_start_node_id=event.parallel_start_node_id, + ), + ) + + def workflow_iteration_next_to_stream_response( + self, + *, + task_id: str, + workflow_execution_id: str, + event: QueueIterationNextEvent, + ) -> IterationNodeNextStreamResponse: + return IterationNodeNextStreamResponse( + task_id=task_id, + workflow_run_id=workflow_execution_id, + data=IterationNodeNextStreamResponse.Data( + id=event.node_id, + node_id=event.node_id, + node_type=event.node_type.value, + title=event.node_data.title, + index=event.index, + pre_iteration_output=event.output, + created_at=int(time.time()), + extras={}, + parallel_id=event.parallel_id, + parallel_start_node_id=event.parallel_start_node_id, + parallel_mode_run_id=event.parallel_mode_run_id, + duration=event.duration, + ), + ) + + def workflow_iteration_completed_to_stream_response( + self, + *, + task_id: str, + workflow_execution_id: str, + event: QueueIterationCompletedEvent, + ) -> IterationNodeCompletedStreamResponse: + return IterationNodeCompletedStreamResponse( + task_id=task_id, + workflow_run_id=workflow_execution_id, + data=IterationNodeCompletedStreamResponse.Data( + id=event.node_id, + node_id=event.node_id, + node_type=event.node_type.value, + title=event.node_data.title, + outputs=event.outputs, + created_at=int(time.time()), + extras={}, + inputs=event.inputs or {}, + status=WorkflowNodeExecutionStatus.SUCCEEDED + if event.error is None + else WorkflowNodeExecutionStatus.FAILED, + error=None, + elapsed_time=(datetime.now(UTC).replace(tzinfo=None) - event.start_at).total_seconds(), + total_tokens=event.metadata.get("total_tokens", 0) if event.metadata else 0, + execution_metadata=event.metadata, + finished_at=int(time.time()), + steps=event.steps, + parallel_id=event.parallel_id, + parallel_start_node_id=event.parallel_start_node_id, + ), + ) + + def workflow_loop_start_to_stream_response( + self, *, task_id: str, workflow_execution_id: str, event: QueueLoopStartEvent + ) -> LoopNodeStartStreamResponse: + return LoopNodeStartStreamResponse( + task_id=task_id, + workflow_run_id=workflow_execution_id, + data=LoopNodeStartStreamResponse.Data( + id=event.node_id, + node_id=event.node_id, + node_type=event.node_type.value, + title=event.node_data.title, + created_at=int(time.time()), + extras={}, + inputs=event.inputs or {}, + metadata=event.metadata or {}, + parallel_id=event.parallel_id, + parallel_start_node_id=event.parallel_start_node_id, + ), + ) + + def workflow_loop_next_to_stream_response( + self, + *, + task_id: str, + workflow_execution_id: str, + event: QueueLoopNextEvent, + ) -> LoopNodeNextStreamResponse: + return LoopNodeNextStreamResponse( + task_id=task_id, + workflow_run_id=workflow_execution_id, + data=LoopNodeNextStreamResponse.Data( + id=event.node_id, + node_id=event.node_id, + node_type=event.node_type.value, + title=event.node_data.title, + index=event.index, + pre_loop_output=event.output, + created_at=int(time.time()), + extras={}, + parallel_id=event.parallel_id, + parallel_start_node_id=event.parallel_start_node_id, + parallel_mode_run_id=event.parallel_mode_run_id, + duration=event.duration, + ), + ) + + def workflow_loop_completed_to_stream_response( + self, + *, + task_id: str, + workflow_execution_id: str, + event: QueueLoopCompletedEvent, + ) -> LoopNodeCompletedStreamResponse: + return LoopNodeCompletedStreamResponse( + task_id=task_id, + workflow_run_id=workflow_execution_id, + data=LoopNodeCompletedStreamResponse.Data( + id=event.node_id, + node_id=event.node_id, + node_type=event.node_type.value, + title=event.node_data.title, + outputs=event.outputs, + created_at=int(time.time()), + extras={}, + inputs=event.inputs or {}, + status=WorkflowNodeExecutionStatus.SUCCEEDED + if event.error is None + else WorkflowNodeExecutionStatus.FAILED, + error=None, + elapsed_time=(datetime.now(UTC).replace(tzinfo=None) - event.start_at).total_seconds(), + total_tokens=event.metadata.get("total_tokens", 0) if event.metadata else 0, + execution_metadata=event.metadata, + finished_at=int(time.time()), + steps=event.steps, + parallel_id=event.parallel_id, + parallel_start_node_id=event.parallel_start_node_id, + ), + ) + + def fetch_files_from_node_outputs(self, outputs_dict: Mapping[str, Any] | None) -> Sequence[Mapping[str, Any]]: + """ + Fetch files from node outputs + :param outputs_dict: node outputs dict + :return: + """ + if not outputs_dict: + return [] + + files = [self._fetch_files_from_variable_value(output_value) for output_value in outputs_dict.values()] + # Remove None + files = [file for file in files if file] + # Flatten list + # Flatten the list of sequences into a single list of mappings + flattened_files = [file for sublist in files if sublist for file in sublist] + + # Convert to tuple to match Sequence type + return tuple(flattened_files) + + def _fetch_files_from_variable_value(self, value: Union[dict, list]) -> Sequence[Mapping[str, Any]]: + """ + Fetch files from variable value + :param value: variable value + :return: + """ + if not value: + return [] + + files = [] + if isinstance(value, list): + for item in value: + file = self._get_file_var_from_value(item) + if file: + files.append(file) + elif isinstance(value, dict): + file = self._get_file_var_from_value(value) + if file: + files.append(file) + + return files + + def _get_file_var_from_value(self, value: Union[dict, list]) -> Mapping[str, Any] | None: + """ + Get file var from value + :param value: variable value + :return: + """ + if not value: + return None + + if isinstance(value, dict) and value.get("dify_model_identity") == FILE_MODEL_IDENTITY: + return value + elif isinstance(value, File): + return value.to_dict() + + return None + + def handle_agent_log(self, task_id: str, event: QueueAgentLogEvent) -> AgentLogStreamResponse: + """ + Handle agent log + :param task_id: task id + :param event: agent log event + :return: + """ + return AgentLogStreamResponse( + task_id=task_id, + data=AgentLogStreamResponse.Data( + node_execution_id=event.node_execution_id, + id=event.id, + parent_id=event.parent_id, + label=event.label, + error=event.error, + status=event.status, + data=event.data, + metadata=event.metadata, + node_id=event.node_id, + ), + ) diff --git a/api/core/app/apps/completion/app_generator.py b/api/core/app/apps/completion/app_generator.py index b1bc412616..adcbaad3ec 100644 --- a/api/core/app/apps/completion/app_generator.py +++ b/api/core/app/apps/completion/app_generator.py @@ -4,7 +4,7 @@ import uuid from collections.abc import Generator, Mapping from typing import Any, Literal, Union, overload -from flask import Flask, current_app +from flask import Flask, copy_current_request_context, current_app from pydantic import ValidationError from configs import dify_config @@ -151,16 +151,17 @@ class CompletionAppGenerator(MessageBasedAppGenerator): message_id=message.id, ) - # new thread - worker_thread = threading.Thread( - target=self._generate_worker, - kwargs={ - "flask_app": current_app._get_current_object(), # type: ignore - "application_generate_entity": application_generate_entity, - "queue_manager": queue_manager, - "message_id": message.id, - }, - ) + # new thread with request context + @copy_current_request_context + def worker_with_context(): + return self._generate_worker( + flask_app=current_app._get_current_object(), # type: ignore + application_generate_entity=application_generate_entity, + queue_manager=queue_manager, + message_id=message.id, + ) + + worker_thread = threading.Thread(target=worker_with_context) worker_thread.start() @@ -313,16 +314,17 @@ class CompletionAppGenerator(MessageBasedAppGenerator): message_id=message.id, ) - # new thread - worker_thread = threading.Thread( - target=self._generate_worker, - kwargs={ - "flask_app": current_app._get_current_object(), # type: ignore - "application_generate_entity": application_generate_entity, - "queue_manager": queue_manager, - "message_id": message.id, - }, - ) + # new thread with request context + @copy_current_request_context + def worker_with_context(): + return self._generate_worker( + flask_app=current_app._get_current_object(), # type: ignore + application_generate_entity=application_generate_entity, + queue_manager=queue_manager, + message_id=message.id, + ) + + worker_thread = threading.Thread(target=worker_with_context) worker_thread.start() diff --git a/api/core/app/apps/message_based_app_generator.py b/api/core/app/apps/message_based_app_generator.py index b1f527c0f2..58b94f4d43 100644 --- a/api/core/app/apps/message_based_app_generator.py +++ b/api/core/app/apps/message_based_app_generator.py @@ -25,7 +25,7 @@ from core.app.task_pipeline.easy_ui_based_generate_task_pipeline import EasyUIBa from core.prompt.utils.prompt_template_parser import PromptTemplateParser from extensions.ext_database import db from models import Account -from models.enums import CreatedByRole +from models.enums import CreatorUserRole from models.model import App, AppMode, AppModelConfig, Conversation, EndUser, Message, MessageFile from services.errors.app_model_config import AppModelConfigBrokenError from services.errors.conversation import ConversationNotExistsError @@ -153,6 +153,8 @@ class MessageBasedAppGenerator(BaseAppGenerator): query = application_generate_entity.query or "New conversation" else: query = next(iter(application_generate_entity.inputs.values()), "New conversation") + if isinstance(query, int): + query = str(query) query = query or "New conversation" conversation_name = (query[:20] + "…") if len(query) > 20 else query @@ -221,7 +223,7 @@ class MessageBasedAppGenerator(BaseAppGenerator): belongs_to="user", url=file.remote_url, upload_file_id=file.related_id, - created_by_role=(CreatedByRole.ACCOUNT if account_id else CreatedByRole.END_USER), + created_by_role=(CreatorUserRole.ACCOUNT if account_id else CreatorUserRole.END_USER), created_by=account_id or end_user_id or "", ) db.session.add(message_file) diff --git a/api/core/app/apps/workflow/app_generator.py b/api/core/app/apps/workflow/app_generator.py index 08986b16f0..f4aec3479b 100644 --- a/api/core/app/apps/workflow/app_generator.py +++ b/api/core/app/apps/workflow/app_generator.py @@ -5,8 +5,9 @@ import uuid from collections.abc import Generator, Mapping, Sequence from typing import Any, Literal, Optional, Union, overload -from flask import Flask, current_app +from flask import Flask, copy_current_request_context, current_app, has_request_context from pydantic import ValidationError +from sqlalchemy.orm import sessionmaker import contexts from configs import dify_config @@ -22,9 +23,14 @@ from core.app.entities.app_invoke_entities import InvokeFrom, WorkflowAppGenerat from core.app.entities.task_entities import WorkflowAppBlockingResponse, WorkflowAppStreamResponse from core.model_runtime.errors.invoke import InvokeAuthorizationError from core.ops.ops_trace_manager import TraceQueueManager +from core.repositories import SQLAlchemyWorkflowNodeExecutionRepository +from core.repositories.sqlalchemy_workflow_execution_repository import SQLAlchemyWorkflowExecutionRepository +from core.workflow.repositories.workflow_execution_repository import WorkflowExecutionRepository +from core.workflow.repositories.workflow_node_execution_repository import WorkflowNodeExecutionRepository from extensions.ext_database import db from factories import file_factory -from models import Account, App, EndUser, Workflow +from models import Account, App, EndUser, Workflow, WorkflowNodeExecutionTriggeredFrom +from models.enums import WorkflowRunTriggeredFrom logger = logging.getLogger(__name__) @@ -126,19 +132,43 @@ class WorkflowAppGenerator(BaseAppGenerator): invoke_from=invoke_from, call_depth=call_depth, trace_manager=trace_manager, - workflow_run_id=workflow_run_id, + workflow_execution_id=workflow_run_id, ) - contexts.tenant_id.set(application_generate_entity.app_config.tenant_id) contexts.plugin_tool_providers.set({}) contexts.plugin_tool_providers_lock.set(threading.Lock()) + # Create repositories + # + # Create session factory + session_factory = sessionmaker(bind=db.engine, expire_on_commit=False) + # Create workflow execution(aka workflow run) repository + if invoke_from == InvokeFrom.DEBUGGER: + workflow_triggered_from = WorkflowRunTriggeredFrom.DEBUGGING + else: + workflow_triggered_from = WorkflowRunTriggeredFrom.APP_RUN + workflow_execution_repository = SQLAlchemyWorkflowExecutionRepository( + session_factory=session_factory, + user=user, + app_id=application_generate_entity.app_config.app_id, + triggered_from=workflow_triggered_from, + ) + # Create workflow node execution repository + workflow_node_execution_repository = SQLAlchemyWorkflowNodeExecutionRepository( + session_factory=session_factory, + user=user, + app_id=application_generate_entity.app_config.app_id, + triggered_from=WorkflowNodeExecutionTriggeredFrom.WORKFLOW_RUN, + ) + return self._generate( app_model=app_model, workflow=workflow, user=user, application_generate_entity=application_generate_entity, invoke_from=invoke_from, + workflow_execution_repository=workflow_execution_repository, + workflow_node_execution_repository=workflow_node_execution_repository, streaming=streaming, workflow_thread_pool_id=workflow_thread_pool_id, ) @@ -151,6 +181,8 @@ class WorkflowAppGenerator(BaseAppGenerator): user: Union[Account, EndUser], application_generate_entity: WorkflowAppGenerateEntity, invoke_from: InvokeFrom, + workflow_execution_repository: WorkflowExecutionRepository, + workflow_node_execution_repository: WorkflowNodeExecutionRepository, streaming: bool = True, workflow_thread_pool_id: Optional[str] = None, ) -> Union[Mapping[str, Any], Generator[str | Mapping[str, Any], None, None]]: @@ -162,6 +194,7 @@ class WorkflowAppGenerator(BaseAppGenerator): :param user: account or end user :param application_generate_entity: application generate entity :param invoke_from: invoke from source + :param workflow_node_execution_repository: repository for workflow node execution :param streaming: is stream :param workflow_thread_pool_id: workflow thread pool id """ @@ -173,17 +206,22 @@ class WorkflowAppGenerator(BaseAppGenerator): app_mode=app_model.mode, ) - # new thread - worker_thread = threading.Thread( - target=self._generate_worker, - kwargs={ - "flask_app": current_app._get_current_object(), # type: ignore - "application_generate_entity": application_generate_entity, - "queue_manager": queue_manager, - "context": contextvars.copy_context(), - "workflow_thread_pool_id": workflow_thread_pool_id, - }, - ) + # new thread with request context and contextvars + context = contextvars.copy_context() + + @copy_current_request_context + def worker_with_context(): + # Run the worker within the copied context + return context.run( + self._generate_worker, + flask_app=current_app._get_current_object(), # type: ignore + application_generate_entity=application_generate_entity, + queue_manager=queue_manager, + context=context, + workflow_thread_pool_id=workflow_thread_pool_id, + ) + + worker_thread = threading.Thread(target=worker_with_context) worker_thread.start() @@ -193,6 +231,8 @@ class WorkflowAppGenerator(BaseAppGenerator): workflow=workflow, queue_manager=queue_manager, user=user, + workflow_execution_repository=workflow_execution_repository, + workflow_node_execution_repository=workflow_node_execution_repository, stream=streaming, ) @@ -239,18 +279,40 @@ class WorkflowAppGenerator(BaseAppGenerator): single_iteration_run=WorkflowAppGenerateEntity.SingleIterationRunEntity( node_id=node_id, inputs=args["inputs"] ), - workflow_run_id=str(uuid.uuid4()), + workflow_execution_id=str(uuid.uuid4()), ) - contexts.tenant_id.set(application_generate_entity.app_config.tenant_id) contexts.plugin_tool_providers.set({}) contexts.plugin_tool_providers_lock.set(threading.Lock()) + # Create repositories + # + # Create session factory + session_factory = sessionmaker(bind=db.engine, expire_on_commit=False) + # Create workflow execution(aka workflow run) repository + workflow_execution_repository = SQLAlchemyWorkflowExecutionRepository( + session_factory=session_factory, + user=user, + app_id=application_generate_entity.app_config.app_id, + triggered_from=WorkflowRunTriggeredFrom.DEBUGGING, + ) + # Create workflow node execution repository + session_factory = sessionmaker(bind=db.engine, expire_on_commit=False) + + workflow_node_execution_repository = SQLAlchemyWorkflowNodeExecutionRepository( + session_factory=session_factory, + user=user, + app_id=application_generate_entity.app_config.app_id, + triggered_from=WorkflowNodeExecutionTriggeredFrom.SINGLE_STEP, + ) + return self._generate( app_model=app_model, workflow=workflow, user=user, invoke_from=InvokeFrom.DEBUGGER, application_generate_entity=application_generate_entity, + workflow_execution_repository=workflow_execution_repository, + workflow_node_execution_repository=workflow_node_execution_repository, streaming=streaming, ) @@ -293,18 +355,40 @@ class WorkflowAppGenerator(BaseAppGenerator): invoke_from=InvokeFrom.DEBUGGER, extras={"auto_generate_conversation_name": False}, single_loop_run=WorkflowAppGenerateEntity.SingleLoopRunEntity(node_id=node_id, inputs=args["inputs"]), - workflow_run_id=str(uuid.uuid4()), + workflow_execution_id=str(uuid.uuid4()), ) - contexts.tenant_id.set(application_generate_entity.app_config.tenant_id) contexts.plugin_tool_providers.set({}) contexts.plugin_tool_providers_lock.set(threading.Lock()) + # Create repositories + # + # Create session factory + session_factory = sessionmaker(bind=db.engine, expire_on_commit=False) + # Create workflow execution(aka workflow run) repository + workflow_execution_repository = SQLAlchemyWorkflowExecutionRepository( + session_factory=session_factory, + user=user, + app_id=application_generate_entity.app_config.app_id, + triggered_from=WorkflowRunTriggeredFrom.DEBUGGING, + ) + # Create workflow node execution repository + session_factory = sessionmaker(bind=db.engine, expire_on_commit=False) + + workflow_node_execution_repository = SQLAlchemyWorkflowNodeExecutionRepository( + session_factory=session_factory, + user=user, + app_id=application_generate_entity.app_config.app_id, + triggered_from=WorkflowNodeExecutionTriggeredFrom.SINGLE_STEP, + ) + return self._generate( app_model=app_model, workflow=workflow, user=user, invoke_from=InvokeFrom.DEBUGGER, application_generate_entity=application_generate_entity, + workflow_execution_repository=workflow_execution_repository, + workflow_node_execution_repository=workflow_node_execution_repository, streaming=streaming, ) @@ -326,8 +410,22 @@ class WorkflowAppGenerator(BaseAppGenerator): """ for var, val in context.items(): var.set(val) + + # FIXME(-LAN-): Save current user before entering new app context + from flask import g + + saved_user = None + if has_request_context() and hasattr(g, "_login_user"): + saved_user = g._login_user + with flask_app.app_context(): try: + # Restore user in new app context + if saved_user is not None: + from flask import g + + g._login_user = saved_user + # workflow app runner = WorkflowAppRunner( application_generate_entity=application_generate_entity, @@ -361,6 +459,8 @@ class WorkflowAppGenerator(BaseAppGenerator): workflow: Workflow, queue_manager: AppQueueManager, user: Union[Account, EndUser], + workflow_execution_repository: WorkflowExecutionRepository, + workflow_node_execution_repository: WorkflowNodeExecutionRepository, stream: bool = False, ) -> Union[WorkflowAppBlockingResponse, Generator[WorkflowAppStreamResponse, None, None]]: """ @@ -370,6 +470,7 @@ class WorkflowAppGenerator(BaseAppGenerator): :param queue_manager: queue manager :param user: account or end user :param stream: is stream + :param workflow_node_execution_repository: optional repository for workflow node execution :return: """ # init generate task pipeline @@ -378,6 +479,8 @@ class WorkflowAppGenerator(BaseAppGenerator): workflow=workflow, queue_manager=queue_manager, user=user, + workflow_execution_repository=workflow_execution_repository, + workflow_node_execution_repository=workflow_node_execution_repository, stream=stream, ) diff --git a/api/core/app/apps/workflow/app_runner.py b/api/core/app/apps/workflow/app_runner.py index b38ee18ac4..b59e34e222 100644 --- a/api/core/app/apps/workflow/app_runner.py +++ b/api/core/app/apps/workflow/app_runner.py @@ -95,7 +95,7 @@ class WorkflowAppRunner(WorkflowBasedAppRunner): SystemVariableKey.USER_ID: user_id, SystemVariableKey.APP_ID: app_config.app_id, SystemVariableKey.WORKFLOW_ID: app_config.workflow_id, - SystemVariableKey.WORKFLOW_RUN_ID: self.application_generate_entity.workflow_run_id, + SystemVariableKey.WORKFLOW_EXECUTION_ID: self.application_generate_entity.workflow_execution_id, } variable_pool = VariablePool( diff --git a/api/core/app/apps/workflow/generate_task_pipeline.py b/api/core/app/apps/workflow/generate_task_pipeline.py index 1f998edb6a..1734dbb598 100644 --- a/api/core/app/apps/workflow/generate_task_pipeline.py +++ b/api/core/app/apps/workflow/generate_task_pipeline.py @@ -3,11 +3,12 @@ import time from collections.abc import Generator from typing import Optional, Union +from sqlalchemy import select from sqlalchemy.orm import Session from constants.tts_auto_play_timeout import TTS_AUTO_PLAY_TIMEOUT, TTS_AUTO_PLAY_YIELD_CPU_TIME -from core.app.apps.advanced_chat.app_generator_tts_publisher import AppGeneratorTTSPublisher, AudioTrunk from core.app.apps.base_app_queue_manager import AppQueueManager +from core.app.apps.common.workflow_response_converter import WorkflowResponseConverter from core.app.entities.app_invoke_entities import ( InvokeFrom, WorkflowAppGenerateEntity, @@ -49,22 +50,24 @@ from core.app.entities.task_entities import ( WorkflowAppStreamResponse, WorkflowFinishStreamResponse, WorkflowStartStreamResponse, - WorkflowTaskState, ) from core.app.task_pipeline.based_generate_task_pipeline import BasedGenerateTaskPipeline -from core.app.task_pipeline.workflow_cycle_manage import WorkflowCycleManage +from core.base.tts import AppGeneratorTTSPublisher, AudioTrunk from core.ops.ops_trace_manager import TraceQueueManager +from core.workflow.entities.workflow_execution import WorkflowExecution, WorkflowExecutionStatus, WorkflowType from core.workflow.enums import SystemVariableKey +from core.workflow.repositories.workflow_execution_repository import WorkflowExecutionRepository +from core.workflow.repositories.workflow_node_execution_repository import WorkflowNodeExecutionRepository +from core.workflow.workflow_cycle_manager import CycleManagerWorkflowInfo, WorkflowCycleManager from extensions.ext_database import db from models.account import Account -from models.enums import CreatedByRole +from models.enums import CreatorUserRole from models.model import EndUser from models.workflow import ( Workflow, WorkflowAppLog, WorkflowAppLogCreatedFrom, WorkflowRun, - WorkflowRunStatus, ) logger = logging.getLogger(__name__) @@ -82,6 +85,8 @@ class WorkflowAppGenerateTaskPipeline: queue_manager: AppQueueManager, user: Union[Account, EndUser], stream: bool, + workflow_execution_repository: WorkflowExecutionRepository, + workflow_node_execution_repository: WorkflowNodeExecutionRepository, ) -> None: self._base_task_pipeline = BasedGenerateTaskPipeline( application_generate_entity=application_generate_entity, @@ -92,29 +97,39 @@ class WorkflowAppGenerateTaskPipeline: if isinstance(user, EndUser): self._user_id = user.id user_session_id = user.session_id - self._created_by_role = CreatedByRole.END_USER + self._created_by_role = CreatorUserRole.END_USER elif isinstance(user, Account): self._user_id = user.id user_session_id = user.id - self._created_by_role = CreatedByRole.ACCOUNT + self._created_by_role = CreatorUserRole.ACCOUNT else: raise ValueError(f"Invalid user type: {type(user)}") - self._workflow_cycle_manager = WorkflowCycleManage( + self._workflow_cycle_manager = WorkflowCycleManager( application_generate_entity=application_generate_entity, workflow_system_variables={ SystemVariableKey.FILES: application_generate_entity.files, SystemVariableKey.USER_ID: user_session_id, SystemVariableKey.APP_ID: application_generate_entity.app_config.app_id, SystemVariableKey.WORKFLOW_ID: workflow.id, - SystemVariableKey.WORKFLOW_RUN_ID: application_generate_entity.workflow_run_id, + SystemVariableKey.WORKFLOW_EXECUTION_ID: application_generate_entity.workflow_execution_id, }, + workflow_info=CycleManagerWorkflowInfo( + workflow_id=workflow.id, + workflow_type=WorkflowType(workflow.type), + version=workflow.version, + graph_data=workflow.graph_dict, + ), + workflow_execution_repository=workflow_execution_repository, + workflow_node_execution_repository=workflow_node_execution_repository, + ) + + self._workflow_response_converter = WorkflowResponseConverter( + application_generate_entity=application_generate_entity, ) self._application_generate_entity = application_generate_entity - self._workflow_id = workflow.id self._workflow_features_dict = workflow.features_dict - self._task_state = WorkflowTaskState() self._workflow_run_id = "" def process(self) -> Union[WorkflowAppBlockingResponse, Generator[WorkflowAppStreamResponse, None, None]]: @@ -253,19 +268,13 @@ class WorkflowAppGenerateTaskPipeline: # override graph runtime state graph_runtime_state = event.graph_runtime_state - with Session(db.engine, expire_on_commit=False) as session: - # init workflow run - workflow_run = self._workflow_cycle_manager._handle_workflow_run_start( - session=session, - workflow_id=self._workflow_id, - user_id=self._user_id, - created_by_role=self._created_by_role, - ) - self._workflow_run_id = workflow_run.id - start_resp = self._workflow_cycle_manager._workflow_start_to_stream_response( - session=session, task_id=self._application_generate_entity.task_id, workflow_run=workflow_run - ) - session.commit() + # init workflow run + workflow_execution = self._workflow_cycle_manager.handle_workflow_run_start() + self._workflow_run_id = workflow_execution.id_ + start_resp = self._workflow_response_converter.workflow_start_to_stream_response( + task_id=self._application_generate_entity.task_id, + workflow_execution=workflow_execution, + ) yield start_resp elif isinstance( @@ -275,13 +284,11 @@ class WorkflowAppGenerateTaskPipeline: if not self._workflow_run_id: raise ValueError("workflow run not initialized.") with Session(db.engine, expire_on_commit=False) as session: - workflow_run = self._workflow_cycle_manager._get_workflow_run( - session=session, workflow_run_id=self._workflow_run_id - ) - workflow_node_execution = self._workflow_cycle_manager._handle_workflow_node_execution_retried( - workflow_run=workflow_run, event=event + workflow_node_execution = self._workflow_cycle_manager.handle_workflow_node_execution_retried( + workflow_execution_id=self._workflow_run_id, + event=event, ) - response = self._workflow_cycle_manager._workflow_node_retry_to_stream_response( + response = self._workflow_response_converter.workflow_node_retry_to_stream_response( event=event, task_id=self._application_generate_entity.task_id, workflow_node_execution=workflow_node_execution, @@ -294,27 +301,22 @@ class WorkflowAppGenerateTaskPipeline: if not self._workflow_run_id: raise ValueError("workflow run not initialized.") - with Session(db.engine, expire_on_commit=False) as session: - workflow_run = self._workflow_cycle_manager._get_workflow_run( - session=session, workflow_run_id=self._workflow_run_id - ) - workflow_node_execution = self._workflow_cycle_manager._handle_node_execution_start( - workflow_run=workflow_run, event=event - ) - node_start_response = self._workflow_cycle_manager._workflow_node_start_to_stream_response( - event=event, - task_id=self._application_generate_entity.task_id, - workflow_node_execution=workflow_node_execution, - ) - session.commit() + workflow_node_execution = self._workflow_cycle_manager.handle_node_execution_start( + workflow_execution_id=self._workflow_run_id, event=event + ) + node_start_response = self._workflow_response_converter.workflow_node_start_to_stream_response( + event=event, + task_id=self._application_generate_entity.task_id, + workflow_node_execution=workflow_node_execution, + ) if node_start_response: yield node_start_response elif isinstance(event, QueueNodeSucceededEvent): - workflow_node_execution = self._workflow_cycle_manager._handle_workflow_node_execution_success( + workflow_node_execution = self._workflow_cycle_manager.handle_workflow_node_execution_success( event=event ) - node_success_response = self._workflow_cycle_manager._workflow_node_finish_to_stream_response( + node_success_response = self._workflow_response_converter.workflow_node_finish_to_stream_response( event=event, task_id=self._application_generate_entity.task_id, workflow_node_execution=workflow_node_execution, @@ -329,10 +331,10 @@ class WorkflowAppGenerateTaskPipeline: | QueueNodeInLoopFailedEvent | QueueNodeExceptionEvent, ): - workflow_node_execution = self._workflow_cycle_manager._handle_workflow_node_execution_failed( + workflow_node_execution = self._workflow_cycle_manager.handle_workflow_node_execution_failed( event=event, ) - node_failed_response = self._workflow_cycle_manager._workflow_node_finish_to_stream_response( + node_failed_response = self._workflow_response_converter.workflow_node_finish_to_stream_response( event=event, task_id=self._application_generate_entity.task_id, workflow_node_execution=workflow_node_execution, @@ -345,18 +347,13 @@ class WorkflowAppGenerateTaskPipeline: if not self._workflow_run_id: raise ValueError("workflow run not initialized.") - with Session(db.engine, expire_on_commit=False) as session: - workflow_run = self._workflow_cycle_manager._get_workflow_run( - session=session, workflow_run_id=self._workflow_run_id - ) - parallel_start_resp = ( - self._workflow_cycle_manager._workflow_parallel_branch_start_to_stream_response( - session=session, - task_id=self._application_generate_entity.task_id, - workflow_run=workflow_run, - event=event, - ) + parallel_start_resp = ( + self._workflow_response_converter.workflow_parallel_branch_start_to_stream_response( + task_id=self._application_generate_entity.task_id, + workflow_execution_id=self._workflow_run_id, + event=event, ) + ) yield parallel_start_resp @@ -364,18 +361,13 @@ class WorkflowAppGenerateTaskPipeline: if not self._workflow_run_id: raise ValueError("workflow run not initialized.") - with Session(db.engine, expire_on_commit=False) as session: - workflow_run = self._workflow_cycle_manager._get_workflow_run( - session=session, workflow_run_id=self._workflow_run_id - ) - parallel_finish_resp = ( - self._workflow_cycle_manager._workflow_parallel_branch_finished_to_stream_response( - session=session, - task_id=self._application_generate_entity.task_id, - workflow_run=workflow_run, - event=event, - ) + parallel_finish_resp = ( + self._workflow_response_converter.workflow_parallel_branch_finished_to_stream_response( + task_id=self._application_generate_entity.task_id, + workflow_execution_id=self._workflow_run_id, + event=event, ) + ) yield parallel_finish_resp @@ -383,16 +375,11 @@ class WorkflowAppGenerateTaskPipeline: if not self._workflow_run_id: raise ValueError("workflow run not initialized.") - with Session(db.engine, expire_on_commit=False) as session: - workflow_run = self._workflow_cycle_manager._get_workflow_run( - session=session, workflow_run_id=self._workflow_run_id - ) - iter_start_resp = self._workflow_cycle_manager._workflow_iteration_start_to_stream_response( - session=session, - task_id=self._application_generate_entity.task_id, - workflow_run=workflow_run, - event=event, - ) + iter_start_resp = self._workflow_response_converter.workflow_iteration_start_to_stream_response( + task_id=self._application_generate_entity.task_id, + workflow_execution_id=self._workflow_run_id, + event=event, + ) yield iter_start_resp @@ -400,16 +387,11 @@ class WorkflowAppGenerateTaskPipeline: if not self._workflow_run_id: raise ValueError("workflow run not initialized.") - with Session(db.engine, expire_on_commit=False) as session: - workflow_run = self._workflow_cycle_manager._get_workflow_run( - session=session, workflow_run_id=self._workflow_run_id - ) - iter_next_resp = self._workflow_cycle_manager._workflow_iteration_next_to_stream_response( - session=session, - task_id=self._application_generate_entity.task_id, - workflow_run=workflow_run, - event=event, - ) + iter_next_resp = self._workflow_response_converter.workflow_iteration_next_to_stream_response( + task_id=self._application_generate_entity.task_id, + workflow_execution_id=self._workflow_run_id, + event=event, + ) yield iter_next_resp @@ -417,16 +399,11 @@ class WorkflowAppGenerateTaskPipeline: if not self._workflow_run_id: raise ValueError("workflow run not initialized.") - with Session(db.engine, expire_on_commit=False) as session: - workflow_run = self._workflow_cycle_manager._get_workflow_run( - session=session, workflow_run_id=self._workflow_run_id - ) - iter_finish_resp = self._workflow_cycle_manager._workflow_iteration_completed_to_stream_response( - session=session, - task_id=self._application_generate_entity.task_id, - workflow_run=workflow_run, - event=event, - ) + iter_finish_resp = self._workflow_response_converter.workflow_iteration_completed_to_stream_response( + task_id=self._application_generate_entity.task_id, + workflow_execution_id=self._workflow_run_id, + event=event, + ) yield iter_finish_resp @@ -434,16 +411,11 @@ class WorkflowAppGenerateTaskPipeline: if not self._workflow_run_id: raise ValueError("workflow run not initialized.") - with Session(db.engine, expire_on_commit=False) as session: - workflow_run = self._workflow_cycle_manager._get_workflow_run( - session=session, workflow_run_id=self._workflow_run_id - ) - loop_start_resp = self._workflow_cycle_manager._workflow_loop_start_to_stream_response( - session=session, - task_id=self._application_generate_entity.task_id, - workflow_run=workflow_run, - event=event, - ) + loop_start_resp = self._workflow_response_converter.workflow_loop_start_to_stream_response( + task_id=self._application_generate_entity.task_id, + workflow_execution_id=self._workflow_run_id, + event=event, + ) yield loop_start_resp @@ -451,16 +423,11 @@ class WorkflowAppGenerateTaskPipeline: if not self._workflow_run_id: raise ValueError("workflow run not initialized.") - with Session(db.engine, expire_on_commit=False) as session: - workflow_run = self._workflow_cycle_manager._get_workflow_run( - session=session, workflow_run_id=self._workflow_run_id - ) - loop_next_resp = self._workflow_cycle_manager._workflow_loop_next_to_stream_response( - session=session, - task_id=self._application_generate_entity.task_id, - workflow_run=workflow_run, - event=event, - ) + loop_next_resp = self._workflow_response_converter.workflow_loop_next_to_stream_response( + task_id=self._application_generate_entity.task_id, + workflow_execution_id=self._workflow_run_id, + event=event, + ) yield loop_next_resp @@ -468,16 +435,11 @@ class WorkflowAppGenerateTaskPipeline: if not self._workflow_run_id: raise ValueError("workflow run not initialized.") - with Session(db.engine, expire_on_commit=False) as session: - workflow_run = self._workflow_cycle_manager._get_workflow_run( - session=session, workflow_run_id=self._workflow_run_id - ) - loop_finish_resp = self._workflow_cycle_manager._workflow_loop_completed_to_stream_response( - session=session, - task_id=self._application_generate_entity.task_id, - workflow_run=workflow_run, - event=event, - ) + loop_finish_resp = self._workflow_response_converter.workflow_loop_completed_to_stream_response( + task_id=self._application_generate_entity.task_id, + workflow_execution_id=self._workflow_run_id, + event=event, + ) yield loop_finish_resp @@ -488,10 +450,8 @@ class WorkflowAppGenerateTaskPipeline: raise ValueError("graph runtime state not initialized.") with Session(db.engine, expire_on_commit=False) as session: - workflow_run = self._workflow_cycle_manager._handle_workflow_run_success( - session=session, + workflow_execution = self._workflow_cycle_manager.handle_workflow_run_success( workflow_run_id=self._workflow_run_id, - start_at=graph_runtime_state.start_at, total_tokens=graph_runtime_state.total_tokens, total_steps=graph_runtime_state.node_run_steps, outputs=event.outputs, @@ -500,12 +460,12 @@ class WorkflowAppGenerateTaskPipeline: ) # save workflow app log - self._save_workflow_app_log(session=session, workflow_run=workflow_run) + self._save_workflow_app_log(session=session, workflow_execution=workflow_execution) - workflow_finish_resp = self._workflow_cycle_manager._workflow_finish_to_stream_response( + workflow_finish_resp = self._workflow_response_converter.workflow_finish_to_stream_response( session=session, task_id=self._application_generate_entity.task_id, - workflow_run=workflow_run, + workflow_execution=workflow_execution, ) session.commit() @@ -517,10 +477,8 @@ class WorkflowAppGenerateTaskPipeline: raise ValueError("graph runtime state not initialized.") with Session(db.engine, expire_on_commit=False) as session: - workflow_run = self._workflow_cycle_manager._handle_workflow_run_partial_success( - session=session, + workflow_execution = self._workflow_cycle_manager.handle_workflow_run_partial_success( workflow_run_id=self._workflow_run_id, - start_at=graph_runtime_state.start_at, total_tokens=graph_runtime_state.total_tokens, total_steps=graph_runtime_state.node_run_steps, outputs=event.outputs, @@ -530,10 +488,12 @@ class WorkflowAppGenerateTaskPipeline: ) # save workflow app log - self._save_workflow_app_log(session=session, workflow_run=workflow_run) + self._save_workflow_app_log(session=session, workflow_execution=workflow_execution) - workflow_finish_resp = self._workflow_cycle_manager._workflow_finish_to_stream_response( - session=session, task_id=self._application_generate_entity.task_id, workflow_run=workflow_run + workflow_finish_resp = self._workflow_response_converter.workflow_finish_to_stream_response( + session=session, + task_id=self._application_generate_entity.task_id, + workflow_execution=workflow_execution, ) session.commit() @@ -545,26 +505,28 @@ class WorkflowAppGenerateTaskPipeline: raise ValueError("graph runtime state not initialized.") with Session(db.engine, expire_on_commit=False) as session: - workflow_run = self._workflow_cycle_manager._handle_workflow_run_failed( - session=session, + workflow_execution = self._workflow_cycle_manager.handle_workflow_run_failed( workflow_run_id=self._workflow_run_id, - start_at=graph_runtime_state.start_at, total_tokens=graph_runtime_state.total_tokens, total_steps=graph_runtime_state.node_run_steps, - status=WorkflowRunStatus.FAILED + status=WorkflowExecutionStatus.FAILED if isinstance(event, QueueWorkflowFailedEvent) - else WorkflowRunStatus.STOPPED, - error=event.error if isinstance(event, QueueWorkflowFailedEvent) else event.get_stop_reason(), + else WorkflowExecutionStatus.STOPPED, + error_message=event.error + if isinstance(event, QueueWorkflowFailedEvent) + else event.get_stop_reason(), conversation_id=None, trace_manager=trace_manager, exceptions_count=event.exceptions_count if isinstance(event, QueueWorkflowFailedEvent) else 0, ) # save workflow app log - self._save_workflow_app_log(session=session, workflow_run=workflow_run) + self._save_workflow_app_log(session=session, workflow_execution=workflow_execution) - workflow_finish_resp = self._workflow_cycle_manager._workflow_finish_to_stream_response( - session=session, task_id=self._application_generate_entity.task_id, workflow_run=workflow_run + workflow_finish_resp = self._workflow_response_converter.workflow_finish_to_stream_response( + session=session, + task_id=self._application_generate_entity.task_id, + workflow_execution=workflow_execution, ) session.commit() @@ -578,12 +540,11 @@ class WorkflowAppGenerateTaskPipeline: if tts_publisher: tts_publisher.publish(queue_message) - self._task_state.answer += delta_text yield self._text_chunk_to_stream_response( delta_text, from_variable_selector=event.from_variable_selector ) elif isinstance(event, QueueAgentLogEvent): - yield self._workflow_cycle_manager._handle_agent_log( + yield self._workflow_response_converter.handle_agent_log( task_id=self._application_generate_entity.task_id, event=event ) else: @@ -592,11 +553,9 @@ class WorkflowAppGenerateTaskPipeline: if tts_publisher: tts_publisher.publish(None) - def _save_workflow_app_log(self, *, session: Session, workflow_run: WorkflowRun) -> None: - """ - Save workflow app log. - :return: - """ + def _save_workflow_app_log(self, *, session: Session, workflow_execution: WorkflowExecution) -> None: + workflow_run = session.scalar(select(WorkflowRun).where(WorkflowRun.id == workflow_execution.id_)) + assert workflow_run is not None invoke_from = self._application_generate_entity.invoke_from if invoke_from == InvokeFrom.SERVICE_API: created_from = WorkflowAppLogCreatedFrom.SERVICE_API diff --git a/api/core/app/apps/workflow_app_runner.py b/api/core/app/apps/workflow_app_runner.py index 0884fac4a9..facc24b4ca 100644 --- a/api/core/app/apps/workflow_app_runner.py +++ b/api/core/app/apps/workflow_app_runner.py @@ -29,8 +29,8 @@ from core.app.entities.queue_entities import ( QueueWorkflowStartedEvent, QueueWorkflowSucceededEvent, ) -from core.workflow.entities.node_entities import NodeRunMetadataKey from core.workflow.entities.variable_pool import VariablePool +from core.workflow.entities.workflow_node_execution import WorkflowNodeExecutionMetadataKey from core.workflow.graph_engine.entities.event import ( AgentLogEvent, GraphEngineEvent, @@ -295,7 +295,7 @@ class WorkflowBasedAppRunner(AppRunner): inputs: Mapping[str, Any] | None = {} process_data: Mapping[str, Any] | None = {} outputs: Mapping[str, Any] | None = {} - execution_metadata: Mapping[NodeRunMetadataKey, Any] | None = {} + execution_metadata: Mapping[WorkflowNodeExecutionMetadataKey, Any] | None = {} if node_run_result: inputs = node_run_result.inputs process_data = node_run_result.process_data diff --git a/api/core/app/entities/app_invoke_entities.py b/api/core/app/entities/app_invoke_entities.py index 56e6b46a60..c0d99693b0 100644 --- a/api/core/app/entities/app_invoke_entities.py +++ b/api/core/app/entities/app_invoke_entities.py @@ -76,6 +76,8 @@ class AppGenerateEntity(BaseModel): App Generate Entity. """ + model_config = ConfigDict(arbitrary_types_allowed=True) + task_id: str # app config @@ -99,9 +101,6 @@ class AppGenerateEntity(BaseModel): # tracing instance trace_manager: Optional[TraceQueueManager] = None - class Config: - arbitrary_types_allowed = True - class EasyUIBasedAppGenerateEntity(AppGenerateEntity): """ @@ -205,7 +204,7 @@ class WorkflowAppGenerateEntity(AppGenerateEntity): # app config app_config: WorkflowUIBasedAppConfig - workflow_run_id: str + workflow_execution_id: str class SingleIterationRunEntity(BaseModel): """ diff --git a/api/core/app/entities/queue_entities.py b/api/core/app/entities/queue_entities.py index 3702326406..42e6a1519c 100644 --- a/api/core/app/entities/queue_entities.py +++ b/api/core/app/entities/queue_entities.py @@ -1,4 +1,4 @@ -from collections.abc import Mapping +from collections.abc import Mapping, Sequence from datetime import datetime from enum import Enum, StrEnum from typing import Any, Optional @@ -6,7 +6,9 @@ from typing import Any, Optional from pydantic import BaseModel from core.model_runtime.entities.llm_entities import LLMResult, LLMResultChunk -from core.workflow.entities.node_entities import AgentNodeStrategyInit, NodeRunMetadataKey +from core.rag.entities.citation_metadata import RetrievalSourceMetadata +from core.workflow.entities.node_entities import AgentNodeStrategyInit +from core.workflow.entities.workflow_node_execution import WorkflowNodeExecutionMetadataKey from core.workflow.graph_engine.entities.graph_runtime_state import GraphRuntimeState from core.workflow.nodes import NodeType from core.workflow.nodes.base import BaseNodeData @@ -264,8 +266,16 @@ class QueueMessageReplaceEvent(AppQueueEvent): QueueMessageReplaceEvent entity """ + class MessageReplaceReason(StrEnum): + """ + Reason for message replace event + """ + + OUTPUT_MODERATION = "output_moderation" + event: QueueEvent = QueueEvent.MESSAGE_REPLACE text: str + reason: str class QueueRetrieverResourcesEvent(AppQueueEvent): @@ -274,7 +284,7 @@ class QueueRetrieverResourcesEvent(AppQueueEvent): """ event: QueueEvent = QueueEvent.RETRIEVER_RESOURCES - retriever_resources: list[dict] + retriever_resources: Sequence[RetrievalSourceMetadata] in_iteration_id: Optional[str] = None """iteration id if node is in iteration""" in_loop_id: Optional[str] = None @@ -404,7 +414,7 @@ class QueueNodeSucceededEvent(AppQueueEvent): inputs: Optional[Mapping[str, Any]] = None process_data: Optional[Mapping[str, Any]] = None outputs: Optional[Mapping[str, Any]] = None - execution_metadata: Optional[Mapping[NodeRunMetadataKey, Any]] = None + execution_metadata: Optional[Mapping[WorkflowNodeExecutionMetadataKey, Any]] = None error: Optional[str] = None """single iteration duration map""" @@ -438,7 +448,7 @@ class QueueNodeRetryEvent(QueueNodeStartedEvent): inputs: Optional[Mapping[str, Any]] = None process_data: Optional[Mapping[str, Any]] = None outputs: Optional[Mapping[str, Any]] = None - execution_metadata: Optional[Mapping[NodeRunMetadataKey, Any]] = None + execution_metadata: Optional[Mapping[WorkflowNodeExecutionMetadataKey, Any]] = None error: str retry_index: int # retry index @@ -472,7 +482,7 @@ class QueueNodeInIterationFailedEvent(AppQueueEvent): inputs: Optional[Mapping[str, Any]] = None process_data: Optional[Mapping[str, Any]] = None outputs: Optional[Mapping[str, Any]] = None - execution_metadata: Optional[Mapping[NodeRunMetadataKey, Any]] = None + execution_metadata: Optional[Mapping[WorkflowNodeExecutionMetadataKey, Any]] = None error: str @@ -505,7 +515,7 @@ class QueueNodeInLoopFailedEvent(AppQueueEvent): inputs: Optional[Mapping[str, Any]] = None process_data: Optional[Mapping[str, Any]] = None outputs: Optional[Mapping[str, Any]] = None - execution_metadata: Optional[Mapping[NodeRunMetadataKey, Any]] = None + execution_metadata: Optional[Mapping[WorkflowNodeExecutionMetadataKey, Any]] = None error: str @@ -538,7 +548,7 @@ class QueueNodeExceptionEvent(AppQueueEvent): inputs: Optional[Mapping[str, Any]] = None process_data: Optional[Mapping[str, Any]] = None outputs: Optional[Mapping[str, Any]] = None - execution_metadata: Optional[Mapping[NodeRunMetadataKey, Any]] = None + execution_metadata: Optional[Mapping[WorkflowNodeExecutionMetadataKey, Any]] = None error: str @@ -571,7 +581,7 @@ class QueueNodeFailedEvent(AppQueueEvent): inputs: Optional[Mapping[str, Any]] = None process_data: Optional[Mapping[str, Any]] = None outputs: Optional[Mapping[str, Any]] = None - execution_metadata: Optional[Mapping[NodeRunMetadataKey, Any]] = None + execution_metadata: Optional[Mapping[WorkflowNodeExecutionMetadataKey, Any]] = None error: str diff --git a/api/core/app/entities/task_entities.py b/api/core/app/entities/task_entities.py index f23ee1b9fd..25c889e922 100644 --- a/api/core/app/entities/task_entities.py +++ b/api/core/app/entities/task_entities.py @@ -2,12 +2,29 @@ from collections.abc import Mapping, Sequence from enum import Enum from typing import Any, Optional -from pydantic import BaseModel, ConfigDict +from pydantic import BaseModel, ConfigDict, Field -from core.model_runtime.entities.llm_entities import LLMResult +from core.model_runtime.entities.llm_entities import LLMResult, LLMUsage from core.model_runtime.utils.encoders import jsonable_encoder +from core.rag.entities.citation_metadata import RetrievalSourceMetadata from core.workflow.entities.node_entities import AgentNodeStrategyInit -from models.workflow import WorkflowNodeExecutionStatus +from core.workflow.entities.workflow_node_execution import WorkflowNodeExecutionMetadataKey, WorkflowNodeExecutionStatus + + +class AnnotationReplyAccount(BaseModel): + id: str + name: str + + +class AnnotationReply(BaseModel): + id: str + account: AnnotationReplyAccount + + +class TaskStateMetadata(BaseModel): + annotation_reply: AnnotationReply | None = None + retriever_resources: Sequence[RetrievalSourceMetadata] = Field(default_factory=list) + usage: LLMUsage | None = None class TaskState(BaseModel): @@ -15,7 +32,7 @@ class TaskState(BaseModel): TaskState entity """ - metadata: dict = {} + metadata: TaskStateMetadata = Field(default_factory=TaskStateMetadata) class EasyUITaskState(TaskState): @@ -148,6 +165,7 @@ class MessageReplaceStreamResponse(StreamResponse): event: StreamEvent = StreamEvent.MESSAGE_REPLACE answer: str + reason: str class AgentThoughtStreamResponse(StreamResponse): @@ -188,8 +206,7 @@ class WorkflowStartStreamResponse(StreamResponse): id: str workflow_id: str - sequence_number: int - inputs: dict + inputs: Mapping[str, Any] created_at: int event: StreamEvent = StreamEvent.WORKFLOW_STARTED @@ -209,9 +226,8 @@ class WorkflowFinishStreamResponse(StreamResponse): id: str workflow_id: str - sequence_number: int status: str - outputs: Optional[dict] = None + outputs: Optional[Mapping[str, Any]] = None error: Optional[str] = None elapsed_time: float total_tokens: int @@ -243,7 +259,7 @@ class NodeStartStreamResponse(StreamResponse): title: str index: int predecessor_node_id: Optional[str] = None - inputs: Optional[dict] = None + inputs: Optional[Mapping[str, Any]] = None created_at: int extras: dict = {} parallel_id: Optional[str] = None @@ -300,13 +316,13 @@ class NodeFinishStreamResponse(StreamResponse): title: str index: int predecessor_node_id: Optional[str] = None - inputs: Optional[dict] = None - process_data: Optional[dict] = None - outputs: Optional[dict] = None + inputs: Optional[Mapping[str, Any]] = None + process_data: Optional[Mapping[str, Any]] = None + outputs: Optional[Mapping[str, Any]] = None status: str error: Optional[str] = None elapsed_time: float - execution_metadata: Optional[dict] = None + execution_metadata: Optional[Mapping[WorkflowNodeExecutionMetadataKey, Any]] = None created_at: int finished_at: int files: Optional[Sequence[Mapping[str, Any]]] = [] @@ -369,13 +385,13 @@ class NodeRetryStreamResponse(StreamResponse): title: str index: int predecessor_node_id: Optional[str] = None - inputs: Optional[dict] = None - process_data: Optional[dict] = None - outputs: Optional[dict] = None + inputs: Optional[Mapping[str, Any]] = None + process_data: Optional[Mapping[str, Any]] = None + outputs: Optional[Mapping[str, Any]] = None status: str error: Optional[str] = None elapsed_time: float - execution_metadata: Optional[dict] = None + execution_metadata: Optional[Mapping[WorkflowNodeExecutionMetadataKey, Any]] = None created_at: int finished_at: int files: Optional[Sequence[Mapping[str, Any]]] = [] @@ -787,7 +803,7 @@ class WorkflowAppBlockingResponse(AppBlockingResponse): id: str workflow_id: str status: str - outputs: Optional[dict] = None + outputs: Optional[Mapping[str, Any]] = None error: Optional[str] = None elapsed_time: float total_tokens: int diff --git a/api/core/app/task_pipeline/based_generate_task_pipeline.py b/api/core/app/task_pipeline/based_generate_task_pipeline.py index a2e06d4e1f..5331c0cc94 100644 --- a/api/core/app/task_pipeline/based_generate_task_pipeline.py +++ b/api/core/app/task_pipeline/based_generate_task_pipeline.py @@ -126,12 +126,12 @@ class BasedGenerateTaskPipeline: if self._output_moderation_handler: self._output_moderation_handler.stop_thread() - completion = self._output_moderation_handler.moderation_completion( + completion, flagged = self._output_moderation_handler.moderation_completion( completion=completion, public_event=False ) self._output_moderation_handler = None - - return completion + if flagged: + return completion return None diff --git a/api/core/app/task_pipeline/easy_ui_based_generate_task_pipeline.py b/api/core/app/task_pipeline/easy_ui_based_generate_task_pipeline.py index 8c9c26d36e..d535e1f835 100644 --- a/api/core/app/task_pipeline/easy_ui_based_generate_task_pipeline.py +++ b/api/core/app/task_pipeline/easy_ui_based_generate_task_pipeline.py @@ -1,4 +1,3 @@ -import json import logging import time from collections.abc import Generator @@ -9,7 +8,6 @@ from sqlalchemy import select from sqlalchemy.orm import Session from constants.tts_auto_play_timeout import TTS_AUTO_PLAY_TIMEOUT, TTS_AUTO_PLAY_YIELD_CPU_TIME -from core.app.apps.advanced_chat.app_generator_tts_publisher import AppGeneratorTTSPublisher, AudioTrunk from core.app.apps.base_app_queue_manager import AppQueueManager, PublishFrom from core.app.entities.app_invoke_entities import ( AgentChatAppGenerateEntity, @@ -44,14 +42,15 @@ from core.app.entities.task_entities import ( StreamResponse, ) from core.app.task_pipeline.based_generate_task_pipeline import BasedGenerateTaskPipeline -from core.app.task_pipeline.message_cycle_manage import MessageCycleManage +from core.app.task_pipeline.message_cycle_manager import MessageCycleManager +from core.base.tts import AppGeneratorTTSPublisher, AudioTrunk from core.model_manager import ModelInstance from core.model_runtime.entities.llm_entities import LLMResult, LLMResultChunk, LLMResultChunkDelta, LLMUsage from core.model_runtime.entities.message_entities import ( AssistantPromptMessage, + TextPromptMessageContent, ) from core.model_runtime.model_providers.__base.large_language_model import LargeLanguageModel -from core.model_runtime.utils.encoders import jsonable_encoder from core.ops.entities.trace_entity import TraceTaskName from core.ops.ops_trace_manager import TraceQueueManager, TraceTask from core.prompt.utils.prompt_message_util import PromptMessageUtil @@ -63,7 +62,7 @@ from models.model import AppMode, Conversation, Message, MessageAgentThought logger = logging.getLogger(__name__) -class EasyUIBasedGenerateTaskPipeline(BasedGenerateTaskPipeline, MessageCycleManage): +class EasyUIBasedGenerateTaskPipeline(BasedGenerateTaskPipeline): """ EasyUIBasedGenerateTaskPipeline is a class that generate stream output and state management for Application. """ @@ -104,6 +103,11 @@ class EasyUIBasedGenerateTaskPipeline(BasedGenerateTaskPipeline, MessageCycleMan ) ) + self._message_cycle_manager = MessageCycleManager( + application_generate_entity=application_generate_entity, + task_state=self._task_state, + ) + self._conversation_name_generate_thread: Optional[Thread] = None def process( @@ -115,7 +119,7 @@ class EasyUIBasedGenerateTaskPipeline(BasedGenerateTaskPipeline, MessageCycleMan ]: if self._application_generate_entity.app_config.app_mode != AppMode.COMPLETION: # start generate conversation name thread - self._conversation_name_generate_thread = self._generate_conversation_name( + self._conversation_name_generate_thread = self._message_cycle_manager.generate_conversation_name( conversation_id=self._conversation_id, query=self._application_generate_entity.query or "" ) @@ -136,9 +140,9 @@ class EasyUIBasedGenerateTaskPipeline(BasedGenerateTaskPipeline, MessageCycleMan if isinstance(stream_response, ErrorStreamResponse): raise stream_response.err elif isinstance(stream_response, MessageEndStreamResponse): - extras = {"usage": jsonable_encoder(self._task_state.llm_result.usage)} + extras = {"usage": self._task_state.llm_result.usage.model_dump()} if self._task_state.metadata: - extras["metadata"] = self._task_state.metadata + extras["metadata"] = self._task_state.metadata.model_dump() response: Union[ChatbotAppBlockingResponse, CompletionAppBlockingResponse] if self._conversation_mode == AppMode.COMPLETION.value: response = CompletionAppBlockingResponse( @@ -277,7 +281,9 @@ class EasyUIBasedGenerateTaskPipeline(BasedGenerateTaskPipeline, MessageCycleMan ) if output_moderation_answer: self._task_state.llm_result.message.content = output_moderation_answer - yield self._message_replace_to_stream_response(answer=output_moderation_answer) + yield self._message_cycle_manager.message_replace_to_stream_response( + answer=output_moderation_answer + ) with Session(db.engine) as session: # Save message @@ -286,9 +292,9 @@ class EasyUIBasedGenerateTaskPipeline(BasedGenerateTaskPipeline, MessageCycleMan message_end_resp = self._message_end_to_stream_response() yield message_end_resp elif isinstance(event, QueueRetrieverResourcesEvent): - self._handle_retriever_resources(event) + self._message_cycle_manager.handle_retriever_resources(event) elif isinstance(event, QueueAnnotationReplyEvent): - annotation = self._handle_annotation_reply(event) + annotation = self._message_cycle_manager.handle_annotation_reply(event) if annotation: self._task_state.llm_result.message.content = annotation.content elif isinstance(event, QueueAgentThoughtEvent): @@ -296,7 +302,7 @@ class EasyUIBasedGenerateTaskPipeline(BasedGenerateTaskPipeline, MessageCycleMan if agent_thought_response is not None: yield agent_thought_response elif isinstance(event, QueueMessageFileEvent): - response = self._message_file_to_stream_response(event) + response = self._message_cycle_manager.message_file_to_stream_response(event) if response: yield response elif isinstance(event, QueueLLMChunkEvent | QueueAgentMessageEvent): @@ -304,6 +310,23 @@ class EasyUIBasedGenerateTaskPipeline(BasedGenerateTaskPipeline, MessageCycleMan delta_text = chunk.delta.message.content if delta_text is None: continue + if isinstance(chunk.delta.message.content, list): + delta_text = "" + for content in chunk.delta.message.content: + logger.debug( + "The content type %s in LLM chunk delta message content.: %r", type(content), content + ) + if isinstance(content, TextPromptMessageContent): + delta_text += content.data + elif isinstance(content, str): + delta_text += content # failback to str + else: + logger.warning( + "Unsupported content type %s in LLM chunk delta message content.: %r", + type(content), + content, + ) + continue if not self._task_state.llm_result.prompt_messages: self._task_state.llm_result.prompt_messages = chunk.prompt_messages @@ -318,7 +341,7 @@ class EasyUIBasedGenerateTaskPipeline(BasedGenerateTaskPipeline, MessageCycleMan self._task_state.llm_result.message.content = current_content if isinstance(event, QueueLLMChunkEvent): - yield self._message_to_stream_response( + yield self._message_cycle_manager.message_to_stream_response( answer=cast(str, delta_text), message_id=self._message_id, ) @@ -328,7 +351,7 @@ class EasyUIBasedGenerateTaskPipeline(BasedGenerateTaskPipeline, MessageCycleMan message_id=self._message_id, ) elif isinstance(event, QueueMessageReplaceEvent): - yield self._message_replace_to_stream_response(answer=event.text) + yield self._message_cycle_manager.message_replace_to_stream_response(answer=event.text) elif isinstance(event, QueuePingEvent): yield self._ping_stream_response() else: @@ -372,9 +395,7 @@ class EasyUIBasedGenerateTaskPipeline(BasedGenerateTaskPipeline, MessageCycleMan message.provider_response_latency = time.perf_counter() - self._start_at message.total_price = usage.total_price message.currency = usage.currency - message.message_metadata = ( - json.dumps(jsonable_encoder(self._task_state.metadata)) if self._task_state.metadata else None - ) + message.message_metadata = self._task_state.metadata.model_dump_json() if trace_manager: trace_manager.add_trace_task( @@ -423,16 +444,12 @@ class EasyUIBasedGenerateTaskPipeline(BasedGenerateTaskPipeline, MessageCycleMan Message end to stream response. :return: """ - self._task_state.metadata["usage"] = jsonable_encoder(self._task_state.llm_result.usage) - - extras = {} - if self._task_state.metadata: - extras["metadata"] = self._task_state.metadata - + self._task_state.metadata.usage = self._task_state.llm_result.usage + metadata_dict = self._task_state.metadata.model_dump() return MessageEndStreamResponse( task_id=self._application_generate_entity.task_id, id=self._message_id, - metadata=extras.get("metadata", {}), + metadata=metadata_dict, ) def _agent_message_to_stream_response(self, answer: str, message_id: str) -> AgentMessageStreamResponse: @@ -455,8 +472,6 @@ class EasyUIBasedGenerateTaskPipeline(BasedGenerateTaskPipeline, MessageCycleMan agent_thought: Optional[MessageAgentThought] = ( db.session.query(MessageAgentThought).filter(MessageAgentThought.id == event.agent_thought_id).first() ) - db.session.refresh(agent_thought) - db.session.close() if agent_thought: return AgentThoughtStreamResponse( diff --git a/api/core/app/task_pipeline/message_cycle_manage.py b/api/core/app/task_pipeline/message_cycle_manager.py similarity index 81% rename from api/core/app/task_pipeline/message_cycle_manage.py rename to api/core/app/task_pipeline/message_cycle_manager.py index 6223b33b67..2343081eaf 100644 --- a/api/core/app/task_pipeline/message_cycle_manage.py +++ b/api/core/app/task_pipeline/message_cycle_manager.py @@ -17,6 +17,8 @@ from core.app.entities.queue_entities import ( QueueRetrieverResourcesEvent, ) from core.app.entities.task_entities import ( + AnnotationReply, + AnnotationReplyAccount, EasyUITaskState, MessageFileStreamResponse, MessageReplaceStreamResponse, @@ -24,13 +26,13 @@ from core.app.entities.task_entities import ( WorkflowTaskState, ) from core.llm_generator.llm_generator import LLMGenerator -from core.tools.tool_file_manager import ToolFileManager +from core.tools.signature import sign_tool_file from extensions.ext_database import db from models.model import AppMode, Conversation, MessageAnnotation, MessageFile from services.annotation_service import AppAnnotationService -class MessageCycleManage: +class MessageCycleManager: def __init__( self, *, @@ -45,7 +47,7 @@ class MessageCycleManage: self._application_generate_entity = application_generate_entity self._task_state = task_state - def _generate_conversation_name(self, *, conversation_id: str, query: str) -> Optional[Thread]: + def generate_conversation_name(self, *, conversation_id: str, query: str) -> Optional[Thread]: """ Generate conversation name. :param conversation_id: conversation id @@ -102,7 +104,7 @@ class MessageCycleManage: db.session.commit() db.session.close() - def _handle_annotation_reply(self, event: QueueAnnotationReplyEvent) -> Optional[MessageAnnotation]: + def handle_annotation_reply(self, event: QueueAnnotationReplyEvent) -> Optional[MessageAnnotation]: """ Handle annotation reply. :param event: event @@ -111,25 +113,28 @@ class MessageCycleManage: annotation = AppAnnotationService.get_annotation_by_id(event.message_annotation_id) if annotation: account = annotation.account - self._task_state.metadata["annotation_reply"] = { - "id": annotation.id, - "account": {"id": annotation.account_id, "name": account.name if account else "Dify user"}, - } + self._task_state.metadata.annotation_reply = AnnotationReply( + id=annotation.id, + account=AnnotationReplyAccount( + id=annotation.account_id, + name=account.name if account else "Dify user", + ), + ) return annotation return None - def _handle_retriever_resources(self, event: QueueRetrieverResourcesEvent) -> None: + def handle_retriever_resources(self, event: QueueRetrieverResourcesEvent) -> None: """ Handle retriever resources. :param event: event :return: """ if self._application_generate_entity.app_config.additional_features.show_retrieve_source: - self._task_state.metadata["retriever_resources"] = event.retriever_resources + self._task_state.metadata.retriever_resources = event.retriever_resources - def _message_file_to_stream_response(self, event: QueueMessageFileEvent) -> Optional[MessageFileStreamResponse]: + def message_file_to_stream_response(self, event: QueueMessageFileEvent) -> Optional[MessageFileStreamResponse]: """ Message file to stream response. :param event: event @@ -154,7 +159,7 @@ class MessageCycleManage: if message_file.url.startswith("http"): url = message_file.url else: - url = ToolFileManager.sign_file(tool_file_id=tool_file_id, extension=extension) + url = sign_tool_file(tool_file_id=tool_file_id, extension=extension) return MessageFileStreamResponse( task_id=self._application_generate_entity.task_id, @@ -166,7 +171,7 @@ class MessageCycleManage: return None - def _message_to_stream_response( + def message_to_stream_response( self, answer: str, message_id: str, from_variable_selector: Optional[list[str]] = None ) -> MessageStreamResponse: """ @@ -182,10 +187,12 @@ class MessageCycleManage: from_variable_selector=from_variable_selector, ) - def _message_replace_to_stream_response(self, answer: str) -> MessageReplaceStreamResponse: + def message_replace_to_stream_response(self, answer: str, reason: str = "") -> MessageReplaceStreamResponse: """ Message replace to stream response. :param answer: answer :return: """ - return MessageReplaceStreamResponse(task_id=self._application_generate_entity.task_id, answer=answer) + return MessageReplaceStreamResponse( + task_id=self._application_generate_entity.task_id, answer=answer, reason=reason + ) diff --git a/api/core/app/task_pipeline/workflow_cycle_manage.py b/api/core/app/task_pipeline/workflow_cycle_manage.py deleted file mode 100644 index 5ce9f737d1..0000000000 --- a/api/core/app/task_pipeline/workflow_cycle_manage.py +++ /dev/null @@ -1,960 +0,0 @@ -import json -import time -from collections.abc import Mapping, Sequence -from datetime import UTC, datetime -from typing import Any, Optional, Union, cast -from uuid import uuid4 - -from sqlalchemy import func, select -from sqlalchemy.orm import Session, sessionmaker - -from core.app.entities.app_invoke_entities import AdvancedChatAppGenerateEntity, InvokeFrom, WorkflowAppGenerateEntity -from core.app.entities.queue_entities import ( - QueueAgentLogEvent, - QueueIterationCompletedEvent, - QueueIterationNextEvent, - QueueIterationStartEvent, - QueueLoopCompletedEvent, - QueueLoopNextEvent, - QueueLoopStartEvent, - QueueNodeExceptionEvent, - QueueNodeFailedEvent, - QueueNodeInIterationFailedEvent, - QueueNodeInLoopFailedEvent, - QueueNodeRetryEvent, - QueueNodeStartedEvent, - QueueNodeSucceededEvent, - QueueParallelBranchRunFailedEvent, - QueueParallelBranchRunStartedEvent, - QueueParallelBranchRunSucceededEvent, -) -from core.app.entities.task_entities import ( - AgentLogStreamResponse, - IterationNodeCompletedStreamResponse, - IterationNodeNextStreamResponse, - IterationNodeStartStreamResponse, - LoopNodeCompletedStreamResponse, - LoopNodeNextStreamResponse, - LoopNodeStartStreamResponse, - NodeFinishStreamResponse, - NodeRetryStreamResponse, - NodeStartStreamResponse, - ParallelBranchFinishedStreamResponse, - ParallelBranchStartStreamResponse, - WorkflowFinishStreamResponse, - WorkflowStartStreamResponse, -) -from core.app.task_pipeline.exc import WorkflowRunNotFoundError -from core.file import FILE_MODEL_IDENTITY, File -from core.model_runtime.utils.encoders import jsonable_encoder -from core.ops.entities.trace_entity import TraceTaskName -from core.ops.ops_trace_manager import TraceQueueManager, TraceTask -from core.repository import RepositoryFactory -from core.tools.tool_manager import ToolManager -from core.workflow.entities.node_entities import NodeRunMetadataKey -from core.workflow.enums import SystemVariableKey -from core.workflow.nodes import NodeType -from core.workflow.nodes.tool.entities import ToolNodeData -from core.workflow.workflow_entry import WorkflowEntry -from extensions.ext_database import db -from models.account import Account -from models.enums import CreatedByRole, WorkflowRunTriggeredFrom -from models.model import EndUser -from models.workflow import ( - Workflow, - WorkflowNodeExecution, - WorkflowNodeExecutionStatus, - WorkflowNodeExecutionTriggeredFrom, - WorkflowRun, - WorkflowRunStatus, -) - - -class WorkflowCycleManage: - def __init__( - self, - *, - application_generate_entity: Union[AdvancedChatAppGenerateEntity, WorkflowAppGenerateEntity], - workflow_system_variables: dict[SystemVariableKey, Any], - ) -> None: - self._workflow_run: WorkflowRun | None = None - self._workflow_node_executions: dict[str, WorkflowNodeExecution] = {} - self._application_generate_entity = application_generate_entity - self._workflow_system_variables = workflow_system_variables - - # Initialize the session factory and repository - # We use the global db engine instead of the session passed to methods - # Disable expire_on_commit to avoid the need for merging objects - self._session_factory = sessionmaker(bind=db.engine, expire_on_commit=False) - self._workflow_node_execution_repository = RepositoryFactory.create_workflow_node_execution_repository( - params={ - "tenant_id": self._application_generate_entity.app_config.tenant_id, - "app_id": self._application_generate_entity.app_config.app_id, - "session_factory": self._session_factory, - } - ) - - # We'll still keep the cache for backward compatibility and performance - # but use the repository for database operations - - def _handle_workflow_run_start( - self, - *, - session: Session, - workflow_id: str, - user_id: str, - created_by_role: CreatedByRole, - ) -> WorkflowRun: - workflow_stmt = select(Workflow).where(Workflow.id == workflow_id) - workflow = session.scalar(workflow_stmt) - if not workflow: - raise ValueError(f"Workflow not found: {workflow_id}") - - max_sequence_stmt = select(func.max(WorkflowRun.sequence_number)).where( - WorkflowRun.tenant_id == workflow.tenant_id, - WorkflowRun.app_id == workflow.app_id, - ) - max_sequence = session.scalar(max_sequence_stmt) or 0 - new_sequence_number = max_sequence + 1 - - inputs = {**self._application_generate_entity.inputs} - for key, value in (self._workflow_system_variables or {}).items(): - if key.value == "conversation": - continue - inputs[f"sys.{key.value}"] = value - - triggered_from = ( - WorkflowRunTriggeredFrom.DEBUGGING - if self._application_generate_entity.invoke_from == InvokeFrom.DEBUGGER - else WorkflowRunTriggeredFrom.APP_RUN - ) - - # handle special values - inputs = dict(WorkflowEntry.handle_special_values(inputs) or {}) - - # init workflow run - # TODO: This workflow_run_id should always not be None, maybe we can use a more elegant way to handle this - workflow_run_id = str(self._workflow_system_variables.get(SystemVariableKey.WORKFLOW_RUN_ID) or uuid4()) - - workflow_run = WorkflowRun() - workflow_run.id = workflow_run_id - workflow_run.tenant_id = workflow.tenant_id - workflow_run.app_id = workflow.app_id - workflow_run.sequence_number = new_sequence_number - workflow_run.workflow_id = workflow.id - workflow_run.type = workflow.type - workflow_run.triggered_from = triggered_from.value - workflow_run.version = workflow.version - workflow_run.graph = workflow.graph - workflow_run.inputs = json.dumps(inputs) - workflow_run.status = WorkflowRunStatus.RUNNING - workflow_run.created_by_role = created_by_role - workflow_run.created_by = user_id - workflow_run.created_at = datetime.now(UTC).replace(tzinfo=None) - - session.add(workflow_run) - - return workflow_run - - def _handle_workflow_run_success( - self, - *, - session: Session, - workflow_run_id: str, - start_at: float, - total_tokens: int, - total_steps: int, - outputs: Mapping[str, Any] | None = None, - conversation_id: Optional[str] = None, - trace_manager: Optional[TraceQueueManager] = None, - ) -> WorkflowRun: - """ - Workflow run success - :param workflow_run_id: workflow run id - :param start_at: start time - :param total_tokens: total tokens - :param total_steps: total steps - :param outputs: outputs - :param conversation_id: conversation id - :return: - """ - workflow_run = self._get_workflow_run(session=session, workflow_run_id=workflow_run_id) - - outputs = WorkflowEntry.handle_special_values(outputs) - - workflow_run.status = WorkflowRunStatus.SUCCEEDED - workflow_run.outputs = json.dumps(outputs or {}) - workflow_run.elapsed_time = time.perf_counter() - start_at - workflow_run.total_tokens = total_tokens - workflow_run.total_steps = total_steps - workflow_run.finished_at = datetime.now(UTC).replace(tzinfo=None) - - if trace_manager: - trace_manager.add_trace_task( - TraceTask( - TraceTaskName.WORKFLOW_TRACE, - workflow_run=workflow_run, - conversation_id=conversation_id, - user_id=trace_manager.user_id, - ) - ) - - return workflow_run - - def _handle_workflow_run_partial_success( - self, - *, - session: Session, - workflow_run_id: str, - start_at: float, - total_tokens: int, - total_steps: int, - outputs: Mapping[str, Any] | None = None, - exceptions_count: int = 0, - conversation_id: Optional[str] = None, - trace_manager: Optional[TraceQueueManager] = None, - ) -> WorkflowRun: - workflow_run = self._get_workflow_run(session=session, workflow_run_id=workflow_run_id) - outputs = WorkflowEntry.handle_special_values(dict(outputs) if outputs else None) - - workflow_run.status = WorkflowRunStatus.PARTIAL_SUCCEEDED.value - workflow_run.outputs = json.dumps(outputs or {}) - workflow_run.elapsed_time = time.perf_counter() - start_at - workflow_run.total_tokens = total_tokens - workflow_run.total_steps = total_steps - workflow_run.finished_at = datetime.now(UTC).replace(tzinfo=None) - workflow_run.exceptions_count = exceptions_count - - if trace_manager: - trace_manager.add_trace_task( - TraceTask( - TraceTaskName.WORKFLOW_TRACE, - workflow_run=workflow_run, - conversation_id=conversation_id, - user_id=trace_manager.user_id, - ) - ) - - return workflow_run - - def _handle_workflow_run_failed( - self, - *, - session: Session, - workflow_run_id: str, - start_at: float, - total_tokens: int, - total_steps: int, - status: WorkflowRunStatus, - error: str, - conversation_id: Optional[str] = None, - trace_manager: Optional[TraceQueueManager] = None, - exceptions_count: int = 0, - ) -> WorkflowRun: - """ - Workflow run failed - :param workflow_run_id: workflow run id - :param start_at: start time - :param total_tokens: total tokens - :param total_steps: total steps - :param status: status - :param error: error message - :return: - """ - workflow_run = self._get_workflow_run(session=session, workflow_run_id=workflow_run_id) - - workflow_run.status = status.value - workflow_run.error = error - workflow_run.elapsed_time = time.perf_counter() - start_at - workflow_run.total_tokens = total_tokens - workflow_run.total_steps = total_steps - workflow_run.finished_at = datetime.now(UTC).replace(tzinfo=None) - workflow_run.exceptions_count = exceptions_count - - # Use the instance repository to find running executions for a workflow run - running_workflow_node_executions = self._workflow_node_execution_repository.get_running_executions( - workflow_run_id=workflow_run.id - ) - - # Update the cache with the retrieved executions - for execution in running_workflow_node_executions: - if execution.node_execution_id: - self._workflow_node_executions[execution.node_execution_id] = execution - - for workflow_node_execution in running_workflow_node_executions: - now = datetime.now(UTC).replace(tzinfo=None) - workflow_node_execution.status = WorkflowNodeExecutionStatus.FAILED.value - workflow_node_execution.error = error - workflow_node_execution.finished_at = now - workflow_node_execution.elapsed_time = (now - workflow_node_execution.created_at).total_seconds() - - if trace_manager: - trace_manager.add_trace_task( - TraceTask( - TraceTaskName.WORKFLOW_TRACE, - workflow_run=workflow_run, - conversation_id=conversation_id, - user_id=trace_manager.user_id, - ) - ) - - return workflow_run - - def _handle_node_execution_start( - self, *, workflow_run: WorkflowRun, event: QueueNodeStartedEvent - ) -> WorkflowNodeExecution: - workflow_node_execution = WorkflowNodeExecution() - workflow_node_execution.id = str(uuid4()) - workflow_node_execution.tenant_id = workflow_run.tenant_id - workflow_node_execution.app_id = workflow_run.app_id - workflow_node_execution.workflow_id = workflow_run.workflow_id - workflow_node_execution.triggered_from = WorkflowNodeExecutionTriggeredFrom.WORKFLOW_RUN.value - workflow_node_execution.workflow_run_id = workflow_run.id - workflow_node_execution.predecessor_node_id = event.predecessor_node_id - workflow_node_execution.index = event.node_run_index - workflow_node_execution.node_execution_id = event.node_execution_id - workflow_node_execution.node_id = event.node_id - workflow_node_execution.node_type = event.node_type.value - workflow_node_execution.title = event.node_data.title - workflow_node_execution.status = WorkflowNodeExecutionStatus.RUNNING.value - workflow_node_execution.created_by_role = workflow_run.created_by_role - workflow_node_execution.created_by = workflow_run.created_by - workflow_node_execution.execution_metadata = json.dumps( - { - NodeRunMetadataKey.PARALLEL_MODE_RUN_ID: event.parallel_mode_run_id, - NodeRunMetadataKey.ITERATION_ID: event.in_iteration_id, - NodeRunMetadataKey.LOOP_ID: event.in_loop_id, - } - ) - workflow_node_execution.created_at = datetime.now(UTC).replace(tzinfo=None) - - # Use the instance repository to save the workflow node execution - self._workflow_node_execution_repository.save(workflow_node_execution) - - self._workflow_node_executions[event.node_execution_id] = workflow_node_execution - return workflow_node_execution - - def _handle_workflow_node_execution_success(self, *, event: QueueNodeSucceededEvent) -> WorkflowNodeExecution: - workflow_node_execution = self._get_workflow_node_execution(node_execution_id=event.node_execution_id) - inputs = WorkflowEntry.handle_special_values(event.inputs) - process_data = WorkflowEntry.handle_special_values(event.process_data) - outputs = WorkflowEntry.handle_special_values(event.outputs) - execution_metadata_dict = dict(event.execution_metadata or {}) - execution_metadata = json.dumps(jsonable_encoder(execution_metadata_dict)) if execution_metadata_dict else None - finished_at = datetime.now(UTC).replace(tzinfo=None) - elapsed_time = (finished_at - event.start_at).total_seconds() - - process_data = WorkflowEntry.handle_special_values(event.process_data) - - workflow_node_execution.status = WorkflowNodeExecutionStatus.SUCCEEDED.value - workflow_node_execution.inputs = json.dumps(inputs) if inputs else None - workflow_node_execution.process_data = json.dumps(process_data) if process_data else None - workflow_node_execution.outputs = json.dumps(outputs) if outputs else None - workflow_node_execution.execution_metadata = execution_metadata - workflow_node_execution.finished_at = finished_at - workflow_node_execution.elapsed_time = elapsed_time - - # Use the instance repository to update the workflow node execution - self._workflow_node_execution_repository.update(workflow_node_execution) - return workflow_node_execution - - def _handle_workflow_node_execution_failed( - self, - *, - event: QueueNodeFailedEvent - | QueueNodeInIterationFailedEvent - | QueueNodeInLoopFailedEvent - | QueueNodeExceptionEvent, - ) -> WorkflowNodeExecution: - """ - Workflow node execution failed - :param event: queue node failed event - :return: - """ - workflow_node_execution = self._get_workflow_node_execution(node_execution_id=event.node_execution_id) - - inputs = WorkflowEntry.handle_special_values(event.inputs) - process_data = WorkflowEntry.handle_special_values(event.process_data) - outputs = WorkflowEntry.handle_special_values(event.outputs) - finished_at = datetime.now(UTC).replace(tzinfo=None) - elapsed_time = (finished_at - event.start_at).total_seconds() - execution_metadata = ( - json.dumps(jsonable_encoder(event.execution_metadata)) if event.execution_metadata else None - ) - process_data = WorkflowEntry.handle_special_values(event.process_data) - workflow_node_execution.status = ( - WorkflowNodeExecutionStatus.FAILED.value - if not isinstance(event, QueueNodeExceptionEvent) - else WorkflowNodeExecutionStatus.EXCEPTION.value - ) - workflow_node_execution.error = event.error - workflow_node_execution.inputs = json.dumps(inputs) if inputs else None - workflow_node_execution.process_data = json.dumps(process_data) if process_data else None - workflow_node_execution.outputs = json.dumps(outputs) if outputs else None - workflow_node_execution.finished_at = finished_at - workflow_node_execution.elapsed_time = elapsed_time - workflow_node_execution.execution_metadata = execution_metadata - - return workflow_node_execution - - def _handle_workflow_node_execution_retried( - self, *, workflow_run: WorkflowRun, event: QueueNodeRetryEvent - ) -> WorkflowNodeExecution: - """ - Workflow node execution failed - :param workflow_run: workflow run - :param event: queue node failed event - :return: - """ - created_at = event.start_at - finished_at = datetime.now(UTC).replace(tzinfo=None) - elapsed_time = (finished_at - created_at).total_seconds() - inputs = WorkflowEntry.handle_special_values(event.inputs) - outputs = WorkflowEntry.handle_special_values(event.outputs) - origin_metadata = { - NodeRunMetadataKey.ITERATION_ID: event.in_iteration_id, - NodeRunMetadataKey.PARALLEL_MODE_RUN_ID: event.parallel_mode_run_id, - NodeRunMetadataKey.LOOP_ID: event.in_loop_id, - } - merged_metadata = ( - {**jsonable_encoder(event.execution_metadata), **origin_metadata} - if event.execution_metadata is not None - else origin_metadata - ) - execution_metadata = json.dumps(merged_metadata) - - workflow_node_execution = WorkflowNodeExecution() - workflow_node_execution.id = str(uuid4()) - workflow_node_execution.tenant_id = workflow_run.tenant_id - workflow_node_execution.app_id = workflow_run.app_id - workflow_node_execution.workflow_id = workflow_run.workflow_id - workflow_node_execution.triggered_from = WorkflowNodeExecutionTriggeredFrom.WORKFLOW_RUN.value - workflow_node_execution.workflow_run_id = workflow_run.id - workflow_node_execution.predecessor_node_id = event.predecessor_node_id - workflow_node_execution.node_execution_id = event.node_execution_id - workflow_node_execution.node_id = event.node_id - workflow_node_execution.node_type = event.node_type.value - workflow_node_execution.title = event.node_data.title - workflow_node_execution.status = WorkflowNodeExecutionStatus.RETRY.value - workflow_node_execution.created_by_role = workflow_run.created_by_role - workflow_node_execution.created_by = workflow_run.created_by - workflow_node_execution.created_at = created_at - workflow_node_execution.finished_at = finished_at - workflow_node_execution.elapsed_time = elapsed_time - workflow_node_execution.error = event.error - workflow_node_execution.inputs = json.dumps(inputs) if inputs else None - workflow_node_execution.outputs = json.dumps(outputs) if outputs else None - workflow_node_execution.execution_metadata = execution_metadata - workflow_node_execution.index = event.node_run_index - - # Use the instance repository to save the workflow node execution - self._workflow_node_execution_repository.save(workflow_node_execution) - - self._workflow_node_executions[event.node_execution_id] = workflow_node_execution - return workflow_node_execution - - def _workflow_start_to_stream_response( - self, - *, - session: Session, - task_id: str, - workflow_run: WorkflowRun, - ) -> WorkflowStartStreamResponse: - _ = session - return WorkflowStartStreamResponse( - task_id=task_id, - workflow_run_id=workflow_run.id, - data=WorkflowStartStreamResponse.Data( - id=workflow_run.id, - workflow_id=workflow_run.workflow_id, - sequence_number=workflow_run.sequence_number, - inputs=dict(workflow_run.inputs_dict or {}), - created_at=int(workflow_run.created_at.timestamp()), - ), - ) - - def _workflow_finish_to_stream_response( - self, - *, - session: Session, - task_id: str, - workflow_run: WorkflowRun, - ) -> WorkflowFinishStreamResponse: - created_by = None - if workflow_run.created_by_role == CreatedByRole.ACCOUNT: - stmt = select(Account).where(Account.id == workflow_run.created_by) - account = session.scalar(stmt) - if account: - created_by = { - "id": account.id, - "name": account.name, - "email": account.email, - } - elif workflow_run.created_by_role == CreatedByRole.END_USER: - stmt = select(EndUser).where(EndUser.id == workflow_run.created_by) - end_user = session.scalar(stmt) - if end_user: - created_by = { - "id": end_user.id, - "user": end_user.session_id, - } - else: - raise NotImplementedError(f"unknown created_by_role: {workflow_run.created_by_role}") - - return WorkflowFinishStreamResponse( - task_id=task_id, - workflow_run_id=workflow_run.id, - data=WorkflowFinishStreamResponse.Data( - id=workflow_run.id, - workflow_id=workflow_run.workflow_id, - sequence_number=workflow_run.sequence_number, - status=workflow_run.status, - outputs=dict(workflow_run.outputs_dict) if workflow_run.outputs_dict else None, - error=workflow_run.error, - elapsed_time=workflow_run.elapsed_time, - total_tokens=workflow_run.total_tokens, - total_steps=workflow_run.total_steps, - created_by=created_by, - created_at=int(workflow_run.created_at.timestamp()), - finished_at=int(workflow_run.finished_at.timestamp()), - files=self._fetch_files_from_node_outputs(dict(workflow_run.outputs_dict)), - exceptions_count=workflow_run.exceptions_count, - ), - ) - - def _workflow_node_start_to_stream_response( - self, - *, - event: QueueNodeStartedEvent, - task_id: str, - workflow_node_execution: WorkflowNodeExecution, - ) -> Optional[NodeStartStreamResponse]: - if workflow_node_execution.node_type in {NodeType.ITERATION.value, NodeType.LOOP.value}: - return None - if not workflow_node_execution.workflow_run_id: - return None - - response = NodeStartStreamResponse( - task_id=task_id, - workflow_run_id=workflow_node_execution.workflow_run_id, - data=NodeStartStreamResponse.Data( - id=workflow_node_execution.id, - node_id=workflow_node_execution.node_id, - node_type=workflow_node_execution.node_type, - title=workflow_node_execution.title, - index=workflow_node_execution.index, - predecessor_node_id=workflow_node_execution.predecessor_node_id, - inputs=workflow_node_execution.inputs_dict, - created_at=int(workflow_node_execution.created_at.timestamp()), - parallel_id=event.parallel_id, - parallel_start_node_id=event.parallel_start_node_id, - parent_parallel_id=event.parent_parallel_id, - parent_parallel_start_node_id=event.parent_parallel_start_node_id, - iteration_id=event.in_iteration_id, - loop_id=event.in_loop_id, - parallel_run_id=event.parallel_mode_run_id, - agent_strategy=event.agent_strategy, - ), - ) - - # extras logic - if event.node_type == NodeType.TOOL: - node_data = cast(ToolNodeData, event.node_data) - response.data.extras["icon"] = ToolManager.get_tool_icon( - tenant_id=self._application_generate_entity.app_config.tenant_id, - provider_type=node_data.provider_type, - provider_id=node_data.provider_id, - ) - - return response - - def _workflow_node_finish_to_stream_response( - self, - *, - event: QueueNodeSucceededEvent - | QueueNodeFailedEvent - | QueueNodeInIterationFailedEvent - | QueueNodeInLoopFailedEvent - | QueueNodeExceptionEvent, - task_id: str, - workflow_node_execution: WorkflowNodeExecution, - ) -> Optional[NodeFinishStreamResponse]: - if workflow_node_execution.node_type in {NodeType.ITERATION.value, NodeType.LOOP.value}: - return None - if not workflow_node_execution.workflow_run_id: - return None - if not workflow_node_execution.finished_at: - return None - - return NodeFinishStreamResponse( - task_id=task_id, - workflow_run_id=workflow_node_execution.workflow_run_id, - data=NodeFinishStreamResponse.Data( - id=workflow_node_execution.id, - node_id=workflow_node_execution.node_id, - node_type=workflow_node_execution.node_type, - index=workflow_node_execution.index, - title=workflow_node_execution.title, - predecessor_node_id=workflow_node_execution.predecessor_node_id, - inputs=workflow_node_execution.inputs_dict, - process_data=workflow_node_execution.process_data_dict, - outputs=workflow_node_execution.outputs_dict, - status=workflow_node_execution.status, - error=workflow_node_execution.error, - elapsed_time=workflow_node_execution.elapsed_time, - execution_metadata=workflow_node_execution.execution_metadata_dict, - created_at=int(workflow_node_execution.created_at.timestamp()), - finished_at=int(workflow_node_execution.finished_at.timestamp()), - files=self._fetch_files_from_node_outputs(workflow_node_execution.outputs_dict or {}), - parallel_id=event.parallel_id, - parallel_start_node_id=event.parallel_start_node_id, - parent_parallel_id=event.parent_parallel_id, - parent_parallel_start_node_id=event.parent_parallel_start_node_id, - iteration_id=event.in_iteration_id, - loop_id=event.in_loop_id, - ), - ) - - def _workflow_node_retry_to_stream_response( - self, - *, - event: QueueNodeRetryEvent, - task_id: str, - workflow_node_execution: WorkflowNodeExecution, - ) -> Optional[Union[NodeRetryStreamResponse, NodeFinishStreamResponse]]: - if workflow_node_execution.node_type in {NodeType.ITERATION.value, NodeType.LOOP.value}: - return None - if not workflow_node_execution.workflow_run_id: - return None - if not workflow_node_execution.finished_at: - return None - - return NodeRetryStreamResponse( - task_id=task_id, - workflow_run_id=workflow_node_execution.workflow_run_id, - data=NodeRetryStreamResponse.Data( - id=workflow_node_execution.id, - node_id=workflow_node_execution.node_id, - node_type=workflow_node_execution.node_type, - index=workflow_node_execution.index, - title=workflow_node_execution.title, - predecessor_node_id=workflow_node_execution.predecessor_node_id, - inputs=workflow_node_execution.inputs_dict, - process_data=workflow_node_execution.process_data_dict, - outputs=workflow_node_execution.outputs_dict, - status=workflow_node_execution.status, - error=workflow_node_execution.error, - elapsed_time=workflow_node_execution.elapsed_time, - execution_metadata=workflow_node_execution.execution_metadata_dict, - created_at=int(workflow_node_execution.created_at.timestamp()), - finished_at=int(workflow_node_execution.finished_at.timestamp()), - files=self._fetch_files_from_node_outputs(workflow_node_execution.outputs_dict or {}), - parallel_id=event.parallel_id, - parallel_start_node_id=event.parallel_start_node_id, - parent_parallel_id=event.parent_parallel_id, - parent_parallel_start_node_id=event.parent_parallel_start_node_id, - iteration_id=event.in_iteration_id, - loop_id=event.in_loop_id, - retry_index=event.retry_index, - ), - ) - - def _workflow_parallel_branch_start_to_stream_response( - self, *, session: Session, task_id: str, workflow_run: WorkflowRun, event: QueueParallelBranchRunStartedEvent - ) -> ParallelBranchStartStreamResponse: - _ = session - return ParallelBranchStartStreamResponse( - task_id=task_id, - workflow_run_id=workflow_run.id, - data=ParallelBranchStartStreamResponse.Data( - parallel_id=event.parallel_id, - parallel_branch_id=event.parallel_start_node_id, - parent_parallel_id=event.parent_parallel_id, - parent_parallel_start_node_id=event.parent_parallel_start_node_id, - iteration_id=event.in_iteration_id, - loop_id=event.in_loop_id, - created_at=int(time.time()), - ), - ) - - def _workflow_parallel_branch_finished_to_stream_response( - self, - *, - session: Session, - task_id: str, - workflow_run: WorkflowRun, - event: QueueParallelBranchRunSucceededEvent | QueueParallelBranchRunFailedEvent, - ) -> ParallelBranchFinishedStreamResponse: - _ = session - return ParallelBranchFinishedStreamResponse( - task_id=task_id, - workflow_run_id=workflow_run.id, - data=ParallelBranchFinishedStreamResponse.Data( - parallel_id=event.parallel_id, - parallel_branch_id=event.parallel_start_node_id, - parent_parallel_id=event.parent_parallel_id, - parent_parallel_start_node_id=event.parent_parallel_start_node_id, - iteration_id=event.in_iteration_id, - loop_id=event.in_loop_id, - status="succeeded" if isinstance(event, QueueParallelBranchRunSucceededEvent) else "failed", - error=event.error if isinstance(event, QueueParallelBranchRunFailedEvent) else None, - created_at=int(time.time()), - ), - ) - - def _workflow_iteration_start_to_stream_response( - self, *, session: Session, task_id: str, workflow_run: WorkflowRun, event: QueueIterationStartEvent - ) -> IterationNodeStartStreamResponse: - _ = session - return IterationNodeStartStreamResponse( - task_id=task_id, - workflow_run_id=workflow_run.id, - data=IterationNodeStartStreamResponse.Data( - id=event.node_id, - node_id=event.node_id, - node_type=event.node_type.value, - title=event.node_data.title, - created_at=int(time.time()), - extras={}, - inputs=event.inputs or {}, - metadata=event.metadata or {}, - parallel_id=event.parallel_id, - parallel_start_node_id=event.parallel_start_node_id, - ), - ) - - def _workflow_iteration_next_to_stream_response( - self, *, session: Session, task_id: str, workflow_run: WorkflowRun, event: QueueIterationNextEvent - ) -> IterationNodeNextStreamResponse: - _ = session - return IterationNodeNextStreamResponse( - task_id=task_id, - workflow_run_id=workflow_run.id, - data=IterationNodeNextStreamResponse.Data( - id=event.node_id, - node_id=event.node_id, - node_type=event.node_type.value, - title=event.node_data.title, - index=event.index, - pre_iteration_output=event.output, - created_at=int(time.time()), - extras={}, - parallel_id=event.parallel_id, - parallel_start_node_id=event.parallel_start_node_id, - parallel_mode_run_id=event.parallel_mode_run_id, - duration=event.duration, - ), - ) - - def _workflow_iteration_completed_to_stream_response( - self, *, session: Session, task_id: str, workflow_run: WorkflowRun, event: QueueIterationCompletedEvent - ) -> IterationNodeCompletedStreamResponse: - _ = session - return IterationNodeCompletedStreamResponse( - task_id=task_id, - workflow_run_id=workflow_run.id, - data=IterationNodeCompletedStreamResponse.Data( - id=event.node_id, - node_id=event.node_id, - node_type=event.node_type.value, - title=event.node_data.title, - outputs=event.outputs, - created_at=int(time.time()), - extras={}, - inputs=event.inputs or {}, - status=WorkflowNodeExecutionStatus.SUCCEEDED - if event.error is None - else WorkflowNodeExecutionStatus.FAILED, - error=None, - elapsed_time=(datetime.now(UTC).replace(tzinfo=None) - event.start_at).total_seconds(), - total_tokens=event.metadata.get("total_tokens", 0) if event.metadata else 0, - execution_metadata=event.metadata, - finished_at=int(time.time()), - steps=event.steps, - parallel_id=event.parallel_id, - parallel_start_node_id=event.parallel_start_node_id, - ), - ) - - def _workflow_loop_start_to_stream_response( - self, *, session: Session, task_id: str, workflow_run: WorkflowRun, event: QueueLoopStartEvent - ) -> LoopNodeStartStreamResponse: - _ = session - return LoopNodeStartStreamResponse( - task_id=task_id, - workflow_run_id=workflow_run.id, - data=LoopNodeStartStreamResponse.Data( - id=event.node_id, - node_id=event.node_id, - node_type=event.node_type.value, - title=event.node_data.title, - created_at=int(time.time()), - extras={}, - inputs=event.inputs or {}, - metadata=event.metadata or {}, - parallel_id=event.parallel_id, - parallel_start_node_id=event.parallel_start_node_id, - ), - ) - - def _workflow_loop_next_to_stream_response( - self, *, session: Session, task_id: str, workflow_run: WorkflowRun, event: QueueLoopNextEvent - ) -> LoopNodeNextStreamResponse: - _ = session - return LoopNodeNextStreamResponse( - task_id=task_id, - workflow_run_id=workflow_run.id, - data=LoopNodeNextStreamResponse.Data( - id=event.node_id, - node_id=event.node_id, - node_type=event.node_type.value, - title=event.node_data.title, - index=event.index, - pre_loop_output=event.output, - created_at=int(time.time()), - extras={}, - parallel_id=event.parallel_id, - parallel_start_node_id=event.parallel_start_node_id, - parallel_mode_run_id=event.parallel_mode_run_id, - duration=event.duration, - ), - ) - - def _workflow_loop_completed_to_stream_response( - self, *, session: Session, task_id: str, workflow_run: WorkflowRun, event: QueueLoopCompletedEvent - ) -> LoopNodeCompletedStreamResponse: - _ = session - return LoopNodeCompletedStreamResponse( - task_id=task_id, - workflow_run_id=workflow_run.id, - data=LoopNodeCompletedStreamResponse.Data( - id=event.node_id, - node_id=event.node_id, - node_type=event.node_type.value, - title=event.node_data.title, - outputs=event.outputs, - created_at=int(time.time()), - extras={}, - inputs=event.inputs or {}, - status=WorkflowNodeExecutionStatus.SUCCEEDED - if event.error is None - else WorkflowNodeExecutionStatus.FAILED, - error=None, - elapsed_time=(datetime.now(UTC).replace(tzinfo=None) - event.start_at).total_seconds(), - total_tokens=event.metadata.get("total_tokens", 0) if event.metadata else 0, - execution_metadata=event.metadata, - finished_at=int(time.time()), - steps=event.steps, - parallel_id=event.parallel_id, - parallel_start_node_id=event.parallel_start_node_id, - ), - ) - - def _fetch_files_from_node_outputs(self, outputs_dict: Mapping[str, Any]) -> Sequence[Mapping[str, Any]]: - """ - Fetch files from node outputs - :param outputs_dict: node outputs dict - :return: - """ - if not outputs_dict: - return [] - - files = [self._fetch_files_from_variable_value(output_value) for output_value in outputs_dict.values()] - # Remove None - files = [file for file in files if file] - # Flatten list - # Flatten the list of sequences into a single list of mappings - flattened_files = [file for sublist in files if sublist for file in sublist] - - # Convert to tuple to match Sequence type - return tuple(flattened_files) - - def _fetch_files_from_variable_value(self, value: Union[dict, list]) -> Sequence[Mapping[str, Any]]: - """ - Fetch files from variable value - :param value: variable value - :return: - """ - if not value: - return [] - - files = [] - if isinstance(value, list): - for item in value: - file = self._get_file_var_from_value(item) - if file: - files.append(file) - elif isinstance(value, dict): - file = self._get_file_var_from_value(value) - if file: - files.append(file) - - return files - - def _get_file_var_from_value(self, value: Union[dict, list]) -> Mapping[str, Any] | None: - """ - Get file var from value - :param value: variable value - :return: - """ - if not value: - return None - - if isinstance(value, dict) and value.get("dify_model_identity") == FILE_MODEL_IDENTITY: - return value - elif isinstance(value, File): - return value.to_dict() - - return None - - def _get_workflow_run(self, *, session: Session, workflow_run_id: str) -> WorkflowRun: - if self._workflow_run and self._workflow_run.id == workflow_run_id: - cached_workflow_run = self._workflow_run - cached_workflow_run = session.merge(cached_workflow_run) - return cached_workflow_run - stmt = select(WorkflowRun).where(WorkflowRun.id == workflow_run_id) - workflow_run = session.scalar(stmt) - if not workflow_run: - raise WorkflowRunNotFoundError(workflow_run_id) - self._workflow_run = workflow_run - - return workflow_run - - def _get_workflow_node_execution(self, node_execution_id: str) -> WorkflowNodeExecution: - # First check the cache for performance - if node_execution_id in self._workflow_node_executions: - cached_execution = self._workflow_node_executions[node_execution_id] - # No need to merge with session since expire_on_commit=False - return cached_execution - - # If not in cache, use the instance repository to get by node_execution_id - execution = self._workflow_node_execution_repository.get_by_node_execution_id(node_execution_id) - - if not execution: - raise ValueError(f"Workflow node execution not found: {node_execution_id}") - - # Update cache - self._workflow_node_executions[node_execution_id] = execution - return execution - - def _handle_agent_log(self, task_id: str, event: QueueAgentLogEvent) -> AgentLogStreamResponse: - """ - Handle agent log - :param task_id: task id - :param event: agent log event - :return: - """ - return AgentLogStreamResponse( - task_id=task_id, - data=AgentLogStreamResponse.Data( - node_execution_id=event.node_execution_id, - id=event.id, - parent_id=event.parent_id, - label=event.label, - error=event.error, - status=event.status, - data=event.data, - metadata=event.metadata, - node_id=event.node_id, - ), - ) diff --git a/api/core/base/__init__.py b/api/core/base/__init__.py new file mode 100644 index 0000000000..3f4bd3b771 --- /dev/null +++ b/api/core/base/__init__.py @@ -0,0 +1 @@ +# Core base package diff --git a/api/core/base/tts/__init__.py b/api/core/base/tts/__init__.py new file mode 100644 index 0000000000..37b6eeebb0 --- /dev/null +++ b/api/core/base/tts/__init__.py @@ -0,0 +1,6 @@ +from core.base.tts.app_generator_tts_publisher import AppGeneratorTTSPublisher, AudioTrunk + +__all__ = [ + "AppGeneratorTTSPublisher", + "AudioTrunk", +] diff --git a/api/core/app/apps/advanced_chat/app_generator_tts_publisher.py b/api/core/base/tts/app_generator_tts_publisher.py similarity index 100% rename from api/core/app/apps/advanced_chat/app_generator_tts_publisher.py rename to api/core/base/tts/app_generator_tts_publisher.py diff --git a/api/core/callback_handler/index_tool_callback_handler.py b/api/core/callback_handler/index_tool_callback_handler.py index 56859df7f4..a3a7b4b812 100644 --- a/api/core/callback_handler/index_tool_callback_handler.py +++ b/api/core/callback_handler/index_tool_callback_handler.py @@ -1,12 +1,18 @@ +import logging +from collections.abc import Sequence + from core.app.apps.base_app_queue_manager import AppQueueManager, PublishFrom from core.app.entities.app_invoke_entities import InvokeFrom from core.app.entities.queue_entities import QueueRetrieverResourcesEvent +from core.rag.entities.citation_metadata import RetrievalSourceMetadata from core.rag.index_processor.constant.index_type import IndexType from core.rag.models.document import Document from extensions.ext_database import db from models.dataset import ChildChunk, DatasetQuery, DocumentSegment from models.dataset import Document as DatasetDocument +_logger = logging.getLogger(__name__) + class DatasetIndexToolCallbackHandler: """Callback handler for dataset tool.""" @@ -42,18 +48,31 @@ class DatasetIndexToolCallbackHandler: """Handle tool end.""" for document in documents: if document.metadata is not None: - dataset_document = DatasetDocument.query.filter( - DatasetDocument.id == document.metadata["document_id"] - ).first() + document_id = document.metadata["document_id"] + dataset_document = db.session.query(DatasetDocument).filter(DatasetDocument.id == document_id).first() + if not dataset_document: + _logger.warning( + "Expected DatasetDocument record to exist, but none was found, document_id=%s", + document_id, + ) + continue if dataset_document.doc_form == IndexType.PARENT_CHILD_INDEX: - child_chunk = ChildChunk.query.filter( - ChildChunk.index_node_id == document.metadata["doc_id"], - ChildChunk.dataset_id == dataset_document.dataset_id, - ChildChunk.document_id == dataset_document.id, - ).first() + child_chunk = ( + db.session.query(ChildChunk) + .filter( + ChildChunk.index_node_id == document.metadata["doc_id"], + ChildChunk.dataset_id == dataset_document.dataset_id, + ChildChunk.document_id == dataset_document.id, + ) + .first() + ) if child_chunk: - segment = DocumentSegment.query.filter(DocumentSegment.id == child_chunk.segment_id).update( - {DocumentSegment.hit_count: DocumentSegment.hit_count + 1}, synchronize_session=False + segment = ( + db.session.query(DocumentSegment) + .filter(DocumentSegment.id == child_chunk.segment_id) + .update( + {DocumentSegment.hit_count: DocumentSegment.hit_count + 1}, synchronize_session=False + ) ) else: query = db.session.query(DocumentSegment).filter( @@ -68,7 +87,8 @@ class DatasetIndexToolCallbackHandler: db.session.commit() - def return_retriever_resource_info(self, resource: list): + # TODO(-LAN-): Improve type check + def return_retriever_resource_info(self, resource: Sequence[RetrievalSourceMetadata]): """Handle return_retriever_resource_info.""" self._queue_manager.publish( QueueRetrieverResourcesEvent(retriever_resources=resource), PublishFrom.APPLICATION_MANAGER diff --git a/api/core/entities/model_entities.py b/api/core/entities/model_entities.py index 5017835565..e1c021a44a 100644 --- a/api/core/entities/model_entities.py +++ b/api/core/entities/model_entities.py @@ -55,6 +55,25 @@ class ProviderModelWithStatusEntity(ProviderModel): status: ModelStatus load_balancing_enabled: bool = False + def raise_for_status(self) -> None: + """ + Check model status and raise ValueError if not active. + + :raises ValueError: When model status is not active, with a descriptive message + """ + if self.status == ModelStatus.ACTIVE: + return + + error_messages = { + ModelStatus.NO_CONFIGURE: "Model is not configured", + ModelStatus.QUOTA_EXCEEDED: "Model quota has been exceeded", + ModelStatus.NO_PERMISSION: "No permission to use this model", + ModelStatus.DISABLED: "Model is disabled", + } + + if self.status in error_messages: + raise ValueError(error_messages[self.status]) + class ModelWithProviderEntity(ProviderModelWithStatusEntity): """ diff --git a/api/core/entities/provider_configuration.py b/api/core/entities/provider_configuration.py index b3affc91a6..66d8d0f414 100644 --- a/api/core/entities/provider_configuration.py +++ b/api/core/entities/provider_configuration.py @@ -754,7 +754,7 @@ class ProviderConfiguration(BaseModel): :param only_active: return active model only :return: """ - provider_models = self.get_provider_models(model_type, only_active) + provider_models = self.get_provider_models(model_type, only_active, model) for provider_model in provider_models: if provider_model.model == model: @@ -763,12 +763,13 @@ class ProviderConfiguration(BaseModel): return None def get_provider_models( - self, model_type: Optional[ModelType] = None, only_active: bool = False + self, model_type: Optional[ModelType] = None, only_active: bool = False, model: Optional[str] = None ) -> list[ModelWithProviderEntity]: """ Get provider models. :param model_type: model type :param only_active: only active models + :param model: model name :return: """ model_provider_factory = ModelProviderFactory(self.tenant_id) @@ -791,14 +792,35 @@ class ProviderConfiguration(BaseModel): ) else: provider_models = self._get_custom_provider_models( - model_types=model_types, provider_schema=provider_schema, model_setting_map=model_setting_map + model_types=model_types, + provider_schema=provider_schema, + model_setting_map=model_setting_map, + model=model, ) if only_active: provider_models = [m for m in provider_models if m.status == ModelStatus.ACTIVE] # resort provider_models - return sorted(provider_models, key=lambda x: x.model_type.value) + # Optimize sorting logic: first sort by provider.position order, then by model_type.value + # Get the position list for model types (retrieve only once for better performance) + model_type_positions = {} + if hasattr(self.provider, "position") and self.provider.position: + model_type_positions = self.provider.position + + def get_sort_key(model: ModelWithProviderEntity): + # Get the position list for the current model type + positions = model_type_positions.get(model.model_type.value, []) + + # If the model name is in the position list, use its index for sorting + # Otherwise use a large value (list length) to place undefined models at the end + position_index = positions.index(model.model) if model.model in positions else len(positions) + + # Return composite sort key: (model_type value, model position index) + return (model.model_type.value, position_index) + + # Sort using the composite sort key + return sorted(provider_models, key=get_sort_key) def _get_system_provider_models( self, @@ -879,37 +901,36 @@ class ProviderConfiguration(BaseModel): ) except Exception as ex: logger.warning(f"get custom model schema failed, {ex}") - - if not custom_model_schema: - continue - - if custom_model_schema.model_type not in model_types: - continue - - status = ModelStatus.ACTIVE - if ( - custom_model_schema.model_type in model_setting_map - and custom_model_schema.model in model_setting_map[custom_model_schema.model_type] - ): - model_setting = model_setting_map[custom_model_schema.model_type][ - custom_model_schema.model - ] - if model_setting.enabled is False: - status = ModelStatus.DISABLED - - provider_models.append( - ModelWithProviderEntity( - model=custom_model_schema.model, - label=custom_model_schema.label, - model_type=custom_model_schema.model_type, - features=custom_model_schema.features, - fetch_from=FetchFrom.PREDEFINED_MODEL, - model_properties=custom_model_schema.model_properties, - deprecated=custom_model_schema.deprecated, - provider=SimpleModelProviderEntity(self.provider), - status=status, - ) + continue + + if not custom_model_schema: + continue + + if custom_model_schema.model_type not in model_types: + continue + + status = ModelStatus.ACTIVE + if ( + custom_model_schema.model_type in model_setting_map + and custom_model_schema.model in model_setting_map[custom_model_schema.model_type] + ): + model_setting = model_setting_map[custom_model_schema.model_type][custom_model_schema.model] + if model_setting.enabled is False: + status = ModelStatus.DISABLED + + provider_models.append( + ModelWithProviderEntity( + model=custom_model_schema.model, + label=custom_model_schema.label, + model_type=custom_model_schema.model_type, + features=custom_model_schema.features, + fetch_from=FetchFrom.PREDEFINED_MODEL, + model_properties=custom_model_schema.model_properties, + deprecated=custom_model_schema.deprecated, + provider=SimpleModelProviderEntity(self.provider), + status=status, ) + ) # if llm name not in restricted llm list, remove it restrict_model_names = [rm.model for rm in restrict_models] @@ -926,6 +947,7 @@ class ProviderConfiguration(BaseModel): model_types: Sequence[ModelType], provider_schema: ProviderEntity, model_setting_map: dict[ModelType, dict[str, ModelSettings]], + model: Optional[str] = None, ) -> list[ModelWithProviderEntity]: """ Get custom provider models. @@ -978,7 +1000,8 @@ class ProviderConfiguration(BaseModel): for model_configuration in self.custom_configuration.models: if model_configuration.model_type not in model_types: continue - + if model and model != model_configuration.model: + continue try: custom_model_schema = self.get_model_schema( model_type=model_configuration.model_type, diff --git a/api/core/extension/extensible.py b/api/core/extension/extensible.py index 231743bf2a..06fdb089d4 100644 --- a/api/core/extension/extensible.py +++ b/api/core/extension/extensible.py @@ -41,45 +41,53 @@ class Extensible: extensions = [] position_map: dict[str, int] = {} - # get the path of the current class - current_path = os.path.abspath(cls.__module__.replace(".", os.path.sep) + ".py") - current_dir_path = os.path.dirname(current_path) - - # traverse subdirectories - for subdir_name in os.listdir(current_dir_path): - if subdir_name.startswith("__"): - continue - - subdir_path = os.path.join(current_dir_path, subdir_name) - extension_name = subdir_name - if os.path.isdir(subdir_path): + # Get the package name from the module path + package_name = ".".join(cls.__module__.split(".")[:-1]) + + try: + # Get package directory path + package_spec = importlib.util.find_spec(package_name) + if not package_spec or not package_spec.origin: + raise ImportError(f"Could not find package {package_name}") + + package_dir = os.path.dirname(package_spec.origin) + + # Traverse subdirectories + for subdir_name in os.listdir(package_dir): + if subdir_name.startswith("__"): + continue + + subdir_path = os.path.join(package_dir, subdir_name) + if not os.path.isdir(subdir_path): + continue + + extension_name = subdir_name file_names = os.listdir(subdir_path) - # is builtin extension, builtin extension - # in the front-end page and business logic, there are special treatments. + # Check for extension module file + if (extension_name + ".py") not in file_names: + logging.warning(f"Missing {extension_name}.py file in {subdir_path}, Skip.") + continue + + # Check for builtin flag and position builtin = False - # default position is 0 can not be None for sort_to_dict_by_position_map position = 0 if "__builtin__" in file_names: builtin = True - builtin_file_path = os.path.join(subdir_path, "__builtin__") if os.path.exists(builtin_file_path): position = int(Path(builtin_file_path).read_text(encoding="utf-8").strip()) position_map[extension_name] = position - if (extension_name + ".py") not in file_names: - logging.warning(f"Missing {extension_name}.py file in {subdir_path}, Skip.") - continue - - # Dynamic loading {subdir_name}.py file and find the subclass of Extensible - py_path = os.path.join(subdir_path, extension_name + ".py") - spec = importlib.util.spec_from_file_location(extension_name, py_path) + # Import the extension module + module_name = f"{package_name}.{extension_name}.{extension_name}" + spec = importlib.util.find_spec(module_name) if not spec or not spec.loader: - raise Exception(f"Failed to load module {extension_name} from {py_path}") + raise ImportError(f"Failed to load module {module_name}") mod = importlib.util.module_from_spec(spec) spec.loader.exec_module(mod) + # Find extension class extension_class = None for name, obj in vars(mod).items(): if isinstance(obj, type) and issubclass(obj, cls) and obj != cls: @@ -87,21 +95,21 @@ class Extensible: break if not extension_class: - logging.warning(f"Missing subclass of {cls.__name__} in {py_path}, Skip.") + logging.warning(f"Missing subclass of {cls.__name__} in {module_name}, Skip.") continue + # Load schema if not builtin json_data: dict[str, Any] = {} if not builtin: - if "schema.json" not in file_names: + json_path = os.path.join(subdir_path, "schema.json") + if not os.path.exists(json_path): logging.warning(f"Missing schema.json file in {subdir_path}, Skip.") continue - json_path = os.path.join(subdir_path, "schema.json") - json_data = {} - if os.path.exists(json_path): - with open(json_path, encoding="utf-8") as f: - json_data = json.load(f) + with open(json_path, encoding="utf-8") as f: + json_data = json.load(f) + # Create extension extensions.append( ModuleExtension( extension_class=extension_class, @@ -113,6 +121,11 @@ class Extensible: ) ) + except Exception as e: + logging.exception("Error scanning extensions") + raise + + # Sort extensions by position sorted_extensions = sort_to_dict_by_position_map( position_map=position_map, data=extensions, name_func=lambda x: x.name ) diff --git a/api/core/external_data_tool/api/__builtin__ b/api/core/external_data_tool/api/__builtin__ index 56a6051ca2..d00491fd7e 100644 --- a/api/core/external_data_tool/api/__builtin__ +++ b/api/core/external_data_tool/api/__builtin__ @@ -1 +1 @@ -1 \ No newline at end of file +1 diff --git a/api/core/file/file_manager.py b/api/core/file/file_manager.py index 9a204e9ff6..ada19ef8ce 100644 --- a/api/core/file/file_manager.py +++ b/api/core/file/file_manager.py @@ -10,12 +10,12 @@ from core.model_runtime.entities import ( VideoPromptMessageContent, ) from core.model_runtime.entities.message_entities import PromptMessageContentUnionTypes +from core.tools.signature import sign_tool_file from extensions.ext_storage import storage from . import helpers from .enums import FileAttribute from .models import File, FileTransferMethod, FileType -from .tool_file_parser import ToolFileParser def get_attr(*, file: File, attr: FileAttribute): @@ -130,6 +130,6 @@ def _to_url(f: File, /): # add sign url if f.related_id is None or f.extension is None: raise ValueError("Missing file related_id or extension") - return ToolFileParser.get_tool_file_manager().sign_file(tool_file_id=f.related_id, extension=f.extension) + return sign_tool_file(tool_file_id=f.related_id, extension=f.extension) else: raise ValueError(f"Unsupported transfer method: {f.transfer_method}") diff --git a/api/core/file/models.py b/api/core/file/models.py index f5db6c2d74..aa3b5f629c 100644 --- a/api/core/file/models.py +++ b/api/core/file/models.py @@ -4,11 +4,11 @@ from typing import Any, Optional from pydantic import BaseModel, Field, model_validator from core.model_runtime.entities.message_entities import ImagePromptMessageContent +from core.tools.signature import sign_tool_file from . import helpers from .constants import FILE_MODEL_IDENTITY from .enums import FileTransferMethod, FileType -from .tool_file_parser import ToolFileParser class ImageConfig(BaseModel): @@ -34,13 +34,21 @@ class FileUploadConfig(BaseModel): class File(BaseModel): + # NOTE: dify_model_identity is a special identifier used to distinguish between + # new and old data formats during serialization and deserialization. dify_model_identity: str = FILE_MODEL_IDENTITY id: Optional[str] = None # message file id tenant_id: str type: FileType transfer_method: FileTransferMethod + # If `transfer_method` is `FileTransferMethod.remote_url`, the + # `remote_url` attribute must not be `None`. remote_url: Optional[str] = None # remote url + # If `transfer_method` is `FileTransferMethod.local_file` or + # `FileTransferMethod.tool_file`, the `related_id` attribute must not be `None`. + # + # It should be set to `ToolFile.id` when `transfer_method` is `tool_file`. related_id: Optional[str] = None filename: Optional[str] = None extension: Optional[str] = Field(default=None, description="File extension, should contains dot") @@ -110,9 +118,7 @@ class File(BaseModel): elif self.transfer_method == FileTransferMethod.TOOL_FILE: assert self.related_id is not None assert self.extension is not None - return ToolFileParser.get_tool_file_manager().sign_file( - tool_file_id=self.related_id, extension=self.extension - ) + return sign_tool_file(tool_file_id=self.related_id, extension=self.extension) def to_plugin_parameter(self) -> dict[str, Any]: return { diff --git a/api/core/file/tool_file_parser.py b/api/core/file/tool_file_parser.py index 6fa101cf36..656c9d48ed 100644 --- a/api/core/file/tool_file_parser.py +++ b/api/core/file/tool_file_parser.py @@ -1,12 +1,19 @@ -from typing import TYPE_CHECKING, Any, cast +from collections.abc import Callable +from typing import TYPE_CHECKING if TYPE_CHECKING: from core.tools.tool_file_manager import ToolFileManager -tool_file_manager: dict[str, Any] = {"manager": None} +_tool_file_manager_factory: Callable[[], "ToolFileManager"] | None = None class ToolFileParser: @staticmethod def get_tool_file_manager() -> "ToolFileManager": - return cast("ToolFileManager", tool_file_manager["manager"]) + assert _tool_file_manager_factory is not None + return _tool_file_manager_factory() + + +def set_tool_file_manager_factory(factory: Callable[[], "ToolFileManager"]) -> None: + global _tool_file_manager_factory + _tool_file_manager_factory = factory diff --git a/api/core/helper/code_executor/code_executor.py b/api/core/helper/code_executor/code_executor.py index 5bb045cce9..2b580cb373 100644 --- a/api/core/helper/code_executor/code_executor.py +++ b/api/core/helper/code_executor/code_executor.py @@ -15,6 +15,7 @@ from core.helper.code_executor.python3.python3_transformer import Python3Templat from core.helper.code_executor.template_transformer import TemplateTransformer logger = logging.getLogger(__name__) +code_execution_endpoint_url = URL(str(dify_config.CODE_EXECUTION_ENDPOINT)) class CodeExecutionError(Exception): @@ -64,7 +65,7 @@ class CodeExecutor: :param code: code :return: """ - url = URL(str(dify_config.CODE_EXECUTION_ENDPOINT)) / "v1" / "sandbox" / "run" + url = code_execution_endpoint_url / "v1" / "sandbox" / "run" headers = {"X-Api-Key": dify_config.CODE_EXECUTION_API_KEY} diff --git a/api/core/helper/code_executor/javascript/javascript_transformer.py b/api/core/helper/code_executor/javascript/javascript_transformer.py index d67a0903aa..62489cdf29 100644 --- a/api/core/helper/code_executor/javascript/javascript_transformer.py +++ b/api/core/helper/code_executor/javascript/javascript_transformer.py @@ -10,13 +10,13 @@ class NodeJsTemplateTransformer(TemplateTransformer): f""" // declare main function {cls._code_placeholder} - + // decode and prepare input object var inputs_obj = JSON.parse(Buffer.from('{cls._inputs_placeholder}', 'base64').toString('utf-8')) - + // execute main function var output_obj = main(inputs_obj) - + // convert output to json and print var output_json = JSON.stringify(output_obj) var result = `<>${{output_json}}<>` diff --git a/api/core/helper/code_executor/jinja2/jinja2_transformer.py b/api/core/helper/code_executor/jinja2/jinja2_transformer.py index 63d58edbc7..54c78cdf92 100644 --- a/api/core/helper/code_executor/jinja2/jinja2_transformer.py +++ b/api/core/helper/code_executor/jinja2/jinja2_transformer.py @@ -21,20 +21,20 @@ class Jinja2TemplateTransformer(TemplateTransformer): import jinja2 template = jinja2.Template('''{cls._code_placeholder}''') return template.render(**inputs) - + import json from base64 import b64decode - + # decode and prepare input dict inputs_obj = json.loads(b64decode('{cls._inputs_placeholder}').decode('utf-8')) - + # execute main function output = main(**inputs_obj) - + # convert output and print result = f'''<>{{output}}<>''' print(result) - + """) return runner_script @@ -43,15 +43,15 @@ class Jinja2TemplateTransformer(TemplateTransformer): preload_script = dedent(""" import jinja2 from base64 import b64decode - + def _jinja2_preload_(): # prepare jinja2 environment, load template and render before to avoid sandbox issue template = jinja2.Template('{{s}}') template.render(s='a') - + if __name__ == '__main__': _jinja2_preload_() - + """) return preload_script diff --git a/api/core/helper/code_executor/python3/python3_transformer.py b/api/core/helper/code_executor/python3/python3_transformer.py index 75a5a44d08..836fd273ae 100644 --- a/api/core/helper/code_executor/python3/python3_transformer.py +++ b/api/core/helper/code_executor/python3/python3_transformer.py @@ -9,16 +9,16 @@ class Python3TemplateTransformer(TemplateTransformer): runner_script = dedent(f""" # declare main function {cls._code_placeholder} - + import json from base64 import b64decode - + # decode and prepare input dict inputs_obj = json.loads(b64decode('{cls._inputs_placeholder}').decode('utf-8')) - + # execute main function output_obj = main(**inputs_obj) - + # convert output to json and print output_json = json.dumps(output_obj, indent=4) result = f'''<>{{output_json}}<>''' diff --git a/api/core/helper/marketplace.py b/api/core/helper/marketplace.py index f4129b88ed..65bf4fc1db 100644 --- a/api/core/helper/marketplace.py +++ b/api/core/helper/marketplace.py @@ -7,29 +7,28 @@ from configs import dify_config from core.helper.download import download_with_size_limit from core.plugin.entities.marketplace import MarketplacePluginDeclaration +marketplace_api_url = URL(str(dify_config.MARKETPLACE_API_URL)) -def get_plugin_pkg_url(plugin_unique_identifier: str): - return (URL(str(dify_config.MARKETPLACE_API_URL)) / "api/v1/plugins/download").with_query( - unique_identifier=plugin_unique_identifier - ) + +def get_plugin_pkg_url(plugin_unique_identifier: str) -> str: + return str((marketplace_api_url / "api/v1/plugins/download").with_query(unique_identifier=plugin_unique_identifier)) def download_plugin_pkg(plugin_unique_identifier: str): - url = str(get_plugin_pkg_url(plugin_unique_identifier)) - return download_with_size_limit(url, dify_config.PLUGIN_MAX_PACKAGE_SIZE) + return download_with_size_limit(get_plugin_pkg_url(plugin_unique_identifier), dify_config.PLUGIN_MAX_PACKAGE_SIZE) def batch_fetch_plugin_manifests(plugin_ids: list[str]) -> Sequence[MarketplacePluginDeclaration]: if len(plugin_ids) == 0: return [] - url = str(URL(str(dify_config.MARKETPLACE_API_URL)) / "api/v1/plugins/batch") + url = str(marketplace_api_url / "api/v1/plugins/batch") response = requests.post(url, json={"plugin_ids": plugin_ids}) response.raise_for_status() return [MarketplacePluginDeclaration(**plugin) for plugin in response.json()["data"]["plugins"]] def record_install_plugin_event(plugin_unique_identifier: str): - url = str(URL(str(dify_config.MARKETPLACE_API_URL)) / "api/v1/stats/plugins/install_count") + url = str(marketplace_api_url / "api/v1/stats/plugins/install_count") response = requests.post(url, json={"unique_identifier": plugin_unique_identifier}) response.raise_for_status() diff --git a/api/core/helper/moderation.py b/api/core/helper/moderation.py index 6a5982eca4..a324ac2767 100644 --- a/api/core/helper/moderation.py +++ b/api/core/helper/moderation.py @@ -1,5 +1,5 @@ import logging -import random +import secrets from typing import cast from core.app.entities.app_invoke_entities import ModelConfigWithCredentialsEntity @@ -38,7 +38,7 @@ def check_moderation(tenant_id: str, model_config: ModelConfigWithCredentialsEnt if len(text_chunks) == 0: return True - text_chunk = random.choice(text_chunks) + text_chunk = secrets.choice(text_chunks) try: model_provider_factory = ModelProviderFactory(tenant_id) diff --git a/api/core/indexing_runner.py b/api/core/indexing_runner.py index a75a4c22d1..848d897779 100644 --- a/api/core/indexing_runner.py +++ b/api/core/indexing_runner.py @@ -9,7 +9,7 @@ import uuid from typing import Any, Optional, cast from flask import current_app -from flask_login import current_user # type: ignore +from flask_login import current_user from sqlalchemy.orm.exc import ObjectDeletedError from configs import dify_config @@ -51,7 +51,7 @@ class IndexingRunner: for dataset_document in dataset_documents: try: # get dataset - dataset = Dataset.query.filter_by(id=dataset_document.dataset_id).first() + dataset = db.session.query(Dataset).filter_by(id=dataset_document.dataset_id).first() if not dataset: raise ValueError("no dataset found") @@ -103,15 +103,17 @@ class IndexingRunner: """Run the indexing process when the index_status is splitting.""" try: # get dataset - dataset = Dataset.query.filter_by(id=dataset_document.dataset_id).first() + dataset = db.session.query(Dataset).filter_by(id=dataset_document.dataset_id).first() if not dataset: raise ValueError("no dataset found") # get exist document_segment list and delete - document_segments = DocumentSegment.query.filter_by( - dataset_id=dataset.id, document_id=dataset_document.id - ).all() + document_segments = ( + db.session.query(DocumentSegment) + .filter_by(dataset_id=dataset.id, document_id=dataset_document.id) + .all() + ) for document_segment in document_segments: db.session.delete(document_segment) @@ -162,15 +164,17 @@ class IndexingRunner: """Run the indexing process when the index_status is indexing.""" try: # get dataset - dataset = Dataset.query.filter_by(id=dataset_document.dataset_id).first() + dataset = db.session.query(Dataset).filter_by(id=dataset_document.dataset_id).first() if not dataset: raise ValueError("no dataset found") # get exist document_segment list and delete - document_segments = DocumentSegment.query.filter_by( - dataset_id=dataset.id, document_id=dataset_document.id - ).all() + document_segments = ( + db.session.query(DocumentSegment) + .filter_by(dataset_id=dataset.id, document_id=dataset_document.id) + .all() + ) documents = [] if document_segments: @@ -254,7 +258,7 @@ class IndexingRunner: embedding_model_instance = None if dataset_id: - dataset = Dataset.query.filter_by(id=dataset_id).first() + dataset = db.session.query(Dataset).filter_by(id=dataset_id).first() if not dataset: raise ValueError("Dataset not found.") if dataset.indexing_technique == "high_quality" or indexing_technique == "high_quality": @@ -587,7 +591,7 @@ class IndexingRunner: @staticmethod def _process_keyword_index(flask_app, dataset_id, document_id, documents): with flask_app.app_context(): - dataset = Dataset.query.filter_by(id=dataset_id).first() + dataset = db.session.query(Dataset).filter_by(id=dataset_id).first() if not dataset: raise ValueError("no dataset found") keyword = Keyword(dataset) @@ -656,10 +660,10 @@ class IndexingRunner: """ Update the document indexing status. """ - count = DatasetDocument.query.filter_by(id=document_id, is_paused=True).count() + count = db.session.query(DatasetDocument).filter_by(id=document_id, is_paused=True).count() if count > 0: raise DocumentIsPausedError() - document = DatasetDocument.query.filter_by(id=document_id).first() + document = db.session.query(DatasetDocument).filter_by(id=document_id).first() if not document: raise DocumentIsDeletedPausedError() @@ -668,7 +672,7 @@ class IndexingRunner: if extra_update_params: update_params.update(extra_update_params) - DatasetDocument.query.filter_by(id=document_id).update(update_params) + db.session.query(DatasetDocument).filter_by(id=document_id).update(update_params) db.session.commit() @staticmethod @@ -676,7 +680,7 @@ class IndexingRunner: """ Update the document segment by document id. """ - DocumentSegment.query.filter_by(document_id=dataset_document_id).update(update_params) + db.session.query(DocumentSegment).filter_by(document_id=dataset_document_id).update(update_params) db.session.commit() def _transform( diff --git a/api/core/llm_generator/llm_generator.py b/api/core/llm_generator/llm_generator.py index d5d2ca60fa..e01896a491 100644 --- a/api/core/llm_generator/llm_generator.py +++ b/api/core/llm_generator/llm_generator.py @@ -3,6 +3,8 @@ import logging import re from typing import Optional, cast +import json_repair + from core.llm_generator.output_parser.rule_config_generator import RuleConfigGeneratorOutputParser from core.llm_generator.output_parser.suggested_questions_after_answer import SuggestedQuestionsAfterAnswerOutputParser from core.llm_generator.prompts import ( @@ -49,15 +51,19 @@ class LLMGenerator: response = cast( LLMResult, model_instance.invoke_llm( - prompt_messages=list(prompts), model_parameters={"max_tokens": 100, "temperature": 1}, stream=False + prompt_messages=list(prompts), model_parameters={"max_tokens": 500, "temperature": 1}, stream=False ), ) answer = cast(str, response.message.content) cleaned_answer = re.sub(r"^.*(\{.*\}).*$", r"\1", answer, flags=re.DOTALL) if cleaned_answer is None: return "" - result_dict = json.loads(cleaned_answer) - answer = result_dict["Your Output"] + try: + result_dict = json.loads(cleaned_answer) + answer = result_dict["Your Output"] + except json.JSONDecodeError as e: + logging.exception("Failed to generate name after answer, use query instead") + answer = query name = answer.strip() if len(name) > 75: @@ -366,7 +372,20 @@ class LLMGenerator: ), ) - generated_json_schema = cast(str, response.message.content) + raw_content = response.message.content + + if not isinstance(raw_content, str): + raise ValueError(f"LLM response content must be a string, got: {type(raw_content)}") + + try: + parsed_content = json.loads(raw_content) + except json.JSONDecodeError: + parsed_content = json_repair.loads(raw_content) + + if not isinstance(parsed_content, dict | list): + raise ValueError(f"Failed to parse structured output from llm: {raw_content}") + + generated_json_schema = json.dumps(parsed_content, indent=2, ensure_ascii=False) return {"output": generated_json_schema, "error": ""} except InvokeError as e: diff --git a/api/core/llm_generator/prompts.py b/api/core/llm_generator/prompts.py index 82d22d7f89..ddfa1e7a66 100644 --- a/api/core/llm_generator/prompts.py +++ b/api/core/llm_generator/prompts.py @@ -1,64 +1,23 @@ -# Written by YORKI MINAKO🤡, Edited by Xiaoyi -CONVERSATION_TITLE_PROMPT = """You need to decompose the user's input into "subject" and "intention" in order to accurately figure out what the user's input language actually is. -Notice: the language type user use could be diverse, which can be English, Chinese, Italian, Español, Arabic, Japanese, French, and etc. -MAKE SURE your output is the SAME language as the user's input! -Your output is restricted only to: (Input language) Intention + Subject(short as possible) -Your output MUST be a valid JSON. +# Written by YORKI MINAKO🤡, Edited by Xiaoyi, Edited by yasu-oh +CONVERSATION_TITLE_PROMPT = """You are asked to generate a concise chat title by decomposing the user’s input into two parts: “Intention” and “Subject”. -Tip: When the user's question is directed at you (the language model), you can add an emoji to make it more fun. +1. Detect Input Language +Automatically identify the language of the user’s input (e.g. English, Chinese, Italian, Español, Arabic, Japanese, French, and etc.). +2. Generate Title +- Combine Intention + Subject into a single, as-short-as-possible phrase. +- The title must be natural, friendly, and in the same language as the input. +- If the input is a direct question to the model, you may add an emoji at the end. -example 1: -User Input: hi, yesterday i had some burgers. +3. Output Format +Return **only** a valid JSON object with these exact keys and no additional text: { - "Language Type": "The user's input is pure English", - "Your Reasoning": "The language of my output must be pure English.", - "Your Output": "sharing yesterday's food" + "Language Type": "", + "Your Reasoning": "", + "Your Output": "" } -example 2: -User Input: hello -{ - "Language Type": "The user's input is written in pure English", - "Your Reasoning": "The language of my output must be pure English.", - "Your Output": "Greeting myself☺️" -} - - -example 3: -User Input: why mmap file: oom -{ - "Language Type": "The user's input is written in pure English", - "Your Reasoning": "The language of my output must be pure English.", - "Your Output": "Asking about the reason for mmap file: oom" -} - - -example 4: -User Input: www.convinceme.yesterday-you-ate-seafood.tv讲了什么? -{ - "Language Type": "The user's input English-Chinese mixed", - "Your Reasoning": "The English-part is an URL, the main intention is still written in Chinese, so the language of my output must be using Chinese.", - "Your Output": "询问网站www.convinceme.yesterday-you-ate-seafood.tv" -} - -example 5: -User Input: why小红的年龄is老than小明? -{ - "Language Type": "The user's input is English-Chinese mixed", - "Your Reasoning": "The English parts are subjective particles, the main intention is written in Chinese, besides, Chinese occupies a greater \"actual meaning\" than English, so the language of my output must be using Chinese.", - "Your Output": "询问小红和小明的年龄" -} - -example 6: -User Input: yo, 你今天咋样? -{ - "Language Type": "The user's input is English-Chinese mixed", - "Your Reasoning": "The English-part is a subjective particle, the main intention is written in Chinese, so the language of my output must be using Chinese.", - "Your Output": "查询今日我的状态☺️" -} - -User Input: +User Input: """ # noqa: E501 PYTHON_CODE_GENERATOR_PROMPT_TEMPLATE = ( @@ -114,6 +73,13 @@ JAVASCRIPT_CODE_GENERATOR_PROMPT_TEMPLATE = ( "4. The returned object should contain at least one key-value pair.\n\n" "5. The returned object should always be in the format: {result: ...}\n\n" "Example:\n" + "/**\n" + " * Multiplies two numbers together.\n" + " *\n" + " * @param {number} arg1 - The first number to multiply.\n" + " * @param {number} arg2 - The second number to multiply.\n" + " * @returns {{ result: number }} The result of the multiplication.\n" + " */\n" "function main(arg1, arg2) {\n" " return {\n" " result: arg1 * arg2\n" @@ -130,7 +96,7 @@ JAVASCRIPT_CODE_GENERATOR_PROMPT_TEMPLATE = ( SUGGESTED_QUESTIONS_AFTER_ANSWER_INSTRUCTION_PROMPT = ( "Please help me predict the three most likely questions that human would ask, " - "and keeping each question under 20 characters.\n" + "and keep each question under 20 characters.\n" "MAKE SURE your output is the SAME language as the Assistant's latest response. " "The output must be an array in JSON format following the specified schema:\n" '["question1","question2","question3"]\n' @@ -156,11 +122,11 @@ Here is a task description for which I would like you to create a high-quality p {{TASK_DESCRIPTION}} Based on task description, please create a well-structured prompt template that another AI could use to consistently complete the task. The prompt template should include: -- Do not include or section and variables in the prompt, assume user will add them at their own will. -- Clear instructions for the AI that will be using this prompt, demarcated with tags. The instructions should provide step-by-step directions on how to complete the task using the input variables. Also Specifies in the instructions that the output should not contain any xml tag. -- Relevant examples if needed to clarify the task further, demarcated with tags. Do not include variables in the prompt. Give three pairs of input and output examples. -- Include other relevant sections demarcated with appropriate XML tags like , . -- Use the same language as task description. +- Do not include or section and variables in the prompt, assume user will add them at their own will. +- Clear instructions for the AI that will be using this prompt, demarcated with tags. The instructions should provide step-by-step directions on how to complete the task using the input variables. Also Specifies in the instructions that the output should not contain any xml tag. +- Relevant examples if needed to clarify the task further, demarcated with tags. Do not include variables in the prompt. Give three pairs of input and output examples. +- Include other relevant sections demarcated with appropriate XML tags like , . +- Use the same language as task description. - Output in ``` xml ``` and start with Please generate the full prompt template with at least 300 words and output only the prompt template. """ # noqa: E501 @@ -171,28 +137,28 @@ Here is a task description for which I would like you to create a high-quality p {{TASK_DESCRIPTION}} Based on task description, please create a well-structured prompt template that another AI could use to consistently complete the task. The prompt template should include: -- Descriptive variable names surrounded by {{ }} (two curly brackets) to indicate where the actual values will be substituted in. Choose variable names that clearly indicate the type of value expected. Variable names have to be composed of number, english alphabets and underline and nothing else. -- Clear instructions for the AI that will be using this prompt, demarcated with tags. The instructions should provide step-by-step directions on how to complete the task using the input variables. Also Specifies in the instructions that the output should not contain any xml tag. -- Relevant examples if needed to clarify the task further, demarcated with tags. Do not use curly brackets any other than in section. +- Descriptive variable names surrounded by {{ }} (two curly brackets) to indicate where the actual values will be substituted in. Choose variable names that clearly indicate the type of value expected. Variable names have to be composed of number, english alphabets and underline and nothing else. +- Clear instructions for the AI that will be using this prompt, demarcated with tags. The instructions should provide step-by-step directions on how to complete the task using the input variables. Also Specifies in the instructions that the output should not contain any xml tag. +- Relevant examples if needed to clarify the task further, demarcated with tags. Do not use curly brackets any other than in section. - Any other relevant sections demarcated with appropriate XML tags like , , etc. -- Use the same language as task description. +- Use the same language as task description. - Output in ``` xml ``` and start with Please generate the full prompt template and output only the prompt template. """ # noqa: E501 RULE_CONFIG_PARAMETER_GENERATE_TEMPLATE = """ -I need to extract the following information from the input text. The tag specifies the 'type', 'description' and 'required' of the information to be extracted. +I need to extract the following information from the input text. The tag specifies the 'type', 'description' and 'required' of the information to be extracted. -variables name bounded two double curly brackets. Variable name has to be composed of number, english alphabets and underline and nothing else. +variables name bounded two double curly brackets. Variable name has to be composed of number, english alphabets and underline and nothing else. Step 1: Carefully read the input and understand the structure of the expected output. -Step 2: Extract relevant parameters from the provided text based on the name and description of object. +Step 2: Extract relevant parameters from the provided text based on the name and description of object. Step 3: Structure the extracted parameters to JSON object as specified in . -Step 4: Ensure that the list of variable_names is properly formatted and valid. The output should not contain any XML tags. Output an empty list if there is no valid variable name in input text. +Step 4: Ensure that the list of variable_names is properly formatted and valid. The output should not contain any XML tags. Output an empty list if there is no valid variable name in input text. ### Structure -Here is the structure of the expected output, I should always follow the output structure. +Here is the structure of the expected output, I should always follow the output structure. ["variable_name_1", "variable_name_2"] ### Input Text @@ -207,13 +173,13 @@ I should always output a valid list. Output nothing other than the list of varia RULE_CONFIG_STATEMENT_GENERATE_TEMPLATE = """ -Step 1: Identify the purpose of the chatbot from the variable {{TASK_DESCRIPTION}} and infer chatbot's tone (e.g., friendly, professional, etc.) to add personality traits. +Step 1: Identify the purpose of the chatbot from the variable {{TASK_DESCRIPTION}} and infer chatbot's tone (e.g., friendly, professional, etc.) to add personality traits. Step 2: Create a coherent and engaging opening statement. Step 3: Ensure the output is welcoming and clearly explains what the chatbot is designed to do. Do not include any XML tags in the output. -Please use the same language as the user's input language. If user uses chinese then generate opening statement in chinese, if user uses english then generate opening statement in english. -Example Input: +Please use the same language as the user's input language. If user uses chinese then generate opening statement in chinese, if user uses english then generate opening statement in english. +Example Input: Provide customer support for an e-commerce website -Example Output: +Example Output: Welcome! I'm here to assist you with any questions or issues you might have with your shopping experience. Whether you're looking for product information, need help with your order, or have any other inquiries, feel free to ask. I'm friendly, helpful, and ready to support you in any way I can. Here is the task description: {{INPUT_TEXT}} @@ -269,15 +235,15 @@ Your task is to convert simple user descriptions into properly formatted JSON Sc { "type": "object", "properties": { - "email": { + "email": { "type": "string", "format": "email" }, - "password": { + "password": { "type": "string", "minLength": 8 }, - "age": { + "age": { "type": "integer", "minimum": 18 } @@ -291,32 +257,30 @@ Your task is to convert simple user descriptions into properly formatted JSON Sc { "type": "object", "properties": { - "properties": { - "songs": { - "type": "array", - "items": { - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "id": { - "type": "string" - }, - "duration": { - "type": "string" - }, - "aritst": { - "type": "string" - } + "songs": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "id": { + "type": "string" + }, + "duration": { + "type": "string" }, - "required": [ - "name", - "id", - "duration", - "aritst" - ] - } + "aritst": { + "type": "string" + } + }, + "required": [ + "name", + "id", + "duration", + "aritst" + ] } } }, diff --git a/api/core/model_manager.py b/api/core/model_manager.py index 0845ef206e..995a30d44c 100644 --- a/api/core/model_manager.py +++ b/api/core/model_manager.py @@ -101,7 +101,7 @@ class ModelInstance: @overload def invoke_llm( self, - prompt_messages: list[PromptMessage], + prompt_messages: Sequence[PromptMessage], model_parameters: Optional[dict] = None, tools: Sequence[PromptMessageTool] | None = None, stop: Optional[list[str]] = None, diff --git a/api/core/model_runtime/docs/en_US/customizable_model_scale_out.md b/api/core/model_runtime/docs/en_US/customizable_model_scale_out.md index 2d71e99fce..d845c4bd09 100644 --- a/api/core/model_runtime/docs/en_US/customizable_model_scale_out.md +++ b/api/core/model_runtime/docs/en_US/customizable_model_scale_out.md @@ -307,4 +307,4 @@ Runtime Errors: """ ``` -For interface method details, see: [Interfaces](./interfaces.md). For specific implementations, refer to: [llm.py](https://github.com/langgenius/dify-runtime/blob/main/lib/model_providers/anthropic/llm/llm.py). \ No newline at end of file +For interface method details, see: [Interfaces](./interfaces.md). For specific implementations, refer to: [llm.py](https://github.com/langgenius/dify-runtime/blob/main/lib/model_providers/anthropic/llm/llm.py). diff --git a/api/core/model_runtime/docs/en_US/predefined_model_scale_out.md b/api/core/model_runtime/docs/en_US/predefined_model_scale_out.md index 3e16257452..a770ed157b 100644 --- a/api/core/model_runtime/docs/en_US/predefined_model_scale_out.md +++ b/api/core/model_runtime/docs/en_US/predefined_model_scale_out.md @@ -170,4 +170,4 @@ Runtime Errors: """ ``` -For interface method explanations, see: [Interfaces](./interfaces.md). For detailed implementation, refer to: [llm.py](https://github.com/langgenius/dify-runtime/blob/main/lib/model_providers/anthropic/llm/llm.py). \ No newline at end of file +For interface method explanations, see: [Interfaces](./interfaces.md). For detailed implementation, refer to: [llm.py](https://github.com/langgenius/dify-runtime/blob/main/lib/model_providers/anthropic/llm/llm.py). diff --git a/api/core/model_runtime/docs/zh_Hans/customizable_model_scale_out.md b/api/core/model_runtime/docs/zh_Hans/customizable_model_scale_out.md index 88ec6861fe..7d30655469 100644 --- a/api/core/model_runtime/docs/zh_Hans/customizable_model_scale_out.md +++ b/api/core/model_runtime/docs/zh_Hans/customizable_model_scale_out.md @@ -294,4 +294,4 @@ provider_credential_schema: """ ``` -接口方法说明见:[Interfaces](./interfaces.md),具体实现可参考:[llm.py](https://github.com/langgenius/dify-runtime/blob/main/lib/model_providers/anthropic/llm/llm.py)。 \ No newline at end of file +接口方法说明见:[Interfaces](./interfaces.md),具体实现可参考:[llm.py](https://github.com/langgenius/dify-runtime/blob/main/lib/model_providers/anthropic/llm/llm.py)。 diff --git a/api/core/model_runtime/docs/zh_Hans/predefined_model_scale_out.md b/api/core/model_runtime/docs/zh_Hans/predefined_model_scale_out.md index b33dc7c94b..80e7982e9f 100644 --- a/api/core/model_runtime/docs/zh_Hans/predefined_model_scale_out.md +++ b/api/core/model_runtime/docs/zh_Hans/predefined_model_scale_out.md @@ -169,4 +169,4 @@ pricing: # 价格信息 """ ``` -接口方法说明见:[Interfaces](./interfaces.md),具体实现可参考:[llm.py](https://github.com/langgenius/dify-runtime/blob/main/lib/model_providers/anthropic/llm/llm.py)。 \ No newline at end of file +接口方法说明见:[Interfaces](./interfaces.md),具体实现可参考:[llm.py](https://github.com/langgenius/dify-runtime/blob/main/lib/model_providers/anthropic/llm/llm.py)。 diff --git a/api/core/model_runtime/entities/llm_entities.py b/api/core/model_runtime/entities/llm_entities.py index 9bb118622b..de5a748d4f 100644 --- a/api/core/model_runtime/entities/llm_entities.py +++ b/api/core/model_runtime/entities/llm_entities.py @@ -17,19 +17,6 @@ class LLMMode(StrEnum): COMPLETION = "completion" CHAT = "chat" - @classmethod - def value_of(cls, value: str) -> "LLMMode": - """ - Get value of given mode. - - :param value: mode value - :return: mode - """ - for mode in cls: - if mode.value == value: - return mode - raise ValueError(f"invalid mode value {value}") - class LLMUsage(ModelUsage): """ diff --git a/api/core/model_runtime/entities/message_entities.py b/api/core/model_runtime/entities/message_entities.py index b1c43d1455..9d010ae28d 100644 --- a/api/core/model_runtime/entities/message_entities.py +++ b/api/core/model_runtime/entities/message_entities.py @@ -1,4 +1,5 @@ -from collections.abc import Sequence +from abc import ABC +from collections.abc import Mapping, Sequence from enum import Enum, StrEnum from typing import Annotated, Any, Literal, Optional, Union @@ -60,8 +61,12 @@ class PromptMessageContentType(StrEnum): DOCUMENT = "document" -class PromptMessageContent(BaseModel): - pass +class PromptMessageContent(ABC, BaseModel): + """ + Model class for prompt message content. + """ + + type: PromptMessageContentType class TextPromptMessageContent(PromptMessageContent): @@ -125,7 +130,16 @@ PromptMessageContentUnionTypes = Annotated[ ] -class PromptMessage(BaseModel): +CONTENT_TYPE_MAPPING: Mapping[PromptMessageContentType, type[PromptMessageContent]] = { + PromptMessageContentType.TEXT: TextPromptMessageContent, + PromptMessageContentType.IMAGE: ImagePromptMessageContent, + PromptMessageContentType.AUDIO: AudioPromptMessageContent, + PromptMessageContentType.VIDEO: VideoPromptMessageContent, + PromptMessageContentType.DOCUMENT: DocumentPromptMessageContent, +} + + +class PromptMessage(ABC, BaseModel): """ Model class for prompt message. """ @@ -142,6 +156,23 @@ class PromptMessage(BaseModel): """ return not self.content + @field_validator("content", mode="before") + @classmethod + def validate_content(cls, v): + if isinstance(v, list): + prompts = [] + for prompt in v: + if isinstance(prompt, PromptMessageContent): + if not isinstance(prompt, TextPromptMessageContent | MultiModalPromptMessageContent): + prompt = CONTENT_TYPE_MAPPING[prompt.type].model_validate(prompt.model_dump()) + elif isinstance(prompt, dict): + prompt = CONTENT_TYPE_MAPPING[prompt["type"]].model_validate(prompt) + else: + raise ValueError(f"invalid prompt message {prompt}") + prompts.append(prompt) + return prompts + return v + @field_serializer("content") def serialize_content( self, content: Optional[Union[str, Sequence[PromptMessageContent]]] diff --git a/api/core/model_runtime/entities/model_entities.py b/api/core/model_runtime/entities/model_entities.py index 373ef2bbe2..568149cc37 100644 --- a/api/core/model_runtime/entities/model_entities.py +++ b/api/core/model_runtime/entities/model_entities.py @@ -160,6 +160,10 @@ class ProviderModel(BaseModel): deprecated: bool = False model_config = ConfigDict(protected_namespaces=()) + @property + def support_structure_output(self) -> bool: + return self.features is not None and ModelFeature.STRUCTURED_OUTPUT in self.features + class ParameterRule(BaseModel): """ diff --git a/api/core/model_runtime/entities/provider_entities.py b/api/core/model_runtime/entities/provider_entities.py index 85321bed94..d0f9ee13e5 100644 --- a/api/core/model_runtime/entities/provider_entities.py +++ b/api/core/model_runtime/entities/provider_entities.py @@ -134,6 +134,9 @@ class ProviderEntity(BaseModel): # pydantic configs model_config = ConfigDict(protected_namespaces=()) + # position from plugin _position.yaml + position: Optional[dict[str, list[str]]] = {} + @field_validator("models", mode="before") @classmethod def validate_models(cls, v): diff --git a/api/core/model_runtime/model_providers/__base/ai_model.py b/api/core/model_runtime/model_providers/__base/ai_model.py index bd05590018..7d5ce1e47e 100644 --- a/api/core/model_runtime/model_providers/__base/ai_model.py +++ b/api/core/model_runtime/model_providers/__base/ai_model.py @@ -24,9 +24,8 @@ from core.model_runtime.errors.invoke import ( InvokeRateLimitError, InvokeServerUnavailableError, ) -from core.model_runtime.model_providers.__base.tokenizers.gpt2_tokenzier import GPT2Tokenizer from core.plugin.entities.plugin_daemon import PluginDaemonInnerError, PluginModelProviderEntity -from core.plugin.manager.model import PluginModelManager +from core.plugin.impl.model import PluginModelClient class AIModel(BaseModel): @@ -141,7 +140,7 @@ class AIModel(BaseModel): :param credentials: model credentials :return: model schema """ - plugin_model_manager = PluginModelManager() + plugin_model_manager = PluginModelClient() cache_key = f"{self.tenant_id}:{self.plugin_id}:{self.provider_name}:{self.model_type.value}:{model}" # sort credentials sorted_credentials = sorted(credentials.items()) if credentials else [] @@ -253,15 +252,3 @@ class AIModel(BaseModel): raise Exception(f"Invalid model parameter rule name {name}") return default_parameter_rule - - def _get_num_tokens_by_gpt2(self, text: str) -> int: - """ - Get number of tokens for given prompt messages by gpt2 - Some provider models do not provide an interface for obtaining the number of tokens. - Here, the gpt2 tokenizer is used to calculate the number of tokens. - This method can be executed offline, and the gpt2 tokenizer has been cached in the project. - - :param text: plain text of prompt. You need to convert the original message to plain text - :return: number of tokens - """ - return GPT2Tokenizer.get_num_tokens(text) diff --git a/api/core/model_runtime/model_providers/__base/large_language_model.py b/api/core/model_runtime/model_providers/__base/large_language_model.py index 1b799131e7..e2cc576f83 100644 --- a/api/core/model_runtime/model_providers/__base/large_language_model.py +++ b/api/core/model_runtime/model_providers/__base/large_language_model.py @@ -13,14 +13,16 @@ from core.model_runtime.entities.llm_entities import LLMResult, LLMResultChunk, from core.model_runtime.entities.message_entities import ( AssistantPromptMessage, PromptMessage, + PromptMessageContentUnionTypes, PromptMessageTool, + TextPromptMessageContent, ) from core.model_runtime.entities.model_entities import ( ModelType, PriceType, ) from core.model_runtime.model_providers.__base.ai_model import AIModel -from core.plugin.manager.model import PluginModelManager +from core.plugin.impl.model import PluginModelClient logger = logging.getLogger(__name__) @@ -140,7 +142,7 @@ class LargeLanguageModel(AIModel): result: Union[LLMResult, Generator[LLMResultChunk, None, None]] try: - plugin_model_manager = PluginModelManager() + plugin_model_manager = PluginModelClient() result = plugin_model_manager.invoke_llm( tenant_id=self.tenant_id, user_id=user or "unknown", @@ -237,7 +239,7 @@ class LargeLanguageModel(AIModel): def _invoke_result_generator( self, model: str, - result: Generator, + result: Generator[LLMResultChunk, None, None], credentials: dict, prompt_messages: Sequence[PromptMessage], model_parameters: dict, @@ -254,11 +256,21 @@ class LargeLanguageModel(AIModel): :return: result generator """ callbacks = callbacks or [] - assistant_message = AssistantPromptMessage(content="") + message_content: list[PromptMessageContentUnionTypes] = [] usage = None system_fingerprint = None real_model = model + def _update_message_content(content: str | list[PromptMessageContentUnionTypes] | None): + if not content: + return + if isinstance(content, list): + message_content.extend(content) + return + if isinstance(content, str): + message_content.append(TextPromptMessageContent(data=content)) + return + try: for chunk in result: # Following https://github.com/langgenius/dify/issues/17799, @@ -280,7 +292,8 @@ class LargeLanguageModel(AIModel): callbacks=callbacks, ) - assistant_message.content += chunk.delta.message.content + _update_message_content(chunk.delta.message.content) + real_model = chunk.model if chunk.delta.usage: usage = chunk.delta.usage @@ -290,6 +303,7 @@ class LargeLanguageModel(AIModel): except Exception as e: raise self._transform_invoke_error(e) + assistant_message = AssistantPromptMessage(content=message_content) self._trigger_after_invoke_callbacks( model=model, result=LLMResult( @@ -326,7 +340,7 @@ class LargeLanguageModel(AIModel): :return: """ if dify_config.PLUGIN_BASED_TOKEN_COUNTING_ENABLED: - plugin_model_manager = PluginModelManager() + plugin_model_manager = PluginModelClient() return plugin_model_manager.get_llm_num_tokens( tenant_id=self.tenant_id, user_id="unknown", diff --git a/api/core/model_runtime/model_providers/__base/moderation_model.py b/api/core/model_runtime/model_providers/__base/moderation_model.py index f98d7572c7..19dc1d599a 100644 --- a/api/core/model_runtime/model_providers/__base/moderation_model.py +++ b/api/core/model_runtime/model_providers/__base/moderation_model.py @@ -5,7 +5,7 @@ from pydantic import ConfigDict from core.model_runtime.entities.model_entities import ModelType from core.model_runtime.model_providers.__base.ai_model import AIModel -from core.plugin.manager.model import PluginModelManager +from core.plugin.impl.model import PluginModelClient class ModerationModel(AIModel): @@ -31,7 +31,7 @@ class ModerationModel(AIModel): self.started_at = time.perf_counter() try: - plugin_model_manager = PluginModelManager() + plugin_model_manager = PluginModelClient() return plugin_model_manager.invoke_moderation( tenant_id=self.tenant_id, user_id=user or "unknown", diff --git a/api/core/model_runtime/model_providers/__base/rerank_model.py b/api/core/model_runtime/model_providers/__base/rerank_model.py index e905cb18d4..569e756a3b 100644 --- a/api/core/model_runtime/model_providers/__base/rerank_model.py +++ b/api/core/model_runtime/model_providers/__base/rerank_model.py @@ -3,7 +3,7 @@ from typing import Optional from core.model_runtime.entities.model_entities import ModelType from core.model_runtime.entities.rerank_entities import RerankResult from core.model_runtime.model_providers.__base.ai_model import AIModel -from core.plugin.manager.model import PluginModelManager +from core.plugin.impl.model import PluginModelClient class RerankModel(AIModel): @@ -36,7 +36,7 @@ class RerankModel(AIModel): :return: rerank result """ try: - plugin_model_manager = PluginModelManager() + plugin_model_manager = PluginModelClient() return plugin_model_manager.invoke_rerank( tenant_id=self.tenant_id, user_id=user or "unknown", diff --git a/api/core/model_runtime/model_providers/__base/speech2text_model.py b/api/core/model_runtime/model_providers/__base/speech2text_model.py index 97ff322f09..c69f65b681 100644 --- a/api/core/model_runtime/model_providers/__base/speech2text_model.py +++ b/api/core/model_runtime/model_providers/__base/speech2text_model.py @@ -4,7 +4,7 @@ from pydantic import ConfigDict from core.model_runtime.entities.model_entities import ModelType from core.model_runtime.model_providers.__base.ai_model import AIModel -from core.plugin.manager.model import PluginModelManager +from core.plugin.impl.model import PluginModelClient class Speech2TextModel(AIModel): @@ -28,7 +28,7 @@ class Speech2TextModel(AIModel): :return: text for given audio file """ try: - plugin_model_manager = PluginModelManager() + plugin_model_manager = PluginModelClient() return plugin_model_manager.invoke_speech_to_text( tenant_id=self.tenant_id, user_id=user or "unknown", diff --git a/api/core/model_runtime/model_providers/__base/text_embedding_model.py b/api/core/model_runtime/model_providers/__base/text_embedding_model.py index c4c1f92177..f7bba0eba1 100644 --- a/api/core/model_runtime/model_providers/__base/text_embedding_model.py +++ b/api/core/model_runtime/model_providers/__base/text_embedding_model.py @@ -6,7 +6,7 @@ from core.entities.embedding_type import EmbeddingInputType from core.model_runtime.entities.model_entities import ModelPropertyKey, ModelType from core.model_runtime.entities.text_embedding_entities import TextEmbeddingResult from core.model_runtime.model_providers.__base.ai_model import AIModel -from core.plugin.manager.model import PluginModelManager +from core.plugin.impl.model import PluginModelClient class TextEmbeddingModel(AIModel): @@ -38,7 +38,7 @@ class TextEmbeddingModel(AIModel): :return: embeddings result """ try: - plugin_model_manager = PluginModelManager() + plugin_model_manager = PluginModelClient() return plugin_model_manager.invoke_text_embedding( tenant_id=self.tenant_id, user_id=user or "unknown", @@ -61,7 +61,7 @@ class TextEmbeddingModel(AIModel): :param texts: texts to embed :return: """ - plugin_model_manager = PluginModelManager() + plugin_model_manager = PluginModelClient() return plugin_model_manager.get_text_embedding_num_tokens( tenant_id=self.tenant_id, user_id="unknown", diff --git a/api/core/model_runtime/model_providers/__base/tokenizers/gpt2_tokenzier.py b/api/core/model_runtime/model_providers/__base/tokenizers/gpt2_tokenzier.py index 2f6f4fbbef..b7db0b78bc 100644 --- a/api/core/model_runtime/model_providers/__base/tokenizers/gpt2_tokenzier.py +++ b/api/core/model_runtime/model_providers/__base/tokenizers/gpt2_tokenzier.py @@ -30,6 +30,8 @@ class GPT2Tokenizer: @staticmethod def get_encoder() -> Any: global _tokenizer, _lock + if _tokenizer is not None: + return _tokenizer with _lock: if _tokenizer is None: # Try to use tiktoken to get the tokenizer because it is faster diff --git a/api/core/model_runtime/model_providers/__base/tts_model.py b/api/core/model_runtime/model_providers/__base/tts_model.py index 1f248d11ac..d51831900c 100644 --- a/api/core/model_runtime/model_providers/__base/tts_model.py +++ b/api/core/model_runtime/model_providers/__base/tts_model.py @@ -6,7 +6,7 @@ from pydantic import ConfigDict from core.model_runtime.entities.model_entities import ModelType from core.model_runtime.model_providers.__base.ai_model import AIModel -from core.plugin.manager.model import PluginModelManager +from core.plugin.impl.model import PluginModelClient logger = logging.getLogger(__name__) @@ -42,7 +42,7 @@ class TTSModel(AIModel): :return: translated audio file """ try: - plugin_model_manager = PluginModelManager() + plugin_model_manager = PluginModelClient() return plugin_model_manager.invoke_tts( tenant_id=self.tenant_id, user_id=user or "unknown", @@ -65,7 +65,7 @@ class TTSModel(AIModel): :param credentials: The credentials required to access the TTS model. :return: A list of voices supported by the TTS model. """ - plugin_model_manager = PluginModelManager() + plugin_model_manager = PluginModelClient() return plugin_model_manager.get_tts_model_voices( tenant_id=self.tenant_id, user_id="unknown", diff --git a/api/core/model_runtime/model_providers/model_provider_factory.py b/api/core/model_runtime/model_providers/model_provider_factory.py index d2fd4916a4..ad46f64ec3 100644 --- a/api/core/model_runtime/model_providers/model_provider_factory.py +++ b/api/core/model_runtime/model_providers/model_provider_factory.py @@ -22,8 +22,8 @@ from core.model_runtime.schema_validators.model_credential_schema_validator impo from core.model_runtime.schema_validators.provider_credential_schema_validator import ProviderCredentialSchemaValidator from core.plugin.entities.plugin import ModelProviderID from core.plugin.entities.plugin_daemon import PluginModelProviderEntity -from core.plugin.manager.asset import PluginAssetManager -from core.plugin.manager.model import PluginModelManager +from core.plugin.impl.asset import PluginAssetManager +from core.plugin.impl.model import PluginModelClient logger = logging.getLogger(__name__) @@ -40,7 +40,7 @@ class ModelProviderFactory: self.provider_position_map = {} self.tenant_id = tenant_id - self.plugin_model_manager = PluginModelManager() + self.plugin_model_manager = PluginModelClient() if not self.provider_position_map: # get the path of current classes diff --git a/api/core/model_runtime/utils/encoders.py b/api/core/model_runtime/utils/encoders.py index 03e3506271..a5c11aeeba 100644 --- a/api/core/model_runtime/utils/encoders.py +++ b/api/core/model_runtime/utils/encoders.py @@ -129,17 +129,18 @@ def jsonable_encoder( sqlalchemy_safe=sqlalchemy_safe, ) if dataclasses.is_dataclass(obj): - # FIXME: mypy error, try to fix it instead of using type: ignore - obj_dict = dataclasses.asdict(obj) # type: ignore - return jsonable_encoder( - obj_dict, - by_alias=by_alias, - exclude_unset=exclude_unset, - exclude_defaults=exclude_defaults, - exclude_none=exclude_none, - custom_encoder=custom_encoder, - sqlalchemy_safe=sqlalchemy_safe, - ) + # Ensure obj is a dataclass instance, not a dataclass type + if not isinstance(obj, type): + obj_dict = dataclasses.asdict(obj) + return jsonable_encoder( + obj_dict, + by_alias=by_alias, + exclude_unset=exclude_unset, + exclude_defaults=exclude_defaults, + exclude_none=exclude_none, + custom_encoder=custom_encoder, + sqlalchemy_safe=sqlalchemy_safe, + ) if isinstance(obj, Enum): return obj.value if isinstance(obj, PurePath): diff --git a/api/core/moderation/api/__builtin__ b/api/core/moderation/api/__builtin__ index e440e5c842..00750edc07 100644 --- a/api/core/moderation/api/__builtin__ +++ b/api/core/moderation/api/__builtin__ @@ -1 +1 @@ -3 \ No newline at end of file +3 diff --git a/api/core/moderation/keywords/__builtin__ b/api/core/moderation/keywords/__builtin__ index d8263ee986..0cfbf08886 100644 --- a/api/core/moderation/keywords/__builtin__ +++ b/api/core/moderation/keywords/__builtin__ @@ -1 +1 @@ -2 \ No newline at end of file +2 diff --git a/api/core/moderation/openai_moderation/__builtin__ b/api/core/moderation/openai_moderation/__builtin__ index 56a6051ca2..d00491fd7e 100644 --- a/api/core/moderation/openai_moderation/__builtin__ +++ b/api/core/moderation/openai_moderation/__builtin__ @@ -1 +1 @@ -1 \ No newline at end of file +1 diff --git a/api/core/moderation/output_moderation.py b/api/core/moderation/output_moderation.py index e595be126c..2ec315417f 100644 --- a/api/core/moderation/output_moderation.py +++ b/api/core/moderation/output_moderation.py @@ -46,14 +46,14 @@ class OutputModeration(BaseModel): if not self.thread: self.thread = self.start_thread() - def moderation_completion(self, completion: str, public_event: bool = False) -> str: + def moderation_completion(self, completion: str, public_event: bool = False) -> tuple[str, bool]: self.buffer = completion self.is_final_chunk = True result = self.moderation(tenant_id=self.tenant_id, app_id=self.app_id, moderation_buffer=completion) if not result or not result.flagged: - return completion + return completion, False if result.action == ModerationAction.DIRECT_OUTPUT: final_output = result.preset_response @@ -61,9 +61,14 @@ class OutputModeration(BaseModel): final_output = result.text if public_event: - self.queue_manager.publish(QueueMessageReplaceEvent(text=final_output), PublishFrom.TASK_PIPELINE) + self.queue_manager.publish( + QueueMessageReplaceEvent( + text=final_output, reason=QueueMessageReplaceEvent.MessageReplaceReason.OUTPUT_MODERATION + ), + PublishFrom.TASK_PIPELINE, + ) - return final_output + return final_output, True def start_thread(self) -> threading.Thread: buffer_size = dify_config.MODERATION_BUFFER_SIZE @@ -112,7 +117,12 @@ class OutputModeration(BaseModel): # trigger replace event if self.thread_running: - self.queue_manager.publish(QueueMessageReplaceEvent(text=final_output), PublishFrom.TASK_PIPELINE) + self.queue_manager.publish( + QueueMessageReplaceEvent( + text=final_output, reason=QueueMessageReplaceEvent.MessageReplaceReason.OUTPUT_MODERATION + ), + PublishFrom.TASK_PIPELINE, + ) if result.action == ModerationAction.DIRECT_OUTPUT: break diff --git a/api/core/ops/base_trace_instance.py b/api/core/ops/base_trace_instance.py index f7b882fc71..8593198bc2 100644 --- a/api/core/ops/base_trace_instance.py +++ b/api/core/ops/base_trace_instance.py @@ -1,7 +1,11 @@ from abc import ABC, abstractmethod +from sqlalchemy.orm import Session + from core.ops.entities.config_entity import BaseTracingConfig from core.ops.entities.trace_entity import BaseTraceInfo +from extensions.ext_database import db +from models import Account, App, TenantAccountJoin class BaseTraceInstance(ABC): @@ -24,3 +28,38 @@ class BaseTraceInstance(ABC): Subclasses must implement specific tracing logic for activities. """ ... + + def get_service_account_with_tenant(self, app_id: str) -> Account: + """ + Get service account for an app and set up its tenant. + + Args: + app_id: The ID of the app + + Returns: + Account: The service account with tenant set up + + Raises: + ValueError: If app, creator account or tenant cannot be found + """ + with Session(db.engine, expire_on_commit=False) as session: + # Get the app to find its creator + app = session.query(App).filter(App.id == app_id).first() + if not app: + raise ValueError(f"App with id {app_id} not found") + + if not app.created_by: + raise ValueError(f"App with id {app_id} has no creator (created_by is None)") + + service_account = session.query(Account).filter(Account.id == app.created_by).first() + if not service_account: + raise ValueError(f"Creator account with id {app.created_by} not found for app {app_id}") + + current_tenant = ( + session.query(TenantAccountJoin).filter_by(account_id=service_account.id, current=True).first() + ) + if not current_tenant: + raise ValueError(f"Current tenant not found for account {service_account.id}") + service_account.set_tenant_id(current_tenant.tenant_id) + + return service_account diff --git a/api/core/ops/entities/config_entity.py b/api/core/ops/entities/config_entity.py index b484242b61..c988bf48d1 100644 --- a/api/core/ops/entities/config_entity.py +++ b/api/core/ops/entities/config_entity.py @@ -1,12 +1,13 @@ -from enum import Enum +from enum import StrEnum from pydantic import BaseModel, ValidationInfo, field_validator -class TracingProviderEnum(Enum): +class TracingProviderEnum(StrEnum): LANGFUSE = "langfuse" LANGSMITH = "langsmith" OPIK = "opik" + WEAVE = "weave" class BaseTracingConfig(BaseModel): @@ -88,5 +89,35 @@ class OpikConfig(BaseTracingConfig): return v +class WeaveConfig(BaseTracingConfig): + """ + Model class for Weave tracing config. + """ + + api_key: str + entity: str | None = None + project: str + endpoint: str = "https://trace.wandb.ai" + host: str | None = None + + @field_validator("endpoint") + @classmethod + def set_value(cls, v, info: ValidationInfo): + if v is None or v == "": + v = "https://trace.wandb.ai" + if not v.startswith("https://"): + raise ValueError("endpoint must start with https://") + + return v + + @field_validator("host") + @classmethod + def validate_host(cls, v, info: ValidationInfo): + if v is not None and v != "": + if not v.startswith(("https://", "http://")): + raise ValueError("host must start with https:// or http://") + return v + + OPS_FILE_PATH = "ops_trace/" OPS_TRACE_FAILED_KEY = "FAILED_OPS_TRACE" diff --git a/api/core/ops/entities/trace_entity.py b/api/core/ops/entities/trace_entity.py index f0e34c0cd7..151fa2aaf4 100644 --- a/api/core/ops/entities/trace_entity.py +++ b/api/core/ops/entities/trace_entity.py @@ -3,7 +3,7 @@ from datetime import datetime from enum import StrEnum from typing import Any, Optional, Union -from pydantic import BaseModel, ConfigDict, field_validator +from pydantic import BaseModel, ConfigDict, field_serializer, field_validator class BaseTraceInfo(BaseModel): @@ -24,10 +24,13 @@ class BaseTraceInfo(BaseModel): return v return "" - class Config: - json_encoders = { - datetime: lambda v: v.isoformat(), - } + model_config = ConfigDict(protected_namespaces=()) + + @field_serializer("start_time", "end_time") + def serialize_datetime(self, dt: datetime | None) -> str | None: + if dt is None: + return None + return dt.isoformat() class WorkflowTraceInfo(BaseTraceInfo): diff --git a/api/core/ops/langfuse_trace/entities/langfuse_trace_entity.py b/api/core/ops/langfuse_trace/entities/langfuse_trace_entity.py index f486da3a6d..46ba1c45b9 100644 --- a/api/core/ops/langfuse_trace/entities/langfuse_trace_entity.py +++ b/api/core/ops/langfuse_trace/entities/langfuse_trace_entity.py @@ -1,3 +1,4 @@ +from collections.abc import Mapping from datetime import datetime from enum import StrEnum from typing import Any, Optional, Union @@ -155,10 +156,10 @@ class LangfuseSpan(BaseModel): description="The status message of the span. Additional field for context of the event. E.g. the error " "message of an error event.", ) - input: Optional[Union[str, dict[str, Any], list, None]] = Field( + input: Optional[Union[str, Mapping[str, Any], list, None]] = Field( default=None, description="The input of the span. Can be any JSON object." ) - output: Optional[Union[str, dict[str, Any], list, None]] = Field( + output: Optional[Union[str, Mapping[str, Any], list, None]] = Field( default=None, description="The output of the span. Can be any JSON object." ) version: Optional[str] = Field( diff --git a/api/core/ops/langfuse_trace/langfuse_trace.py b/api/core/ops/langfuse_trace/langfuse_trace.py index fa78b7b8e9..0ea74e9ef0 100644 --- a/api/core/ops/langfuse_trace/langfuse_trace.py +++ b/api/core/ops/langfuse_trace/langfuse_trace.py @@ -1,4 +1,3 @@ -import json import logging import os from datetime import datetime, timedelta @@ -29,9 +28,10 @@ from core.ops.langfuse_trace.entities.langfuse_trace_entity import ( UnitEnum, ) from core.ops.utils import filter_none_values -from core.repository.repository_factory import RepositoryFactory +from core.repositories import SQLAlchemyWorkflowNodeExecutionRepository +from core.workflow.nodes.enums import NodeType from extensions.ext_database import db -from models.model import EndUser +from models import EndUser, WorkflowNodeExecutionTriggeredFrom logger = logging.getLogger(__name__) @@ -113,8 +113,18 @@ class LangFuseDataTrace(BaseTraceInstance): # through workflow_run_id get all_nodes_execution using repository session_factory = sessionmaker(bind=db.engine) - workflow_node_execution_repository = RepositoryFactory.create_workflow_node_execution_repository( - params={"tenant_id": trace_info.tenant_id, "session_factory": session_factory}, + # Find the app's creator account + app_id = trace_info.metadata.get("app_id") + if not app_id: + raise ValueError("No app_id found in trace_info metadata") + + service_account = self.get_service_account_with_tenant(app_id) + + workflow_node_execution_repository = SQLAlchemyWorkflowNodeExecutionRepository( + session_factory=session_factory, + user=service_account, + app_id=trace_info.metadata.get("app_id"), + triggered_from=WorkflowNodeExecutionTriggeredFrom.WORKFLOW_RUN, ) # Get all executions for this workflow run @@ -124,23 +134,22 @@ class LangFuseDataTrace(BaseTraceInstance): for node_execution in workflow_node_executions: node_execution_id = node_execution.id - tenant_id = node_execution.tenant_id - app_id = node_execution.app_id + tenant_id = trace_info.tenant_id # Use from trace_info instead + app_id = trace_info.metadata.get("app_id") # Use from trace_info instead node_name = node_execution.title node_type = node_execution.node_type status = node_execution.status - if node_type == "llm": - inputs = ( - json.loads(node_execution.process_data).get("prompts", {}) if node_execution.process_data else {} - ) + if node_type == NodeType.LLM: + inputs = node_execution.process_data.get("prompts", {}) if node_execution.process_data else {} else: - inputs = json.loads(node_execution.inputs) if node_execution.inputs else {} - outputs = json.loads(node_execution.outputs) if node_execution.outputs else {} + inputs = node_execution.inputs if node_execution.inputs else {} + outputs = node_execution.outputs if node_execution.outputs else {} created_at = node_execution.created_at or datetime.now() elapsed_time = node_execution.elapsed_time finished_at = created_at + timedelta(seconds=elapsed_time) - metadata = json.loads(node_execution.execution_metadata) if node_execution.execution_metadata else {} + execution_metadata = node_execution.metadata if node_execution.metadata else {} + metadata = {str(k): v for k, v in execution_metadata.items()} metadata.update( { "workflow_run_id": trace_info.workflow_run_id, @@ -152,7 +161,7 @@ class LangFuseDataTrace(BaseTraceInstance): "status": status, } ) - process_data = json.loads(node_execution.process_data) if node_execution.process_data else {} + process_data = node_execution.process_data if node_execution.process_data else {} model_provider = process_data.get("model_provider", None) model_name = process_data.get("model_name", None) if model_provider is not None and model_name is not None: diff --git a/api/core/ops/langsmith_trace/entities/langsmith_trace_entity.py b/api/core/ops/langsmith_trace/entities/langsmith_trace_entity.py index 348b7ba501..4fd01136ba 100644 --- a/api/core/ops/langsmith_trace/entities/langsmith_trace_entity.py +++ b/api/core/ops/langsmith_trace/entities/langsmith_trace_entity.py @@ -1,3 +1,4 @@ +from collections.abc import Mapping from datetime import datetime from enum import StrEnum from typing import Any, Optional, Union @@ -30,8 +31,8 @@ class LangSmithMultiModel(BaseModel): class LangSmithRunModel(LangSmithTokenUsage, LangSmithMultiModel): name: Optional[str] = Field(..., description="Name of the run") - inputs: Optional[Union[str, dict[str, Any], list, None]] = Field(None, description="Inputs of the run") - outputs: Optional[Union[str, dict[str, Any], list, None]] = Field(None, description="Outputs of the run") + inputs: Optional[Union[str, Mapping[str, Any], list, None]] = Field(None, description="Inputs of the run") + outputs: Optional[Union[str, Mapping[str, Any], list, None]] = Field(None, description="Outputs of the run") run_type: LangSmithRunType = Field(..., description="Type of the run") start_time: Optional[datetime | str] = Field(None, description="Start time of the run") end_time: Optional[datetime | str] = Field(None, description="End time of the run") diff --git a/api/core/ops/langsmith_trace/langsmith_trace.py b/api/core/ops/langsmith_trace/langsmith_trace.py index 85a0eafdc1..8a392940db 100644 --- a/api/core/ops/langsmith_trace/langsmith_trace.py +++ b/api/core/ops/langsmith_trace/langsmith_trace.py @@ -1,4 +1,3 @@ -import json import logging import os import uuid @@ -28,9 +27,11 @@ from core.ops.langsmith_trace.entities.langsmith_trace_entity import ( LangSmithRunUpdateModel, ) from core.ops.utils import filter_none_values, generate_dotted_order -from core.repository.repository_factory import RepositoryFactory +from core.repositories import SQLAlchemyWorkflowNodeExecutionRepository +from core.workflow.entities.workflow_node_execution import WorkflowNodeExecutionMetadataKey +from core.workflow.nodes.enums import NodeType from extensions.ext_database import db -from models.model import EndUser, MessageFile +from models import EndUser, MessageFile, WorkflowNodeExecutionTriggeredFrom logger = logging.getLogger(__name__) @@ -137,12 +138,18 @@ class LangSmithDataTrace(BaseTraceInstance): # through workflow_run_id get all_nodes_execution using repository session_factory = sessionmaker(bind=db.engine) - workflow_node_execution_repository = RepositoryFactory.create_workflow_node_execution_repository( - params={ - "tenant_id": trace_info.tenant_id, - "app_id": trace_info.metadata.get("app_id"), - "session_factory": session_factory, - }, + # Find the app's creator account + app_id = trace_info.metadata.get("app_id") + if not app_id: + raise ValueError("No app_id found in trace_info metadata") + + service_account = self.get_service_account_with_tenant(app_id) + + workflow_node_execution_repository = SQLAlchemyWorkflowNodeExecutionRepository( + session_factory=session_factory, + user=service_account, + app_id=trace_info.metadata.get("app_id"), + triggered_from=WorkflowNodeExecutionTriggeredFrom.WORKFLOW_RUN, ) # Get all executions for this workflow run @@ -152,27 +159,23 @@ class LangSmithDataTrace(BaseTraceInstance): for node_execution in workflow_node_executions: node_execution_id = node_execution.id - tenant_id = node_execution.tenant_id - app_id = node_execution.app_id + tenant_id = trace_info.tenant_id # Use from trace_info instead + app_id = trace_info.metadata.get("app_id") # Use from trace_info instead node_name = node_execution.title node_type = node_execution.node_type status = node_execution.status - if node_type == "llm": - inputs = ( - json.loads(node_execution.process_data).get("prompts", {}) if node_execution.process_data else {} - ) + if node_type == NodeType.LLM: + inputs = node_execution.process_data.get("prompts", {}) if node_execution.process_data else {} else: - inputs = json.loads(node_execution.inputs) if node_execution.inputs else {} - outputs = json.loads(node_execution.outputs) if node_execution.outputs else {} + inputs = node_execution.inputs if node_execution.inputs else {} + outputs = node_execution.outputs if node_execution.outputs else {} created_at = node_execution.created_at or datetime.now() elapsed_time = node_execution.elapsed_time finished_at = created_at + timedelta(seconds=elapsed_time) - execution_metadata = ( - json.loads(node_execution.execution_metadata) if node_execution.execution_metadata else {} - ) - node_total_tokens = execution_metadata.get("total_tokens", 0) - metadata = execution_metadata.copy() + execution_metadata = node_execution.metadata if node_execution.metadata else {} + node_total_tokens = execution_metadata.get(WorkflowNodeExecutionMetadataKey.TOTAL_TOKENS) or 0 + metadata = {str(key): value for key, value in execution_metadata.items()} metadata.update( { "workflow_run_id": trace_info.workflow_run_id, @@ -185,7 +188,7 @@ class LangSmithDataTrace(BaseTraceInstance): } ) - process_data = json.loads(node_execution.process_data) if node_execution.process_data else {} + process_data = node_execution.process_data if node_execution.process_data else {} if process_data and process_data.get("model_mode") == "chat": run_type = LangSmithRunType.llm @@ -195,7 +198,7 @@ class LangSmithDataTrace(BaseTraceInstance): "ls_model_name": process_data.get("model_name", ""), } ) - elif node_type == "knowledge-retrieval": + elif node_type == NodeType.KNOWLEDGE_RETRIEVAL: run_type = LangSmithRunType.retriever else: run_type = LangSmithRunType.tool diff --git a/api/core/ops/opik_trace/opik_trace.py b/api/core/ops/opik_trace/opik_trace.py index 923b9a24ed..f4d2760ba5 100644 --- a/api/core/ops/opik_trace/opik_trace.py +++ b/api/core/ops/opik_trace/opik_trace.py @@ -1,4 +1,3 @@ -import json import logging import os import uuid @@ -22,9 +21,11 @@ from core.ops.entities.trace_entity import ( TraceTaskName, WorkflowTraceInfo, ) -from core.repository.repository_factory import RepositoryFactory +from core.repositories import SQLAlchemyWorkflowNodeExecutionRepository +from core.workflow.entities.workflow_node_execution import WorkflowNodeExecutionMetadataKey +from core.workflow.nodes.enums import NodeType from extensions.ext_database import db -from models.model import EndUser, MessageFile +from models import EndUser, MessageFile, WorkflowNodeExecutionTriggeredFrom logger = logging.getLogger(__name__) @@ -114,6 +115,7 @@ class OpikDataTrace(BaseTraceInstance): "metadata": workflow_metadata, "input": wrap_dict("input", trace_info.workflow_run_inputs), "output": wrap_dict("output", trace_info.workflow_run_outputs), + "thread_id": trace_info.conversation_id, "tags": ["message", "workflow"], "project_name": self.project, } @@ -143,6 +145,7 @@ class OpikDataTrace(BaseTraceInstance): "metadata": workflow_metadata, "input": wrap_dict("input", trace_info.workflow_run_inputs), "output": wrap_dict("output", trace_info.workflow_run_outputs), + "thread_id": trace_info.conversation_id, "tags": ["workflow"], "project_name": self.project, } @@ -150,12 +153,18 @@ class OpikDataTrace(BaseTraceInstance): # through workflow_run_id get all_nodes_execution using repository session_factory = sessionmaker(bind=db.engine) - workflow_node_execution_repository = RepositoryFactory.create_workflow_node_execution_repository( - params={ - "tenant_id": trace_info.tenant_id, - "app_id": trace_info.metadata.get("app_id"), - "session_factory": session_factory, - }, + # Find the app's creator account + app_id = trace_info.metadata.get("app_id") + if not app_id: + raise ValueError("No app_id found in trace_info metadata") + + service_account = self.get_service_account_with_tenant(app_id) + + workflow_node_execution_repository = SQLAlchemyWorkflowNodeExecutionRepository( + session_factory=session_factory, + user=service_account, + app_id=trace_info.metadata.get("app_id"), + triggered_from=WorkflowNodeExecutionTriggeredFrom.WORKFLOW_RUN, ) # Get all executions for this workflow run @@ -165,26 +174,22 @@ class OpikDataTrace(BaseTraceInstance): for node_execution in workflow_node_executions: node_execution_id = node_execution.id - tenant_id = node_execution.tenant_id - app_id = node_execution.app_id + tenant_id = trace_info.tenant_id # Use from trace_info instead + app_id = trace_info.metadata.get("app_id") # Use from trace_info instead node_name = node_execution.title node_type = node_execution.node_type status = node_execution.status - if node_type == "llm": - inputs = ( - json.loads(node_execution.process_data).get("prompts", {}) if node_execution.process_data else {} - ) + if node_type == NodeType.LLM: + inputs = node_execution.process_data.get("prompts", {}) if node_execution.process_data else {} else: - inputs = json.loads(node_execution.inputs) if node_execution.inputs else {} - outputs = json.loads(node_execution.outputs) if node_execution.outputs else {} + inputs = node_execution.inputs if node_execution.inputs else {} + outputs = node_execution.outputs if node_execution.outputs else {} created_at = node_execution.created_at or datetime.now() elapsed_time = node_execution.elapsed_time finished_at = created_at + timedelta(seconds=elapsed_time) - execution_metadata = ( - json.loads(node_execution.execution_metadata) if node_execution.execution_metadata else {} - ) - metadata = execution_metadata.copy() + execution_metadata = node_execution.metadata if node_execution.metadata else {} + metadata = {str(k): v for k, v in execution_metadata.items()} metadata.update( { "workflow_run_id": trace_info.workflow_run_id, @@ -197,7 +202,7 @@ class OpikDataTrace(BaseTraceInstance): } ) - process_data = json.loads(node_execution.process_data) if node_execution.process_data else {} + process_data = node_execution.process_data if node_execution.process_data else {} provider = None model = None @@ -230,7 +235,7 @@ class OpikDataTrace(BaseTraceInstance): parent_span_id = trace_info.workflow_app_log_id or trace_info.workflow_run_id if not total_tokens: - total_tokens = execution_metadata.get("total_tokens", 0) + total_tokens = execution_metadata.get(WorkflowNodeExecutionMetadataKey.TOTAL_TOKENS) or 0 span_data = { "trace_id": opik_trace_id, @@ -292,6 +297,7 @@ class OpikDataTrace(BaseTraceInstance): "metadata": wrap_metadata(metadata), "input": trace_info.inputs, "output": message_data.answer, + "thread_id": message_data.conversation_id, "tags": ["message", str(trace_info.conversation_mode)], "project_name": self.project, } @@ -406,6 +412,7 @@ class OpikDataTrace(BaseTraceInstance): "metadata": wrap_metadata(trace_info.metadata), "input": trace_info.inputs, "output": trace_info.outputs, + "thread_id": trace_info.conversation_id, "tags": ["generate_name"], "project_name": self.project, } diff --git a/api/core/ops/ops_trace_manager.py b/api/core/ops/ops_trace_manager.py index 6fc02393fe..e0dfe0c312 100644 --- a/api/core/ops/ops_trace_manager.py +++ b/api/core/ops/ops_trace_manager.py @@ -16,9 +16,6 @@ from sqlalchemy.orm import Session from core.helper.encrypter import decrypt_token, encrypt_token, obfuscated_token from core.ops.entities.config_entity import ( OPS_FILE_PATH, - LangfuseConfig, - LangSmithConfig, - OpikConfig, TracingProviderEnum, ) from core.ops.entities.trace_entity import ( @@ -32,9 +29,8 @@ from core.ops.entities.trace_entity import ( TraceTaskName, WorkflowTraceInfo, ) -from core.ops.langfuse_trace.langfuse_trace import LangFuseDataTrace -from core.ops.langsmith_trace.langsmith_trace import LangSmithDataTrace from core.ops.utils import get_message_data +from core.workflow.entities.workflow_execution import WorkflowExecution from extensions.ext_database import db from extensions.ext_storage import storage from models.model import App, AppModelConfig, Conversation, Message, MessageFile, TraceAppConfig @@ -42,32 +38,58 @@ from models.workflow import WorkflowAppLog, WorkflowRun from tasks.ops_trace_task import process_trace_tasks -def build_opik_trace_instance(config: OpikConfig): - from core.ops.opik_trace.opik_trace import OpikDataTrace +class OpsTraceProviderConfigMap(dict[str, dict[str, Any]]): + def __getitem__(self, provider: str) -> dict[str, Any]: + match provider: + case TracingProviderEnum.LANGFUSE: + from core.ops.entities.config_entity import LangfuseConfig + from core.ops.langfuse_trace.langfuse_trace import LangFuseDataTrace - return OpikDataTrace(config) + return { + "config_class": LangfuseConfig, + "secret_keys": ["public_key", "secret_key"], + "other_keys": ["host", "project_key"], + "trace_instance": LangFuseDataTrace, + } + + case TracingProviderEnum.LANGSMITH: + from core.ops.entities.config_entity import LangSmithConfig + from core.ops.langsmith_trace.langsmith_trace import LangSmithDataTrace + return { + "config_class": LangSmithConfig, + "secret_keys": ["api_key"], + "other_keys": ["project", "endpoint"], + "trace_instance": LangSmithDataTrace, + } -provider_config_map: dict[str, dict[str, Any]] = { - TracingProviderEnum.LANGFUSE.value: { - "config_class": LangfuseConfig, - "secret_keys": ["public_key", "secret_key"], - "other_keys": ["host", "project_key"], - "trace_instance": LangFuseDataTrace, - }, - TracingProviderEnum.LANGSMITH.value: { - "config_class": LangSmithConfig, - "secret_keys": ["api_key"], - "other_keys": ["project", "endpoint"], - "trace_instance": LangSmithDataTrace, - }, - TracingProviderEnum.OPIK.value: { - "config_class": OpikConfig, - "secret_keys": ["api_key"], - "other_keys": ["project", "url", "workspace"], - "trace_instance": lambda config: build_opik_trace_instance(config), - }, -} + case TracingProviderEnum.OPIK: + from core.ops.entities.config_entity import OpikConfig + from core.ops.opik_trace.opik_trace import OpikDataTrace + + return { + "config_class": OpikConfig, + "secret_keys": ["api_key"], + "other_keys": ["project", "url", "workspace"], + "trace_instance": OpikDataTrace, + } + + case TracingProviderEnum.WEAVE: + from core.ops.entities.config_entity import WeaveConfig + from core.ops.weave_trace.weave_trace import WeaveDataTrace + + return { + "config_class": WeaveConfig, + "secret_keys": ["api_key"], + "other_keys": ["project", "entity", "endpoint", "host"], + "trace_instance": WeaveDataTrace, + } + + case _: + raise KeyError(f"Unsupported tracing provider: {provider}") + + +provider_config_map: dict[str, dict[str, Any]] = OpsTraceProviderConfigMap() class OpsTraceManager: @@ -213,7 +235,11 @@ class OpsTraceManager: return None tracing_provider = app_ops_trace_config.get("tracing_provider") - if tracing_provider is None or tracing_provider not in provider_config_map: + if tracing_provider is None: + return None + try: + provider_config_map[tracing_provider] + except KeyError: return None # decrypt_token @@ -266,8 +292,14 @@ class OpsTraceManager: :return: """ # auth check - if tracing_provider not in provider_config_map and tracing_provider is not None: - raise ValueError(f"Invalid tracing provider: {tracing_provider}") + if enabled == True: + try: + provider_config_map[tracing_provider] + except KeyError: + raise ValueError(f"Invalid tracing provider: {tracing_provider}") + else: + if tracing_provider is not None: + raise ValueError(f"Invalid tracing provider: {tracing_provider}") app_config: Optional[App] = db.session.query(App).filter(App.id == app_id).first() if not app_config: @@ -346,7 +378,7 @@ class TraceTask: self, trace_type: Any, message_id: Optional[str] = None, - workflow_run: Optional[WorkflowRun] = None, + workflow_execution: Optional[WorkflowExecution] = None, conversation_id: Optional[str] = None, user_id: Optional[str] = None, timer: Optional[Any] = None, @@ -354,7 +386,7 @@ class TraceTask: ): self.trace_type = trace_type self.message_id = message_id - self.workflow_run_id = workflow_run.id if workflow_run else None + self.workflow_run_id = workflow_execution.id_ if workflow_execution else None self.conversation_id = conversation_id self.user_id = user_id self.timer = timer @@ -455,6 +487,7 @@ class TraceTask: "file_list": file_list, "triggered_from": workflow_run.triggered_from, "user_id": user_id, + "app_id": workflow_run.app_id, } workflow_trace_info = WorkflowTraceInfo( diff --git a/api/core/ops/weave_trace/__init__.py b/api/core/ops/weave_trace/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/api/core/ops/weave_trace/entities/__init__.py b/api/core/ops/weave_trace/entities/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/api/core/ops/weave_trace/entities/weave_trace_entity.py b/api/core/ops/weave_trace/entities/weave_trace_entity.py new file mode 100644 index 0000000000..7f489f37ac --- /dev/null +++ b/api/core/ops/weave_trace/entities/weave_trace_entity.py @@ -0,0 +1,98 @@ +from collections.abc import Mapping +from typing import Any, Optional, Union + +from pydantic import BaseModel, Field, field_validator +from pydantic_core.core_schema import ValidationInfo + +from core.ops.utils import replace_text_with_content + + +class WeaveTokenUsage(BaseModel): + input_tokens: Optional[int] = None + output_tokens: Optional[int] = None + total_tokens: Optional[int] = None + + +class WeaveMultiModel(BaseModel): + file_list: Optional[list[str]] = Field(None, description="List of files") + + +class WeaveTraceModel(WeaveTokenUsage, WeaveMultiModel): + id: str = Field(..., description="ID of the trace") + op: str = Field(..., description="Name of the operation") + inputs: Optional[Union[str, Mapping[str, Any], list, None]] = Field(None, description="Inputs of the trace") + outputs: Optional[Union[str, Mapping[str, Any], list, None]] = Field(None, description="Outputs of the trace") + attributes: Optional[Union[str, dict[str, Any], list, None]] = Field( + None, description="Metadata and attributes associated with trace" + ) + exception: Optional[str] = Field(None, description="Exception message of the trace") + + @field_validator("inputs", "outputs") + @classmethod + def ensure_dict(cls, v, info: ValidationInfo): + field_name = info.field_name + values = info.data + if v == {} or v is None: + return v + usage_metadata = { + "input_tokens": values.get("input_tokens", 0), + "output_tokens": values.get("output_tokens", 0), + "total_tokens": values.get("total_tokens", 0), + } + file_list = values.get("file_list", []) + if isinstance(v, str): + if field_name == "inputs": + return { + "messages": { + "role": "user", + "content": v, + "usage_metadata": usage_metadata, + "file_list": file_list, + }, + } + elif field_name == "outputs": + return { + "choices": { + "role": "ai", + "content": v, + "usage_metadata": usage_metadata, + "file_list": file_list, + }, + } + elif isinstance(v, list): + data = {} + if len(v) > 0 and isinstance(v[0], dict): + # rename text to content + v = replace_text_with_content(data=v) + if field_name == "inputs": + data = { + "messages": [ + dict(msg, **{"usage_metadata": usage_metadata, "file_list": file_list}) for msg in v + ] + if isinstance(v, list) + else v, + } + elif field_name == "outputs": + data = { + "choices": { + "role": "ai", + "content": v, + "usage_metadata": usage_metadata, + "file_list": file_list, + }, + } + return data + else: + return { + "choices": { + "role": "ai" if field_name == "outputs" else "user", + "content": str(v), + "usage_metadata": usage_metadata, + "file_list": file_list, + }, + } + if isinstance(v, dict): + v["usage_metadata"] = usage_metadata + v["file_list"] = file_list + return v + return v diff --git a/api/core/ops/weave_trace/weave_trace.py b/api/core/ops/weave_trace/weave_trace.py new file mode 100644 index 0000000000..3917348a91 --- /dev/null +++ b/api/core/ops/weave_trace/weave_trace.py @@ -0,0 +1,419 @@ +import logging +import os +import uuid +from datetime import datetime, timedelta +from typing import Any, Optional, cast + +import wandb +import weave +from sqlalchemy.orm import sessionmaker + +from core.ops.base_trace_instance import BaseTraceInstance +from core.ops.entities.config_entity import WeaveConfig +from core.ops.entities.trace_entity import ( + BaseTraceInfo, + DatasetRetrievalTraceInfo, + GenerateNameTraceInfo, + MessageTraceInfo, + ModerationTraceInfo, + SuggestedQuestionTraceInfo, + ToolTraceInfo, + TraceTaskName, + WorkflowTraceInfo, +) +from core.ops.weave_trace.entities.weave_trace_entity import WeaveTraceModel +from core.repositories import SQLAlchemyWorkflowNodeExecutionRepository +from core.workflow.entities.workflow_node_execution import WorkflowNodeExecutionMetadataKey +from core.workflow.nodes.enums import NodeType +from extensions.ext_database import db +from models import EndUser, MessageFile, WorkflowNodeExecutionTriggeredFrom + +logger = logging.getLogger(__name__) + + +class WeaveDataTrace(BaseTraceInstance): + def __init__( + self, + weave_config: WeaveConfig, + ): + super().__init__(weave_config) + self.weave_api_key = weave_config.api_key + self.project_name = weave_config.project + self.entity = weave_config.entity + self.host = weave_config.host + + # Login with API key first, including host if provided + if self.host: + login_status = wandb.login(key=self.weave_api_key, verify=True, relogin=True, host=self.host) + else: + login_status = wandb.login(key=self.weave_api_key, verify=True, relogin=True) + + if not login_status: + logger.error("Failed to login to Weights & Biases with the provided API key") + raise ValueError("Weave login failed") + + # Then initialize weave client + self.weave_client = weave.init( + project_name=(f"{self.entity}/{self.project_name}" if self.entity else self.project_name) + ) + self.file_base_url = os.getenv("FILES_URL", "http://127.0.0.1:5001") + self.calls: dict[str, Any] = {} + + def get_project_url( + self, + ): + try: + project_url = f"https://wandb.ai/{self.weave_client._project_id()}" + return project_url + except Exception as e: + logger.debug(f"Weave get run url failed: {str(e)}") + raise ValueError(f"Weave get run url failed: {str(e)}") + + def trace(self, trace_info: BaseTraceInfo): + logger.debug(f"Trace info: {trace_info}") + if isinstance(trace_info, WorkflowTraceInfo): + self.workflow_trace(trace_info) + if isinstance(trace_info, MessageTraceInfo): + self.message_trace(trace_info) + if isinstance(trace_info, ModerationTraceInfo): + self.moderation_trace(trace_info) + if isinstance(trace_info, SuggestedQuestionTraceInfo): + self.suggested_question_trace(trace_info) + if isinstance(trace_info, DatasetRetrievalTraceInfo): + self.dataset_retrieval_trace(trace_info) + if isinstance(trace_info, ToolTraceInfo): + self.tool_trace(trace_info) + if isinstance(trace_info, GenerateNameTraceInfo): + self.generate_name_trace(trace_info) + + def workflow_trace(self, trace_info: WorkflowTraceInfo): + trace_id = trace_info.message_id or trace_info.workflow_run_id + if trace_info.start_time is None: + trace_info.start_time = datetime.now() + + if trace_info.message_id: + message_attributes = trace_info.metadata + message_attributes["workflow_app_log_id"] = trace_info.workflow_app_log_id + + message_attributes["message_id"] = trace_info.message_id + message_attributes["workflow_run_id"] = trace_info.workflow_run_id + message_attributes["trace_id"] = trace_id + message_attributes["start_time"] = trace_info.start_time + message_attributes["end_time"] = trace_info.end_time + message_attributes["tags"] = ["message", "workflow"] + + message_run = WeaveTraceModel( + id=trace_info.message_id, + op=str(TraceTaskName.MESSAGE_TRACE.value), + inputs=dict(trace_info.workflow_run_inputs), + outputs=dict(trace_info.workflow_run_outputs), + total_tokens=trace_info.total_tokens, + attributes=message_attributes, + exception=trace_info.error, + file_list=[], + ) + self.start_call(message_run, parent_run_id=trace_info.workflow_run_id) + self.finish_call(message_run) + + workflow_attributes = trace_info.metadata + workflow_attributes["workflow_run_id"] = trace_info.workflow_run_id + workflow_attributes["trace_id"] = trace_id + workflow_attributes["start_time"] = trace_info.start_time + workflow_attributes["end_time"] = trace_info.end_time + workflow_attributes["tags"] = ["workflow"] + + workflow_run = WeaveTraceModel( + file_list=trace_info.file_list, + total_tokens=trace_info.total_tokens, + id=trace_info.workflow_run_id, + op=str(TraceTaskName.WORKFLOW_TRACE.value), + inputs=dict(trace_info.workflow_run_inputs), + outputs=dict(trace_info.workflow_run_outputs), + attributes=workflow_attributes, + exception=trace_info.error, + ) + + self.start_call(workflow_run, parent_run_id=trace_info.message_id) + + # through workflow_run_id get all_nodes_execution using repository + session_factory = sessionmaker(bind=db.engine) + # Find the app's creator account + app_id = trace_info.metadata.get("app_id") + if not app_id: + raise ValueError("No app_id found in trace_info metadata") + + service_account = self.get_service_account_with_tenant(app_id) + + workflow_node_execution_repository = SQLAlchemyWorkflowNodeExecutionRepository( + session_factory=session_factory, + user=service_account, + app_id=trace_info.metadata.get("app_id"), + triggered_from=WorkflowNodeExecutionTriggeredFrom.WORKFLOW_RUN, + ) + + # Get all executions for this workflow run + workflow_node_executions = workflow_node_execution_repository.get_by_workflow_run( + workflow_run_id=trace_info.workflow_run_id + ) + + for node_execution in workflow_node_executions: + node_execution_id = node_execution.id + tenant_id = trace_info.tenant_id # Use from trace_info instead + app_id = trace_info.metadata.get("app_id") # Use from trace_info instead + node_name = node_execution.title + node_type = node_execution.node_type + status = node_execution.status + if node_type == NodeType.LLM: + inputs = node_execution.process_data.get("prompts", {}) if node_execution.process_data else {} + else: + inputs = node_execution.inputs if node_execution.inputs else {} + outputs = node_execution.outputs if node_execution.outputs else {} + created_at = node_execution.created_at or datetime.now() + elapsed_time = node_execution.elapsed_time + finished_at = created_at + timedelta(seconds=elapsed_time) + + execution_metadata = node_execution.metadata if node_execution.metadata else {} + node_total_tokens = execution_metadata.get(WorkflowNodeExecutionMetadataKey.TOTAL_TOKENS) or 0 + attributes = {str(k): v for k, v in execution_metadata.items()} + attributes.update( + { + "workflow_run_id": trace_info.workflow_run_id, + "node_execution_id": node_execution_id, + "tenant_id": tenant_id, + "app_id": app_id, + "app_name": node_name, + "node_type": node_type, + "status": status, + } + ) + + process_data = node_execution.process_data if node_execution.process_data else {} + if process_data and process_data.get("model_mode") == "chat": + attributes.update( + { + "ls_provider": process_data.get("model_provider", ""), + "ls_model_name": process_data.get("model_name", ""), + } + ) + attributes["tags"] = ["node_execution"] + attributes["start_time"] = created_at + attributes["end_time"] = finished_at + attributes["elapsed_time"] = elapsed_time + attributes["workflow_run_id"] = trace_info.workflow_run_id + attributes["trace_id"] = trace_id + node_run = WeaveTraceModel( + total_tokens=node_total_tokens, + op=node_type, + inputs=inputs, + outputs=outputs, + file_list=trace_info.file_list, + attributes=attributes, + id=node_execution_id, + exception=None, + ) + + self.start_call(node_run, parent_run_id=trace_info.workflow_run_id) + self.finish_call(node_run) + + self.finish_call(workflow_run) + + def message_trace(self, trace_info: MessageTraceInfo): + # get message file data + file_list = cast(list[str], trace_info.file_list) or [] + message_file_data: Optional[MessageFile] = trace_info.message_file_data + file_url = f"{self.file_base_url}/{message_file_data.url}" if message_file_data else "" + file_list.append(file_url) + attributes = trace_info.metadata + message_data = trace_info.message_data + if message_data is None: + return + message_id = message_data.id + + user_id = message_data.from_account_id + attributes["user_id"] = user_id + + if message_data.from_end_user_id: + end_user_data: Optional[EndUser] = ( + db.session.query(EndUser).filter(EndUser.id == message_data.from_end_user_id).first() + ) + if end_user_data is not None: + end_user_id = end_user_data.session_id + attributes["end_user_id"] = end_user_id + + attributes["message_id"] = message_id + attributes["start_time"] = trace_info.start_time + attributes["end_time"] = trace_info.end_time + attributes["tags"] = ["message", str(trace_info.conversation_mode)] + message_run = WeaveTraceModel( + id=message_id, + op=str(TraceTaskName.MESSAGE_TRACE.value), + input_tokens=trace_info.message_tokens, + output_tokens=trace_info.answer_tokens, + total_tokens=trace_info.total_tokens, + inputs=trace_info.inputs, + outputs=trace_info.outputs, + exception=trace_info.error, + file_list=file_list, + attributes=attributes, + ) + self.start_call(message_run) + + # create llm run parented to message run + llm_run = WeaveTraceModel( + id=str(uuid.uuid4()), + input_tokens=trace_info.message_tokens, + output_tokens=trace_info.answer_tokens, + total_tokens=trace_info.total_tokens, + op="llm", + inputs=trace_info.inputs, + outputs=trace_info.outputs, + attributes=attributes, + file_list=[], + exception=None, + ) + self.start_call( + llm_run, + parent_run_id=message_id, + ) + self.finish_call(llm_run) + self.finish_call(message_run) + + def moderation_trace(self, trace_info: ModerationTraceInfo): + if trace_info.message_data is None: + return + + attributes = trace_info.metadata + attributes["tags"] = ["moderation"] + attributes["message_id"] = trace_info.message_id + attributes["start_time"] = trace_info.start_time or trace_info.message_data.created_at + attributes["end_time"] = trace_info.end_time or trace_info.message_data.updated_at + + moderation_run = WeaveTraceModel( + id=str(uuid.uuid4()), + op=str(TraceTaskName.MODERATION_TRACE.value), + inputs=trace_info.inputs, + outputs={ + "action": trace_info.action, + "flagged": trace_info.flagged, + "preset_response": trace_info.preset_response, + "inputs": trace_info.inputs, + }, + attributes=attributes, + exception=getattr(trace_info, "error", None), + file_list=[], + ) + self.start_call(moderation_run, parent_run_id=trace_info.message_id) + self.finish_call(moderation_run) + + def suggested_question_trace(self, trace_info: SuggestedQuestionTraceInfo): + message_data = trace_info.message_data + if message_data is None: + return + attributes = trace_info.metadata + attributes["message_id"] = trace_info.message_id + attributes["tags"] = ["suggested_question"] + attributes["start_time"] = (trace_info.start_time or message_data.created_at,) + attributes["end_time"] = (trace_info.end_time or message_data.updated_at,) + + suggested_question_run = WeaveTraceModel( + id=str(uuid.uuid4()), + op=str(TraceTaskName.SUGGESTED_QUESTION_TRACE.value), + inputs=trace_info.inputs, + outputs=trace_info.suggested_question, + attributes=attributes, + exception=trace_info.error, + file_list=[], + ) + + self.start_call(suggested_question_run, parent_run_id=trace_info.message_id) + self.finish_call(suggested_question_run) + + def dataset_retrieval_trace(self, trace_info: DatasetRetrievalTraceInfo): + if trace_info.message_data is None: + return + attributes = trace_info.metadata + attributes["message_id"] = trace_info.message_id + attributes["tags"] = ["dataset_retrieval"] + attributes["start_time"] = (trace_info.start_time or trace_info.message_data.created_at,) + attributes["end_time"] = (trace_info.end_time or trace_info.message_data.updated_at,) + + dataset_retrieval_run = WeaveTraceModel( + id=str(uuid.uuid4()), + op=str(TraceTaskName.DATASET_RETRIEVAL_TRACE.value), + inputs=trace_info.inputs, + outputs={"documents": trace_info.documents}, + attributes=attributes, + exception=getattr(trace_info, "error", None), + file_list=[], + ) + + self.start_call(dataset_retrieval_run, parent_run_id=trace_info.message_id) + self.finish_call(dataset_retrieval_run) + + def tool_trace(self, trace_info: ToolTraceInfo): + attributes = trace_info.metadata + attributes["tags"] = ["tool", trace_info.tool_name] + attributes["start_time"] = trace_info.start_time + attributes["end_time"] = trace_info.end_time + + tool_run = WeaveTraceModel( + id=str(uuid.uuid4()), + op=trace_info.tool_name, + inputs=trace_info.tool_inputs, + outputs=trace_info.tool_outputs, + file_list=[cast(str, trace_info.file_url)] if trace_info.file_url else [], + attributes=attributes, + exception=trace_info.error, + ) + message_id = trace_info.message_id or getattr(trace_info, "conversation_id", None) + message_id = message_id or None + self.start_call(tool_run, parent_run_id=message_id) + self.finish_call(tool_run) + + def generate_name_trace(self, trace_info: GenerateNameTraceInfo): + attributes = trace_info.metadata + attributes["tags"] = ["generate_name"] + attributes["start_time"] = trace_info.start_time + attributes["end_time"] = trace_info.end_time + + name_run = WeaveTraceModel( + id=str(uuid.uuid4()), + op=str(TraceTaskName.GENERATE_NAME_TRACE.value), + inputs=trace_info.inputs, + outputs=trace_info.outputs, + attributes=attributes, + exception=getattr(trace_info, "error", None), + file_list=[], + ) + + self.start_call(name_run) + self.finish_call(name_run) + + def api_check(self): + try: + if self.host: + login_status = wandb.login(key=self.weave_api_key, verify=True, relogin=True, host=self.host) + else: + login_status = wandb.login(key=self.weave_api_key, verify=True, relogin=True) + + if not login_status: + raise ValueError("Weave login failed") + else: + print("Weave login successful") + return True + except Exception as e: + logger.debug(f"Weave API check failed: {str(e)}") + raise ValueError(f"Weave API check failed: {str(e)}") + + def start_call(self, run_data: WeaveTraceModel, parent_run_id: Optional[str] = None): + call = self.weave_client.create_call(op=run_data.op, inputs=run_data.inputs, attributes=run_data.attributes) + self.calls[run_data.id] = call + if parent_run_id: + self.calls[run_data.id].parent_id = parent_run_id + + def finish_call(self, run_data: WeaveTraceModel): + call = self.calls.get(run_data.id) + if call: + self.weave_client.finish_call(call=call, output=run_data.outputs, exception=run_data.exception) + else: + raise ValueError(f"Call with id {run_data.id} not found") diff --git a/api/core/plugin/backwards_invocation/app.py b/api/core/plugin/backwards_invocation/app.py index 484f52e33c..4e43561a15 100644 --- a/api/core/plugin/backwards_invocation/app.py +++ b/api/core/plugin/backwards_invocation/app.py @@ -72,7 +72,7 @@ class PluginAppBackwardsInvocation(BaseBackwardsInvocation): raise ValueError("missing query") return cls.invoke_chat_app(app, user, conversation_id, query, stream, inputs, files) - elif app.mode == AppMode.WORKFLOW.value: + elif app.mode == AppMode.WORKFLOW: return cls.invoke_workflow_app(app, user, stream, inputs, files) elif app.mode == AppMode.COMPLETION: return cls.invoke_completion_app(app, user, stream, inputs, files) diff --git a/api/core/plugin/backwards_invocation/base.py b/api/core/plugin/backwards_invocation/base.py index 3214e07469..2a5f857576 100644 --- a/api/core/plugin/backwards_invocation/base.py +++ b/api/core/plugin/backwards_invocation/base.py @@ -11,14 +11,12 @@ class BaseBackwardsInvocation: try: for chunk in response: if isinstance(chunk, BaseModel | dict): - yield BaseBackwardsInvocationResponse(data=chunk).model_dump_json().encode() + b"\n\n" - elif isinstance(chunk, str): - yield f"event: {chunk}\n\n".encode() + yield BaseBackwardsInvocationResponse(data=chunk).model_dump_json().encode() except Exception as e: error_message = BaseBackwardsInvocationResponse(error=str(e)).model_dump_json() - yield f"{error_message}\n\n".encode() + yield error_message.encode() else: - yield BaseBackwardsInvocationResponse(data=response).model_dump_json().encode() + b"\n\n" + yield BaseBackwardsInvocationResponse(data=response).model_dump_json().encode() T = TypeVar("T", bound=dict | Mapping | str | bool | int | BaseModel) diff --git a/api/core/plugin/backwards_invocation/model.py b/api/core/plugin/backwards_invocation/model.py index 490a475c16..072644e53b 100644 --- a/api/core/plugin/backwards_invocation/model.py +++ b/api/core/plugin/backwards_invocation/model.py @@ -21,7 +21,7 @@ from core.plugin.entities.request import ( ) from core.tools.entities.tool_entities import ToolProviderType from core.tools.utils.model_invocation_utils import ModelInvocationUtils -from core.workflow.nodes.llm.node import LLMNode +from core.workflow.nodes.llm import llm_utils from models.account import Tenant @@ -55,20 +55,21 @@ class PluginModelBackwardsInvocation(BaseBackwardsInvocation): def handle() -> Generator[LLMResultChunk, None, None]: for chunk in response: if chunk.delta.usage: - LLMNode.deduct_llm_quota( + llm_utils.deduct_llm_quota( tenant_id=tenant.id, model_instance=model_instance, usage=chunk.delta.usage ) + chunk.prompt_messages = [] yield chunk return handle() else: if response.usage: - LLMNode.deduct_llm_quota(tenant_id=tenant.id, model_instance=model_instance, usage=response.usage) + llm_utils.deduct_llm_quota(tenant_id=tenant.id, model_instance=model_instance, usage=response.usage) def handle_non_streaming(response: LLMResult) -> Generator[LLMResultChunk, None, None]: yield LLMResultChunk( model=response.model, - prompt_messages=response.prompt_messages, + prompt_messages=[], system_fingerprint=response.system_fingerprint, delta=LLMResultChunkDelta( index=0, @@ -239,8 +240,8 @@ class PluginModelBackwardsInvocation(BaseBackwardsInvocation): content = payload.text SUMMARY_PROMPT = """You are a professional language researcher, you are interested in the language -and you can quickly aimed at the main point of an webpage and reproduce it in your own words but -retain the original meaning and keep the key points. +and you can quickly aimed at the main point of an webpage and reproduce it in your own words but +retain the original meaning and keep the key points. however, the text you got is too long, what you got is possible a part of the text. Please summarize the text you got. diff --git a/api/core/plugin/backwards_invocation/node.py b/api/core/plugin/backwards_invocation/node.py index db07e52f3f..7898795ce2 100644 --- a/api/core/plugin/backwards_invocation/node.py +++ b/api/core/plugin/backwards_invocation/node.py @@ -64,9 +64,9 @@ class PluginNodeBackwardsInvocation(BaseBackwardsInvocation): ) return { - "inputs": execution.inputs_dict, - "outputs": execution.outputs_dict, - "process_data": execution.process_data_dict, + "inputs": execution.inputs, + "outputs": execution.outputs, + "process_data": execution.process_data, } @classmethod @@ -113,7 +113,7 @@ class PluginNodeBackwardsInvocation(BaseBackwardsInvocation): ) return { - "inputs": execution.inputs_dict, - "outputs": execution.outputs_dict, - "process_data": execution.process_data_dict, + "inputs": execution.inputs, + "outputs": execution.outputs, + "process_data": execution.process_data, } diff --git a/api/core/plugin/endpoint/exc.py b/api/core/plugin/endpoint/exc.py new file mode 100644 index 0000000000..aa29f1e9a1 --- /dev/null +++ b/api/core/plugin/endpoint/exc.py @@ -0,0 +1,6 @@ +class EndpointSetupFailedError(ValueError): + """ + Endpoint setup failed error + """ + + pass diff --git a/api/core/plugin/entities/endpoint.py b/api/core/plugin/entities/endpoint.py index 6c6c8bf9bc..d7ba75bb4f 100644 --- a/api/core/plugin/entities/endpoint.py +++ b/api/core/plugin/entities/endpoint.py @@ -24,7 +24,7 @@ class EndpointProviderDeclaration(BaseModel): """ settings: list[ProviderConfig] = Field(default_factory=list) - endpoints: Optional[list[EndpointDeclaration]] = Field(default_factory=list) + endpoints: Optional[list[EndpointDeclaration]] = Field(default_factory=list[EndpointDeclaration]) class EndpointEntity(BasePluginEntity): diff --git a/api/core/plugin/entities/plugin.py b/api/core/plugin/entities/plugin.py index 07ed94380a..bdf7d5ce1f 100644 --- a/api/core/plugin/entities/plugin.py +++ b/api/core/plugin/entities/plugin.py @@ -52,7 +52,7 @@ class PluginResourceRequirements(BaseModel): model: Optional[Model] = Field(default=None) node: Optional[Node] = Field(default=None) endpoint: Optional[Endpoint] = Field(default=None) - storage: Storage = Field(default=None) + storage: Optional[Storage] = Field(default=None) permission: Optional[Permission] = Field(default=None) @@ -66,9 +66,9 @@ class PluginCategory(enum.StrEnum): class PluginDeclaration(BaseModel): class Plugins(BaseModel): - tools: Optional[list[str]] = Field(default_factory=list) - models: Optional[list[str]] = Field(default_factory=list) - endpoints: Optional[list[str]] = Field(default_factory=list) + tools: Optional[list[str]] = Field(default_factory=list[str]) + models: Optional[list[str]] = Field(default_factory=list[str]) + endpoints: Optional[list[str]] = Field(default_factory=list[str]) class Meta(BaseModel): minimum_dify_version: Optional[str] = Field(default=None, pattern=r"^\d{1,4}(\.\d{1,4}){1,3}(-\w{1,16})?$") @@ -84,6 +84,7 @@ class PluginDeclaration(BaseModel): resource: PluginResourceRequirements plugins: Plugins tags: list[str] = Field(default_factory=list) + repo: Optional[str] = Field(default=None) verified: bool = Field(default=False) tool: Optional[ToolProviderEntity] = None model: Optional[ProviderEntity] = None diff --git a/api/core/plugin/entities/plugin_daemon.py b/api/core/plugin/entities/plugin_daemon.py index 1588cbc3c7..e9275c31cc 100644 --- a/api/core/plugin/entities/plugin_daemon.py +++ b/api/core/plugin/entities/plugin_daemon.py @@ -1,6 +1,7 @@ +from collections.abc import Mapping from datetime import datetime from enum import StrEnum -from typing import Generic, Optional, TypeVar +from typing import Any, Generic, Optional, TypeVar from pydantic import BaseModel, ConfigDict, Field @@ -8,7 +9,7 @@ from core.agent.plugin_entities import AgentProviderEntityWithPlugin from core.model_runtime.entities.model_entities import AIModelEntity from core.model_runtime.entities.provider_entities import ProviderEntity from core.plugin.entities.base import BasePluginEntity -from core.plugin.entities.plugin import PluginDeclaration +from core.plugin.entities.plugin import PluginDeclaration, PluginEntity from core.tools.entities.common_entities import I18nObject from core.tools.entities.tool_entities import ToolProviderEntityWithPlugin @@ -158,3 +159,16 @@ class PluginInstallTaskStartResponse(BaseModel): class PluginUploadResponse(BaseModel): unique_identifier: str = Field(description="The unique identifier of the plugin.") manifest: PluginDeclaration + + +class PluginOAuthAuthorizationUrlResponse(BaseModel): + authorization_url: str = Field(description="The URL of the authorization.") + + +class PluginOAuthCredentialsResponse(BaseModel): + credentials: Mapping[str, Any] = Field(description="The credentials of the OAuth.") + + +class PluginListResponse(BaseModel): + list: list[PluginEntity] + total: int diff --git a/api/core/plugin/entities/request.py b/api/core/plugin/entities/request.py index 6c0c7f2868..1692020ec8 100644 --- a/api/core/plugin/entities/request.py +++ b/api/core/plugin/entities/request.py @@ -55,8 +55,8 @@ class RequestInvokeLLM(BaseRequestInvokeModel): mode: str completion_params: dict[str, Any] = Field(default_factory=dict) prompt_messages: list[PromptMessage] = Field(default_factory=list) - tools: Optional[list[PromptMessageTool]] = Field(default_factory=list) - stop: Optional[list[str]] = Field(default_factory=list) + tools: Optional[list[PromptMessageTool]] = Field(default_factory=list[PromptMessageTool]) + stop: Optional[list[str]] = Field(default_factory=list[str]) stream: Optional[bool] = False model_config = ConfigDict(protected_namespaces=()) diff --git a/api/core/plugin/manager/agent.py b/api/core/plugin/impl/agent.py similarity index 97% rename from api/core/plugin/manager/agent.py rename to api/core/plugin/impl/agent.py index 50172f12f2..66b77c7489 100644 --- a/api/core/plugin/manager/agent.py +++ b/api/core/plugin/impl/agent.py @@ -6,10 +6,10 @@ from core.plugin.entities.plugin import GenericProviderID from core.plugin.entities.plugin_daemon import ( PluginAgentProviderEntity, ) -from core.plugin.manager.base import BasePluginManager +from core.plugin.impl.base import BasePluginClient -class PluginAgentManager(BasePluginManager): +class PluginAgentClient(BasePluginClient): def fetch_agent_strategy_providers(self, tenant_id: str) -> list[PluginAgentProviderEntity]: """ Fetch agent providers for the given tenant. diff --git a/api/core/plugin/manager/asset.py b/api/core/plugin/impl/asset.py similarity index 76% rename from api/core/plugin/manager/asset.py rename to api/core/plugin/impl/asset.py index 17755d3561..b9bfe2d2cf 100644 --- a/api/core/plugin/manager/asset.py +++ b/api/core/plugin/impl/asset.py @@ -1,7 +1,7 @@ -from core.plugin.manager.base import BasePluginManager +from core.plugin.impl.base import BasePluginClient -class PluginAssetManager(BasePluginManager): +class PluginAssetManager(BasePluginClient): def fetch_asset(self, tenant_id: str, id: str) -> bytes: """ Fetch an asset by id. diff --git a/api/core/plugin/manager/base.py b/api/core/plugin/impl/base.py similarity index 85% rename from api/core/plugin/manager/base.py rename to api/core/plugin/impl/base.py index d8d7b3e860..7375726fa9 100644 --- a/api/core/plugin/manager/base.py +++ b/api/core/plugin/impl/base.py @@ -6,6 +6,7 @@ from typing import TypeVar import requests from pydantic import BaseModel +from requests.exceptions import HTTPError from yarl import URL from configs import dify_config @@ -17,8 +18,9 @@ from core.model_runtime.errors.invoke import ( InvokeServerUnavailableError, ) from core.model_runtime.errors.validate import CredentialsValidateFailedError +from core.plugin.endpoint.exc import EndpointSetupFailedError from core.plugin.entities.plugin_daemon import PluginDaemonBasicResponse, PluginDaemonError, PluginDaemonInnerError -from core.plugin.manager.exc import ( +from core.plugin.impl.exc import ( PluginDaemonBadRequestError, PluginDaemonInternalServerError, PluginDaemonNotFoundError, @@ -29,15 +31,14 @@ from core.plugin.manager.exc import ( PluginUniqueIdentifierError, ) -plugin_daemon_inner_api_baseurl = dify_config.PLUGIN_DAEMON_URL -plugin_daemon_inner_api_key = dify_config.PLUGIN_DAEMON_KEY +plugin_daemon_inner_api_baseurl = URL(str(dify_config.PLUGIN_DAEMON_URL)) T = TypeVar("T", bound=(BaseModel | dict | list | bool | str)) logger = logging.getLogger(__name__) -class BasePluginManager: +class BasePluginClient: def _request( self, method: str, @@ -51,9 +52,9 @@ class BasePluginManager: """ Make a request to the plugin daemon inner API. """ - url = URL(str(plugin_daemon_inner_api_baseurl)) / path + url = plugin_daemon_inner_api_baseurl / path headers = headers or {} - headers["X-Api-Key"] = plugin_daemon_inner_api_key + headers["X-Api-Key"] = dify_config.PLUGIN_DAEMON_KEY headers["Accept-Encoding"] = "gzip, deflate, br" if headers.get("Content-Type") == "application/json" and isinstance(data, dict): @@ -135,12 +136,31 @@ class BasePluginManager: """ Make a request to the plugin daemon inner API and return the response as a model. """ - response = self._request(method, path, headers, data, params, files) - json_response = response.json() - if transformer: - json_response = transformer(json_response) + try: + response = self._request(method, path, headers, data, params, files) + response.raise_for_status() + except HTTPError as e: + msg = f"Failed to request plugin daemon, status: {e.response.status_code}, url: {path}" + logging.exception(msg) + raise e + except Exception as e: + msg = f"Failed to request plugin daemon, url: {path}" + logging.exception(msg) + raise ValueError(msg) from e + + try: + json_response = response.json() + if transformer: + json_response = transformer(json_response) + rep = PluginDaemonBasicResponse[type](**json_response) # type: ignore + except Exception: + msg = ( + f"Failed to parse response from plugin daemon to PluginDaemonBasicResponse [{str(type.__name__)}]," + f" url: {path}" + ) + logging.exception(msg) + raise ValueError(msg) - rep = PluginDaemonBasicResponse[type](**json_response) # type: ignore if rep.code != 0: try: error = PluginDaemonError(**json.loads(rep.message)) @@ -219,6 +239,8 @@ class BasePluginManager: raise InvokeServerUnavailableError(description=args.get("description")) case CredentialsValidateFailedError.__name__: raise CredentialsValidateFailedError(error_object.get("message")) + case EndpointSetupFailedError.__name__: + raise EndpointSetupFailedError(error_object.get("message")) case _: raise PluginInvokeError(description=message) case PluginDaemonInternalServerError.__name__: diff --git a/api/core/plugin/manager/debugging.py b/api/core/plugin/impl/debugging.py similarity index 78% rename from api/core/plugin/manager/debugging.py rename to api/core/plugin/impl/debugging.py index fb6bad7fa3..523377895c 100644 --- a/api/core/plugin/manager/debugging.py +++ b/api/core/plugin/impl/debugging.py @@ -1,9 +1,9 @@ from pydantic import BaseModel -from core.plugin.manager.base import BasePluginManager +from core.plugin.impl.base import BasePluginClient -class PluginDebuggingManager(BasePluginManager): +class PluginDebuggingClient(BasePluginClient): def get_debugging_key(self, tenant_id: str) -> str: """ Get the debugging key for the given tenant. diff --git a/api/core/plugin/manager/endpoint.py b/api/core/plugin/impl/endpoint.py similarity index 97% rename from api/core/plugin/manager/endpoint.py rename to api/core/plugin/impl/endpoint.py index 415b981ffb..5b88742be5 100644 --- a/api/core/plugin/manager/endpoint.py +++ b/api/core/plugin/impl/endpoint.py @@ -1,8 +1,8 @@ from core.plugin.entities.endpoint import EndpointEntityWithInstance -from core.plugin.manager.base import BasePluginManager +from core.plugin.impl.base import BasePluginClient -class PluginEndpointManager(BasePluginManager): +class PluginEndpointClient(BasePluginClient): def create_endpoint( self, tenant_id: str, user_id: str, plugin_unique_identifier: str, name: str, settings: dict ) -> bool: diff --git a/api/core/plugin/manager/exc.py b/api/core/plugin/impl/exc.py similarity index 100% rename from api/core/plugin/manager/exc.py rename to api/core/plugin/impl/exc.py diff --git a/api/core/plugin/manager/model.py b/api/core/plugin/impl/model.py similarity index 99% rename from api/core/plugin/manager/model.py rename to api/core/plugin/impl/model.py index 5ebc0c2320..f7607eef8d 100644 --- a/api/core/plugin/manager/model.py +++ b/api/core/plugin/impl/model.py @@ -18,10 +18,10 @@ from core.plugin.entities.plugin_daemon import ( PluginTextEmbeddingNumTokensResponse, PluginVoicesResponse, ) -from core.plugin.manager.base import BasePluginManager +from core.plugin.impl.base import BasePluginClient -class PluginModelManager(BasePluginManager): +class PluginModelClient(BasePluginClient): def fetch_model_providers(self, tenant_id: str) -> Sequence[PluginModelProviderEntity]: """ Fetch model providers for the given tenant. diff --git a/api/core/plugin/impl/oauth.py b/api/core/plugin/impl/oauth.py new file mode 100644 index 0000000000..91774984c8 --- /dev/null +++ b/api/core/plugin/impl/oauth.py @@ -0,0 +1,98 @@ +from collections.abc import Mapping +from typing import Any + +from werkzeug import Request + +from core.plugin.entities.plugin_daemon import PluginOAuthAuthorizationUrlResponse, PluginOAuthCredentialsResponse +from core.plugin.impl.base import BasePluginClient + + +class OAuthHandler(BasePluginClient): + def get_authorization_url( + self, + tenant_id: str, + user_id: str, + plugin_id: str, + provider: str, + system_credentials: Mapping[str, Any], + ) -> PluginOAuthAuthorizationUrlResponse: + return self._request_with_plugin_daemon_response( + "POST", + f"plugin/{tenant_id}/dispatch/oauth/get_authorization_url", + PluginOAuthAuthorizationUrlResponse, + data={ + "user_id": user_id, + "data": { + "provider": provider, + "system_credentials": system_credentials, + }, + }, + headers={ + "X-Plugin-ID": plugin_id, + "Content-Type": "application/json", + }, + ) + + def get_credentials( + self, + tenant_id: str, + user_id: str, + plugin_id: str, + provider: str, + system_credentials: Mapping[str, Any], + request: Request, + ) -> PluginOAuthCredentialsResponse: + """ + Get credentials from the given request. + """ + + # encode request to raw http request + raw_request_bytes = self._convert_request_to_raw_data(request) + + return self._request_with_plugin_daemon_response( + "POST", + f"plugin/{tenant_id}/dispatch/oauth/get_credentials", + PluginOAuthCredentialsResponse, + data={ + "user_id": user_id, + "data": { + "provider": provider, + "system_credentials": system_credentials, + "raw_request_bytes": raw_request_bytes, + }, + }, + headers={ + "X-Plugin-ID": plugin_id, + "Content-Type": "application/json", + }, + ) + + def _convert_request_to_raw_data(self, request: Request) -> bytes: + """ + Convert a Request object to raw HTTP data. + + Args: + request: The Request object to convert. + + Returns: + The raw HTTP data as bytes. + """ + # Start with the request line + method = request.method + path = request.path + protocol = request.headers.get("HTTP_VERSION", "HTTP/1.1") + raw_data = f"{method} {path} {protocol}\r\n".encode() + + # Add headers + for header_name, header_value in request.headers.items(): + raw_data += f"{header_name}: {header_value}\r\n".encode() + + # Add empty line to separate headers from body + raw_data += b"\r\n" + + # Add body if exists + body = request.get_data(as_text=False) + if body: + raw_data += body + + return raw_data diff --git a/api/core/plugin/manager/plugin.py b/api/core/plugin/impl/plugin.py similarity index 92% rename from api/core/plugin/manager/plugin.py rename to api/core/plugin/impl/plugin.py index 15dcd6cb34..1cd2dc1be7 100644 --- a/api/core/plugin/manager/plugin.py +++ b/api/core/plugin/impl/plugin.py @@ -9,11 +9,16 @@ from core.plugin.entities.plugin import ( PluginInstallation, PluginInstallationSource, ) -from core.plugin.entities.plugin_daemon import PluginInstallTask, PluginInstallTaskStartResponse, PluginUploadResponse -from core.plugin.manager.base import BasePluginManager +from core.plugin.entities.plugin_daemon import ( + PluginInstallTask, + PluginInstallTaskStartResponse, + PluginListResponse, + PluginUploadResponse, +) +from core.plugin.impl.base import BasePluginClient -class PluginInstallationManager(BasePluginManager): +class PluginInstaller(BasePluginClient): def fetch_plugin_by_identifier( self, tenant_id: str, @@ -27,12 +32,21 @@ class PluginInstallationManager(BasePluginManager): ) def list_plugins(self, tenant_id: str) -> list[PluginEntity]: - return self._request_with_plugin_daemon_response( + result = self._request_with_plugin_daemon_response( "GET", f"plugin/{tenant_id}/management/list", - list[PluginEntity], + PluginListResponse, params={"page": 1, "page_size": 256}, ) + return result.list + + def list_plugins_with_total(self, tenant_id: str, page: int, page_size: int) -> PluginListResponse: + return self._request_with_plugin_daemon_response( + "GET", + f"plugin/{tenant_id}/management/list", + PluginListResponse, + params={"page": page, "page_size": page_size}, + ) def upload_pkg( self, diff --git a/api/core/plugin/manager/tool.py b/api/core/plugin/impl/tool.py similarity index 98% rename from api/core/plugin/manager/tool.py rename to api/core/plugin/impl/tool.py index 7592f867e1..19b26c8fe3 100644 --- a/api/core/plugin/manager/tool.py +++ b/api/core/plugin/impl/tool.py @@ -5,11 +5,11 @@ from pydantic import BaseModel from core.plugin.entities.plugin import GenericProviderID, ToolProviderID from core.plugin.entities.plugin_daemon import PluginBasicBooleanResponse, PluginToolProviderEntity -from core.plugin.manager.base import BasePluginManager +from core.plugin.impl.base import BasePluginClient from core.tools.entities.tool_entities import ToolInvokeMessage, ToolParameter -class PluginToolManager(BasePluginManager): +class PluginToolManager(BasePluginClient): def fetch_tool_providers(self, tenant_id: str) -> list[PluginToolProviderEntity]: """ Fetch tool providers for the given tenant. diff --git a/api/core/prompt/prompt_templates/baichuan_chat.json b/api/core/prompt/prompt_templates/baichuan_chat.json index 03b6a53cff..b3f7cdaa18 100644 --- a/api/core/prompt/prompt_templates/baichuan_chat.json +++ b/api/core/prompt/prompt_templates/baichuan_chat.json @@ -10,4 +10,4 @@ ], "query_prompt": "\n\n用户:{{#query#}}", "stops": ["用户:"] -} \ No newline at end of file +} diff --git a/api/core/prompt/prompt_templates/baichuan_completion.json b/api/core/prompt/prompt_templates/baichuan_completion.json index ae8c0dac53..cee9ea47cd 100644 --- a/api/core/prompt/prompt_templates/baichuan_completion.json +++ b/api/core/prompt/prompt_templates/baichuan_completion.json @@ -6,4 +6,4 @@ ], "query_prompt": "{{#query#}}", "stops": null -} \ No newline at end of file +} diff --git a/api/core/prompt/prompt_templates/common_completion.json b/api/core/prompt/prompt_templates/common_completion.json index c148772010..706a8140d1 100644 --- a/api/core/prompt/prompt_templates/common_completion.json +++ b/api/core/prompt/prompt_templates/common_completion.json @@ -6,4 +6,4 @@ ], "query_prompt": "{{#query#}}", "stops": null -} \ No newline at end of file +} diff --git a/api/core/provider_manager.py b/api/core/provider_manager.py index 7570200175..488a394679 100644 --- a/api/core/provider_manager.py +++ b/api/core/provider_manager.py @@ -3,7 +3,9 @@ from collections import defaultdict from json import JSONDecodeError from typing import Any, Optional, cast +from sqlalchemy import select from sqlalchemy.exc import IntegrityError +from sqlalchemy.orm import Session from configs import dify_config from core.entities.model_entities import DefaultModelEntity, DefaultModelProviderEntity @@ -393,19 +395,13 @@ class ProviderManager: @staticmethod def _get_all_providers(tenant_id: str) -> dict[str, list[Provider]]: - """ - Get all provider records of the workspace. - - :param tenant_id: workspace id - :return: - """ - providers = db.session.query(Provider).filter(Provider.tenant_id == tenant_id, Provider.is_valid == True).all() - provider_name_to_provider_records_dict = defaultdict(list) - for provider in providers: - # TODO: Use provider name with prefix after the data migration - provider_name_to_provider_records_dict[str(ModelProviderID(provider.provider_name))].append(provider) - + with Session(db.engine, expire_on_commit=False) as session: + stmt = select(Provider).where(Provider.tenant_id == tenant_id, Provider.is_valid == True) + providers = session.scalars(stmt) + for provider in providers: + # Use provider name with prefix after the data migration + provider_name_to_provider_records_dict[str(ModelProviderID(provider.provider_name))].append(provider) return provider_name_to_provider_records_dict @staticmethod @@ -416,17 +412,12 @@ class ProviderManager: :param tenant_id: workspace id :return: """ - # Get all provider model records of the workspace - provider_models = ( - db.session.query(ProviderModel) - .filter(ProviderModel.tenant_id == tenant_id, ProviderModel.is_valid == True) - .all() - ) - provider_name_to_provider_model_records_dict = defaultdict(list) - for provider_model in provider_models: - provider_name_to_provider_model_records_dict[provider_model.provider_name].append(provider_model) - + with Session(db.engine, expire_on_commit=False) as session: + stmt = select(ProviderModel).where(ProviderModel.tenant_id == tenant_id, ProviderModel.is_valid == True) + provider_models = session.scalars(stmt) + for provider_model in provider_models: + provider_name_to_provider_model_records_dict[provider_model.provider_name].append(provider_model) return provider_name_to_provider_model_records_dict @staticmethod @@ -437,17 +428,14 @@ class ProviderManager: :param tenant_id: workspace id :return: """ - preferred_provider_types = ( - db.session.query(TenantPreferredModelProvider) - .filter(TenantPreferredModelProvider.tenant_id == tenant_id) - .all() - ) - - provider_name_to_preferred_provider_type_records_dict = { - preferred_provider_type.provider_name: preferred_provider_type - for preferred_provider_type in preferred_provider_types - } - + provider_name_to_preferred_provider_type_records_dict = {} + with Session(db.engine, expire_on_commit=False) as session: + stmt = select(TenantPreferredModelProvider).where(TenantPreferredModelProvider.tenant_id == tenant_id) + preferred_provider_types = session.scalars(stmt) + provider_name_to_preferred_provider_type_records_dict = { + preferred_provider_type.provider_name: preferred_provider_type + for preferred_provider_type in preferred_provider_types + } return provider_name_to_preferred_provider_type_records_dict @staticmethod @@ -458,18 +446,14 @@ class ProviderManager: :param tenant_id: workspace id :return: """ - provider_model_settings = ( - db.session.query(ProviderModelSetting).filter(ProviderModelSetting.tenant_id == tenant_id).all() - ) - provider_name_to_provider_model_settings_dict = defaultdict(list) - for provider_model_setting in provider_model_settings: - ( + with Session(db.engine, expire_on_commit=False) as session: + stmt = select(ProviderModelSetting).where(ProviderModelSetting.tenant_id == tenant_id) + provider_model_settings = session.scalars(stmt) + for provider_model_setting in provider_model_settings: provider_name_to_provider_model_settings_dict[provider_model_setting.provider_name].append( provider_model_setting ) - ) - return provider_name_to_provider_model_settings_dict @staticmethod @@ -492,15 +476,14 @@ class ProviderManager: if not model_load_balancing_enabled: return {} - provider_load_balancing_configs = ( - db.session.query(LoadBalancingModelConfig).filter(LoadBalancingModelConfig.tenant_id == tenant_id).all() - ) - provider_name_to_provider_load_balancing_model_configs_dict = defaultdict(list) - for provider_load_balancing_config in provider_load_balancing_configs: - provider_name_to_provider_load_balancing_model_configs_dict[ - provider_load_balancing_config.provider_name - ].append(provider_load_balancing_config) + with Session(db.engine, expire_on_commit=False) as session: + stmt = select(LoadBalancingModelConfig).where(LoadBalancingModelConfig.tenant_id == tenant_id) + provider_load_balancing_configs = session.scalars(stmt) + for provider_load_balancing_config in provider_load_balancing_configs: + provider_name_to_provider_load_balancing_model_configs_dict[ + provider_load_balancing_config.provider_name + ].append(provider_load_balancing_config) return provider_name_to_provider_load_balancing_model_configs_dict @@ -626,10 +609,9 @@ class ProviderManager: if not cached_provider_credentials: try: # fix origin data - if ( - custom_provider_record.encrypted_config - and not custom_provider_record.encrypted_config.startswith("{") - ): + if custom_provider_record.encrypted_config is None: + raise ValueError("No credentials found") + if not custom_provider_record.encrypted_config.startswith("{"): provider_credentials = {"openai_api_key": custom_provider_record.encrypted_config} else: provider_credentials = json.loads(custom_provider_record.encrypted_config) @@ -733,7 +715,7 @@ class ProviderManager: return SystemConfiguration(enabled=False) # Convert provider_records to dict - quota_type_to_provider_records_dict = {} + quota_type_to_provider_records_dict: dict[ProviderQuotaType, Provider] = {} for provider_record in provider_records: if provider_record.provider_type != ProviderType.SYSTEM.value: continue @@ -758,6 +740,11 @@ class ProviderManager: else: provider_record = quota_type_to_provider_records_dict[provider_quota.quota_type] + if provider_record.quota_used is None: + raise ValueError("quota_used is None") + if provider_record.quota_limit is None: + raise ValueError("quota_limit is None") + quota_configuration = QuotaConfiguration( quota_type=provider_quota.quota_type, quota_unit=provider_hosting_configuration.quota_unit or QuotaUnit.TOKENS, @@ -791,10 +778,9 @@ class ProviderManager: cached_provider_credentials = provider_credentials_cache.get() if not cached_provider_credentials: - try: - provider_credentials: dict[str, Any] = json.loads(provider_record.encrypted_config) - except JSONDecodeError: - provider_credentials = {} + provider_credentials: dict[str, Any] = {} + if provider_records and provider_records[0].encrypted_config: + provider_credentials = json.loads(provider_records[0].encrypted_config) # Get provider credential secret variables provider_credential_secret_variables = self._extract_secret_variables( diff --git a/api/core/rag/datasource/keyword/jieba/stopwords.py b/api/core/rag/datasource/keyword/jieba/stopwords.py index 9abe78d6ef..54b65d9a2d 100644 --- a/api/core/rag/datasource/keyword/jieba/stopwords.py +++ b/api/core/rag/datasource/keyword/jieba/stopwords.py @@ -720,7 +720,7 @@ STOPWORDS = { "〉", "〈", "…", - " ", + " ", "0", "1", "2", @@ -731,16 +731,6 @@ STOPWORDS = { "7", "8", "9", - "0", - "1", - "2", - "3", - "4", - "5", - "6", - "7", - "8", - "9", "二", "三", "四", diff --git a/api/core/rag/datasource/retrieval_service.py b/api/core/rag/datasource/retrieval_service.py index 46a5330bdb..2c5178241c 100644 --- a/api/core/rag/datasource/retrieval_service.py +++ b/api/core/rag/datasource/retrieval_service.py @@ -10,6 +10,7 @@ from core.rag.data_post_processor.data_post_processor import DataPostProcessor from core.rag.datasource.keyword.keyword_factory import Keyword from core.rag.datasource.vdb.vector_factory import Vector from core.rag.embedding.retrieval import RetrievalSegments +from core.rag.entities.metadata_entities import MetadataCondition from core.rag.index_processor.constant.index_type import IndexType from core.rag.models.document import Document from core.rag.rerank.rerank_type import RerankMode @@ -119,12 +120,25 @@ class RetrievalService: return all_documents @classmethod - def external_retrieve(cls, dataset_id: str, query: str, external_retrieval_model: Optional[dict] = None): + def external_retrieve( + cls, + dataset_id: str, + query: str, + external_retrieval_model: Optional[dict] = None, + metadata_filtering_conditions: Optional[dict] = None, + ): dataset = db.session.query(Dataset).filter(Dataset.id == dataset_id).first() if not dataset: return [] + metadata_condition = ( + MetadataCondition(**metadata_filtering_conditions) if metadata_filtering_conditions else None + ) all_documents = ExternalDatasetService.fetch_external_knowledge_retrieval( - dataset.tenant_id, dataset_id, query, external_retrieval_model or {} + dataset.tenant_id, + dataset_id, + query, + external_retrieval_model or {}, + metadata_condition=metadata_condition, ) return all_documents @@ -391,7 +405,29 @@ class RetrievalService: record["child_chunks"] = segment_child_map[record["segment"].id].get("child_chunks") # type: ignore record["score"] = segment_child_map[record["segment"].id]["max_score"] - return [RetrievalSegments(**record) for record in records] + result = [] + for record in records: + # Extract segment + segment = record["segment"] + + # Extract child_chunks, ensuring it's a list or None + child_chunks = record.get("child_chunks") + if not isinstance(child_chunks, list): + child_chunks = None + + # Extract score, ensuring it's a float or None + score_value = record.get("score") + score = ( + float(score_value) + if score_value is not None and isinstance(score_value, int | float | str) + else None + ) + + # Create RetrievalSegments object + retrieval_segment = RetrievalSegments(segment=segment, child_chunks=child_chunks, score=score) + result.append(retrieval_segment) + + return result except Exception as e: db.session.rollback() raise e diff --git a/api/core/rag/datasource/vdb/analyticdb/analyticdb_vector_sql.py b/api/core/rag/datasource/vdb/analyticdb/analyticdb_vector_sql.py index c1792943bb..14481b1f10 100644 --- a/api/core/rag/datasource/vdb/analyticdb/analyticdb_vector_sql.py +++ b/api/core/rag/datasource/vdb/analyticdb/analyticdb_vector_sql.py @@ -156,8 +156,8 @@ class AnalyticdbVectorBySql: values = [] id_prefix = str(uuid.uuid4()) + "_" sql = f""" - INSERT INTO {self.table_name} - (id, ref_doc_id, vector, page_content, metadata_, to_tsvector) + INSERT INTO {self.table_name} + (id, ref_doc_id, vector, page_content, metadata_, to_tsvector) VALUES (%s, %s, %s, %s, %s, to_tsvector('zh_cn', %s)); """ for i, doc in enumerate(documents): @@ -242,7 +242,7 @@ class AnalyticdbVectorBySql: where_clause += f"AND metadata_->>'document_id' IN ({document_ids})" with self._get_cursor() as cur: cur.execute( - f"""SELECT id, vector, page_content, metadata_, + f"""SELECT id, vector, page_content, metadata_, ts_rank(to_tsvector, to_tsquery_from_text(%s, 'zh_cn'), 32) AS score FROM {self.table_name} WHERE to_tsvector@@to_tsquery_from_text(%s, 'zh_cn') {where_clause} diff --git a/api/core/rag/datasource/vdb/baidu/baidu_vector.py b/api/core/rag/datasource/vdb/baidu/baidu_vector.py index 86f1f5bfe4..db7ffc9c4f 100644 --- a/api/core/rag/datasource/vdb/baidu/baidu_vector.py +++ b/api/core/rag/datasource/vdb/baidu/baidu_vector.py @@ -85,7 +85,6 @@ class BaiduVector(BaseVector): end = min(start + batch_size, total_count) rows = [] assert len(metadatas) == total_count, "metadatas length should be equal to total_count" - # FIXME do you need this assert? for i in range(start, end, 1): row = Row( id=metadatas[i].get("doc_id", str(uuid.uuid4())), diff --git a/api/core/rag/datasource/vdb/elasticsearch/elasticsearch_vector.py b/api/core/rag/datasource/vdb/elasticsearch/elasticsearch_vector.py index 033d05a077..44cc5d3e98 100644 --- a/api/core/rag/datasource/vdb/elasticsearch/elasticsearch_vector.py +++ b/api/core/rag/datasource/vdb/elasticsearch/elasticsearch_vector.py @@ -142,7 +142,7 @@ class ElasticSearchVector(BaseVector): if score > score_threshold: if doc.metadata is not None: doc.metadata["score"] = score - docs.append(doc) + docs.append(doc) return docs diff --git a/api/core/rag/datasource/vdb/lindorm/lindorm_vector.py b/api/core/rag/datasource/vdb/lindorm/lindorm_vector.py index 643ac2df4e..e9ff1ce43d 100644 --- a/api/core/rag/datasource/vdb/lindorm/lindorm_vector.py +++ b/api/core/rag/datasource/vdb/lindorm/lindorm_vector.py @@ -32,6 +32,7 @@ class LindormVectorStoreConfig(BaseModel): username: Optional[str] = None password: Optional[str] = None using_ugc: Optional[bool] = False + request_timeout: Optional[float] = 1.0 # timeout units: s @model_validator(mode="before") @classmethod @@ -251,9 +252,9 @@ class LindormVectorStore(BaseVector): query = default_vector_search_query(query_vector=query_vector, k=top_k, filters=filters, **kwargs) try: - params = {} + params = {"timeout": self._client_config.request_timeout} if self._using_ugc: - params["routing"] = self._routing + params["routing"] = self._routing # type: ignore response = self._client.search(index=self._collection_name, body=query, params=params) except Exception: logger.exception(f"Error executing vector search, query: {query}") @@ -304,8 +305,8 @@ class LindormVectorStore(BaseVector): routing=routing, routing_field=self._routing_field, ) - - response = self._client.search(index=self._collection_name, body=full_text_query) + params = {"timeout": self._client_config.request_timeout} + response = self._client.search(index=self._collection_name, body=full_text_query, params=params) docs = [] for hit in response["hits"]["hits"]: docs.append( @@ -554,6 +555,7 @@ class LindormVectorStoreFactory(AbstractVectorFactory): username=dify_config.LINDORM_USERNAME, password=dify_config.LINDORM_PASSWORD, using_ugc=dify_config.USING_UGC_INDEX, + request_timeout=dify_config.LINDORM_QUERY_TIMEOUT, ) using_ugc = dify_config.USING_UGC_INDEX if using_ugc is None: diff --git a/api/core/rag/datasource/vdb/milvus/milvus_vector.py b/api/core/rag/datasource/vdb/milvus/milvus_vector.py index 100bcb198c..63de6a0603 100644 --- a/api/core/rag/datasource/vdb/milvus/milvus_vector.py +++ b/api/core/rag/datasource/vdb/milvus/milvus_vector.py @@ -27,8 +27,8 @@ class MilvusConfig(BaseModel): uri: str # Milvus server URI token: Optional[str] = None # Optional token for authentication - user: str # Username for authentication - password: str # Password for authentication + user: Optional[str] = None # Username for authentication + password: Optional[str] = None # Password for authentication batch_size: int = 100 # Batch size for operations database: str = "default" # Database name enable_hybrid_search: bool = False # Flag to enable hybrid search @@ -43,10 +43,11 @@ class MilvusConfig(BaseModel): """ if not values.get("uri"): raise ValueError("config MILVUS_URI is required") - if not values.get("user"): - raise ValueError("config MILVUS_USER is required") - if not values.get("password"): - raise ValueError("config MILVUS_PASSWORD is required") + if not values.get("token"): + if not values.get("user"): + raise ValueError("config MILVUS_USER is required") + if not values.get("password"): + raise ValueError("config MILVUS_PASSWORD is required") return values def to_milvus_params(self): @@ -96,6 +97,10 @@ class MilvusVector(BaseVector): try: milvus_version = self._client.get_server_version() + # Check if it's Zilliz Cloud - it supports full-text search with Milvus 2.5 compatibility + if "Zilliz Cloud" in milvus_version: + return True + # For standard Milvus installations, check version number return version.parse(milvus_version).base_version >= version.parse("2.5.0").base_version except Exception as e: logger.warning(f"Failed to check Milvus version: {str(e)}. Disabling hybrid search.") @@ -356,11 +361,14 @@ class MilvusVector(BaseVector): ) redis_client.set(collection_exist_cache_key, 1, ex=3600) - def _init_client(self, config) -> MilvusClient: + def _init_client(self, config: MilvusConfig) -> MilvusClient: """ Initialize and return a Milvus client. """ - client = MilvusClient(uri=config.uri, user=config.user, password=config.password, db_name=config.database) + if config.token: + client = MilvusClient(uri=config.uri, token=config.token, db_name=config.database) + else: + client = MilvusClient(uri=config.uri, user=config.user, password=config.password, db_name=config.database) return client diff --git a/api/core/rag/datasource/vdb/oceanbase/oceanbase_vector.py b/api/core/rag/datasource/vdb/oceanbase/oceanbase_vector.py index ae6b0c51ab..dd196e1f09 100644 --- a/api/core/rag/datasource/vdb/oceanbase/oceanbase_vector.py +++ b/api/core/rag/datasource/vdb/oceanbase/oceanbase_vector.py @@ -80,6 +80,23 @@ class OceanBaseVector(BaseVector): self.delete() + vals = [] + params = self._client.perform_raw_text_sql("SHOW PARAMETERS LIKE '%ob_vector_memory_limit_percentage%'") + for row in params: + val = int(row[6]) + vals.append(val) + if len(vals) == 0: + raise ValueError("ob_vector_memory_limit_percentage not found in parameters.") + if any(val == 0 for val in vals): + try: + self._client.perform_raw_text_sql("ALTER SYSTEM SET ob_vector_memory_limit_percentage = 30") + except Exception as e: + raise Exception( + "Failed to set ob_vector_memory_limit_percentage. " + + "Maybe the database user has insufficient privilege.", + e, + ) + cols = [ Column("id", String(36), primary_key=True, autoincrement=False), Column("vector", VECTOR(self._vec_dim)), @@ -110,22 +127,6 @@ class OceanBaseVector(BaseVector): + "to support fulltext index and vector index in the same table", e, ) - vals = [] - params = self._client.perform_raw_text_sql("SHOW PARAMETERS LIKE '%ob_vector_memory_limit_percentage%'") - for row in params: - val = int(row[6]) - vals.append(val) - if len(vals) == 0: - raise ValueError("ob_vector_memory_limit_percentage not found in parameters.") - if any(val == 0 for val in vals): - try: - self._client.perform_raw_text_sql("ALTER SYSTEM SET ob_vector_memory_limit_percentage = 30") - except Exception as e: - raise Exception( - "Failed to set ob_vector_memory_limit_percentage. " - + "Maybe the database user has insufficient privilege.", - e, - ) redis_client.set(collection_exist_cache_key, 1, ex=3600) def _check_hybrid_search_support(self) -> bool: @@ -203,7 +204,7 @@ class OceanBaseVector(BaseVector): full_sql = f"""SELECT metadata, text, MATCH (text) AGAINST (:query) AS score FROM {self._collection_name} - WHERE MATCH (text) AGAINST (:query) > 0 + WHERE MATCH (text) AGAINST (:query) > 0 {where_clause} ORDER BY score DESC LIMIT {top_k}""" diff --git a/api/core/rag/datasource/vdb/opengauss/opengauss.py b/api/core/rag/datasource/vdb/opengauss/opengauss.py index dae908f67d..2548881b9c 100644 --- a/api/core/rag/datasource/vdb/opengauss/opengauss.py +++ b/api/core/rag/datasource/vdb/opengauss/opengauss.py @@ -59,12 +59,12 @@ CREATE TABLE IF NOT EXISTS {table_name} ( """ SQL_CREATE_INDEX_PQ = """ -CREATE INDEX IF NOT EXISTS embedding_{table_name}_pq_idx ON {table_name} +CREATE INDEX IF NOT EXISTS embedding_{table_name}_pq_idx ON {table_name} USING hnsw (embedding vector_cosine_ops) WITH (m = 16, ef_construction = 64, enable_pq=on, pq_m={pq_m}); """ SQL_CREATE_INDEX = """ -CREATE INDEX IF NOT EXISTS embedding_cosine_{table_name}_idx ON {table_name} +CREATE INDEX IF NOT EXISTS embedding_cosine_{table_name}_idx ON {table_name} USING hnsw (embedding vector_cosine_ops) WITH (m = 16, ef_construction = 64); """ diff --git a/api/core/rag/datasource/vdb/opensearch/opensearch_vector.py b/api/core/rag/datasource/vdb/opensearch/opensearch_vector.py index 6636646cff..0abb3c0077 100644 --- a/api/core/rag/datasource/vdb/opensearch/opensearch_vector.py +++ b/api/core/rag/datasource/vdb/opensearch/opensearch_vector.py @@ -1,10 +1,9 @@ import json import logging -import ssl -from typing import Any, Optional +from typing import Any, Literal, Optional from uuid import uuid4 -from opensearchpy import OpenSearch, helpers +from opensearchpy import OpenSearch, Urllib3AWSV4SignerAuth, Urllib3HttpConnection, helpers from opensearchpy.helpers import BulkIndexError from pydantic import BaseModel, model_validator @@ -24,9 +23,13 @@ logger = logging.getLogger(__name__) class OpenSearchConfig(BaseModel): host: str port: int + secure: bool = False # use_ssl + verify_certs: bool = True + auth_method: Literal["basic", "aws_managed_iam"] = "basic" user: Optional[str] = None password: Optional[str] = None - secure: bool = False + aws_region: Optional[str] = None + aws_service: Optional[str] = None @model_validator(mode="before") @classmethod @@ -35,24 +38,42 @@ class OpenSearchConfig(BaseModel): raise ValueError("config OPENSEARCH_HOST is required") if not values.get("port"): raise ValueError("config OPENSEARCH_PORT is required") + if values.get("auth_method") == "aws_managed_iam": + if not values.get("aws_region"): + raise ValueError("config OPENSEARCH_AWS_REGION is required for AWS_MANAGED_IAM auth method") + if not values.get("aws_service"): + raise ValueError("config OPENSEARCH_AWS_SERVICE is required for AWS_MANAGED_IAM auth method") + if not values.get("OPENSEARCH_SECURE") and values.get("OPENSEARCH_VERIFY_CERTS"): + raise ValueError("verify_certs=True requires secure (HTTPS) connection") return values - def create_ssl_context(self) -> ssl.SSLContext: - ssl_context = ssl.create_default_context() - ssl_context.check_hostname = False - ssl_context.verify_mode = ssl.CERT_NONE # Disable Certificate Validation - return ssl_context + def create_aws_managed_iam_auth(self) -> Urllib3AWSV4SignerAuth: + import boto3 # type: ignore + + return Urllib3AWSV4SignerAuth( + credentials=boto3.Session().get_credentials(), + region=self.aws_region, + service=self.aws_service, # type: ignore[arg-type] + ) def to_opensearch_params(self) -> dict[str, Any]: params = { "hosts": [{"host": self.host, "port": self.port}], "use_ssl": self.secure, - "verify_certs": self.secure, + "verify_certs": self.verify_certs, + "connection_class": Urllib3HttpConnection, + "pool_maxsize": 20, } - if self.user and self.password: + + if self.auth_method == "basic": + logger.info("Using basic authentication for OpenSearch Vector DB") + params["http_auth"] = (self.user, self.password) - if self.secure: - params["ssl_context"] = self.create_ssl_context() + elif self.auth_method == "aws_managed_iam": + logger.info("Using AWS managed IAM role for OpenSearch Vector DB") + + params["http_auth"] = self.create_aws_managed_iam_auth() + return params @@ -76,16 +97,23 @@ class OpenSearchVector(BaseVector): action = { "_op_type": "index", "_index": self._collection_name.lower(), - "_id": uuid4().hex, "_source": { Field.CONTENT_KEY.value: documents[i].page_content, Field.VECTOR.value: embeddings[i], # Make sure you pass an array here Field.METADATA_KEY.value: documents[i].metadata, }, } + # See https://github.com/langchain-ai/langchainjs/issues/4346#issuecomment-1935123377 + if self._client_config.aws_service not in ["aoss"]: + action["_id"] = uuid4().hex actions.append(action) - helpers.bulk(self._client, actions) + helpers.bulk( + client=self._client, + actions=actions, + timeout=30, + max_retries=3, + ) def get_ids_by_metadata_field(self, key: str, value: str): query = {"query": {"term": {f"{Field.METADATA_KEY.value}.{key}": value}}} @@ -156,7 +184,16 @@ class OpenSearchVector(BaseVector): } document_ids_filter = kwargs.get("document_ids_filter") if document_ids_filter: - query["query"] = {"terms": {"metadata.document_id": document_ids_filter}} + query["query"] = { + "script_score": { + "query": {"bool": {"filter": [{"terms": {Field.DOCUMENT_ID.value: document_ids_filter}}]}}, + "script": { + "source": "knn_score", + "lang": "knn", + "params": {"field": Field.VECTOR.value, "query_value": query_vector, "space_type": "l2"}, + }, + } + } try: response = self._client.search(index=self._collection_name.lower(), body=query) @@ -181,10 +218,10 @@ class OpenSearchVector(BaseVector): return docs def search_by_full_text(self, query: str, **kwargs: Any) -> list[Document]: - full_text_query = {"query": {"match": {Field.CONTENT_KEY.value: query}}} + full_text_query = {"query": {"bool": {"must": [{"match": {Field.CONTENT_KEY.value: query}}]}}} document_ids_filter = kwargs.get("document_ids_filter") if document_ids_filter: - full_text_query["query"]["terms"] = {"metadata.document_id": document_ids_filter} + full_text_query["query"]["bool"]["filter"] = [{"terms": {"metadata.document_id": document_ids_filter}}] response = self._client.search(index=self._collection_name.lower(), body=full_text_query) @@ -227,13 +264,15 @@ class OpenSearchVector(BaseVector): Field.METADATA_KEY.value: { "type": "object", "properties": { - "doc_id": {"type": "keyword"} # Map doc_id to keyword type + "doc_id": {"type": "keyword"}, # Map doc_id to keyword type + "document_id": {"type": "keyword"}, }, }, } }, } + logger.info(f"Creating OpenSearch index {self._collection_name.lower()}") self._client.indices.create(index=self._collection_name.lower(), body=index_body) redis_client.set(collection_exist_cache_key, 1, ex=3600) @@ -252,9 +291,13 @@ class OpenSearchVectorFactory(AbstractVectorFactory): open_search_config = OpenSearchConfig( host=dify_config.OPENSEARCH_HOST or "localhost", port=dify_config.OPENSEARCH_PORT, + secure=dify_config.OPENSEARCH_SECURE, + verify_certs=dify_config.OPENSEARCH_VERIFY_CERTS, + auth_method=dify_config.OPENSEARCH_AUTH_METHOD.value, user=dify_config.OPENSEARCH_USER, password=dify_config.OPENSEARCH_PASSWORD, - secure=dify_config.OPENSEARCH_SECURE, + aws_region=dify_config.OPENSEARCH_AWS_REGION, + aws_service=dify_config.OPENSEARCH_AWS_SERVICE, ) return OpenSearchVector(collection_name=collection_name, config=open_search_config) diff --git a/api/core/rag/datasource/vdb/oracle/oraclevector.py b/api/core/rag/datasource/vdb/oracle/oraclevector.py index 63695e6f3f..d1c8142b3d 100644 --- a/api/core/rag/datasource/vdb/oracle/oraclevector.py +++ b/api/core/rag/datasource/vdb/oracle/oraclevector.py @@ -59,8 +59,8 @@ CREATE TABLE IF NOT EXISTS {table_name} ( ) """ SQL_CREATE_INDEX = """ -CREATE INDEX IF NOT EXISTS idx_docs_{table_name} ON {table_name}(text) -INDEXTYPE IS CTXSYS.CONTEXT PARAMETERS +CREATE INDEX IF NOT EXISTS idx_docs_{table_name} ON {table_name}(text) +INDEXTYPE IS CTXSYS.CONTEXT PARAMETERS ('FILTER CTXSYS.NULL_FILTER SECTION GROUP CTXSYS.HTML_SECTION_GROUP LEXER world_lexer') """ @@ -164,7 +164,7 @@ class OracleVector(BaseVector): with conn.cursor() as cur: try: cur.execute( - f"""INSERT INTO {self.table_name} (id, text, meta, embedding) + f"""INSERT INTO {self.table_name} (id, text, meta, embedding) VALUES (:1, :2, :3, :4)""", value, ) @@ -227,8 +227,8 @@ class OracleVector(BaseVector): conn.outputtypehandler = self.output_type_handler with conn.cursor() as cur: cur.execute( - f"""SELECT meta, text, vector_distance(embedding,(select to_vector(:1) from dual),cosine) - AS distance FROM {self.table_name} + f"""SELECT meta, text, vector_distance(embedding,(select to_vector(:1) from dual),cosine) + AS distance FROM {self.table_name} {where_clause} ORDER BY distance fetch first {top_k} rows only""", [numpy.array(query_vector)], ) @@ -261,7 +261,7 @@ class OracleVector(BaseVector): words = pseg.cut(query) current_entity = "" for word, pos in words: - if pos in {"nr", "Ng", "eng", "nz", "n", "ORG", "v"}: # nr: 人名, ns: 地名, nt: 机构名 + if pos in {"nr", "Ng", "eng", "nz", "n", "ORG", "v"}: # nr: 人名,ns: 地名,nt: 机构名 current_entity += word else: if current_entity: @@ -290,7 +290,7 @@ class OracleVector(BaseVector): document_ids = ", ".join(f"'{id}'" for id in document_ids_filter) where_clause = f" AND metadata->>'document_id' in ({document_ids}) " cur.execute( - f"""select meta, text, embedding FROM {self.table_name} + f"""select meta, text, embedding FROM {self.table_name} WHERE CONTAINS(text, :kk, 1) > 0 {where_clause} order by score(1) desc fetch first {top_k} rows only""", kk=" ACCUM ".join(entities), @@ -303,7 +303,6 @@ class OracleVector(BaseVector): return docs else: return [Document(page_content="", metadata={})] - return [] def delete(self) -> None: with self._get_connection() as conn: diff --git a/api/core/rag/datasource/vdb/pgvector/pgvector.py b/api/core/rag/datasource/vdb/pgvector/pgvector.py index eab51ab01d..04e9cf801e 100644 --- a/api/core/rag/datasource/vdb/pgvector/pgvector.py +++ b/api/core/rag/datasource/vdb/pgvector/pgvector.py @@ -1,3 +1,4 @@ +import hashlib import json import logging import uuid @@ -61,12 +62,12 @@ CREATE TABLE IF NOT EXISTS {table_name} ( """ SQL_CREATE_INDEX = """ -CREATE INDEX IF NOT EXISTS embedding_cosine_v1_idx ON {table_name} +CREATE INDEX IF NOT EXISTS embedding_cosine_v1_idx_{index_hash} ON {table_name} USING hnsw (embedding vector_cosine_ops) WITH (m = 16, ef_construction = 64); """ SQL_CREATE_INDEX_PG_BIGM = """ -CREATE INDEX IF NOT EXISTS bigm_idx ON {table_name} +CREATE INDEX IF NOT EXISTS bigm_idx_{index_hash} ON {table_name} USING gin (text gin_bigm_ops); """ @@ -76,6 +77,7 @@ class PGVector(BaseVector): super().__init__(collection_name) self.pool = self._create_connection_pool(config) self.table_name = f"embedding_{collection_name}" + self.index_hash = hashlib.md5(self.table_name.encode()).hexdigest()[:8] self.pg_bigm = config.pg_bigm def get_type(self) -> str: @@ -256,10 +258,9 @@ class PGVector(BaseVector): # PG hnsw index only support 2000 dimension or less # ref: https://github.com/pgvector/pgvector?tab=readme-ov-file#indexing if dimension <= 2000: - cur.execute(SQL_CREATE_INDEX.format(table_name=self.table_name)) + cur.execute(SQL_CREATE_INDEX.format(table_name=self.table_name, index_hash=self.index_hash)) if self.pg_bigm: - cur.execute("CREATE EXTENSION IF NOT EXISTS pg_bigm") - cur.execute(SQL_CREATE_INDEX_PG_BIGM.format(table_name=self.table_name)) + cur.execute(SQL_CREATE_INDEX_PG_BIGM.format(table_name=self.table_name, index_hash=self.index_hash)) redis_client.set(collection_exist_cache_key, 1, ex=3600) diff --git a/api/core/rag/datasource/vdb/pyvastbase/__init__.py b/api/core/rag/datasource/vdb/pyvastbase/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/api/core/rag/datasource/vdb/pyvastbase/vastbase_vector.py b/api/core/rag/datasource/vdb/pyvastbase/vastbase_vector.py new file mode 100644 index 0000000000..156730ff37 --- /dev/null +++ b/api/core/rag/datasource/vdb/pyvastbase/vastbase_vector.py @@ -0,0 +1,243 @@ +import json +import uuid +from contextlib import contextmanager +from typing import Any + +import psycopg2.extras # type: ignore +import psycopg2.pool # type: ignore +from pydantic import BaseModel, model_validator + +from configs import dify_config +from core.rag.datasource.vdb.vector_base import BaseVector +from core.rag.datasource.vdb.vector_factory import AbstractVectorFactory +from core.rag.datasource.vdb.vector_type import VectorType +from core.rag.embedding.embedding_base import Embeddings +from core.rag.models.document import Document +from extensions.ext_redis import redis_client +from models.dataset import Dataset + + +class VastbaseVectorConfig(BaseModel): + host: str + port: int + user: str + password: str + database: str + min_connection: int + max_connection: int + + @model_validator(mode="before") + @classmethod + def validate_config(cls, values: dict) -> dict: + if not values["host"]: + raise ValueError("config VASTBASE_HOST is required") + if not values["port"]: + raise ValueError("config VASTBASE_PORT is required") + if not values["user"]: + raise ValueError("config VASTBASE_USER is required") + if not values["password"]: + raise ValueError("config VASTBASE_PASSWORD is required") + if not values["database"]: + raise ValueError("config VASTBASE_DATABASE is required") + if not values["min_connection"]: + raise ValueError("config VASTBASE_MIN_CONNECTION is required") + if not values["max_connection"]: + raise ValueError("config VASTBASE_MAX_CONNECTION is required") + if values["min_connection"] > values["max_connection"]: + raise ValueError("config VASTBASE_MIN_CONNECTION should less than VASTBASE_MAX_CONNECTION") + return values + + +SQL_CREATE_TABLE = """ +CREATE TABLE IF NOT EXISTS {table_name} ( + id UUID PRIMARY KEY, + text TEXT NOT NULL, + meta JSONB NOT NULL, + embedding floatvector({dimension}) NOT NULL +); +""" + +SQL_CREATE_INDEX = """ +CREATE INDEX IF NOT EXISTS embedding_cosine_v1_idx ON {table_name} +USING hnsw (embedding floatvector_cosine_ops) WITH (m = 16, ef_construction = 64); +""" + + +class VastbaseVector(BaseVector): + def __init__(self, collection_name: str, config: VastbaseVectorConfig): + super().__init__(collection_name) + self.pool = self._create_connection_pool(config) + self.table_name = f"embedding_{collection_name}" + + def get_type(self) -> str: + return VectorType.VASTBASE + + def _create_connection_pool(self, config: VastbaseVectorConfig): + return psycopg2.pool.SimpleConnectionPool( + config.min_connection, + config.max_connection, + host=config.host, + port=config.port, + user=config.user, + password=config.password, + database=config.database, + ) + + @contextmanager + def _get_cursor(self): + conn = self.pool.getconn() + cur = conn.cursor() + try: + yield cur + finally: + cur.close() + conn.commit() + self.pool.putconn(conn) + + def create(self, texts: list[Document], embeddings: list[list[float]], **kwargs): + dimension = len(embeddings[0]) + self._create_collection(dimension) + return self.add_texts(texts, embeddings) + + def add_texts(self, documents: list[Document], embeddings: list[list[float]], **kwargs): + values = [] + pks = [] + for i, doc in enumerate(documents): + if doc.metadata is not None: + doc_id = doc.metadata.get("doc_id", str(uuid.uuid4())) + pks.append(doc_id) + values.append( + ( + doc_id, + doc.page_content, + json.dumps(doc.metadata), + embeddings[i], + ) + ) + with self._get_cursor() as cur: + psycopg2.extras.execute_values( + cur, f"INSERT INTO {self.table_name} (id, text, meta, embedding) VALUES %s", values + ) + return pks + + def text_exists(self, id: str) -> bool: + with self._get_cursor() as cur: + cur.execute(f"SELECT id FROM {self.table_name} WHERE id = %s", (id,)) + return cur.fetchone() is not None + + def get_by_ids(self, ids: list[str]) -> list[Document]: + with self._get_cursor() as cur: + cur.execute(f"SELECT meta, text FROM {self.table_name} WHERE id IN %s", (tuple(ids),)) + docs = [] + for record in cur: + docs.append(Document(page_content=record[1], metadata=record[0])) + return docs + + def delete_by_ids(self, ids: list[str]) -> None: + # Avoiding crashes caused by performing delete operations on empty lists in certain scenarios + # Scenario 1: extract a document fails, resulting in a table not being created. + # Then clicking the retry button triggers a delete operation on an empty list. + if not ids: + return + with self._get_cursor() as cur: + cur.execute(f"DELETE FROM {self.table_name} WHERE id IN %s", (tuple(ids),)) + + def delete_by_metadata_field(self, key: str, value: str) -> None: + with self._get_cursor() as cur: + cur.execute(f"DELETE FROM {self.table_name} WHERE meta->>%s = %s", (key, value)) + + def search_by_vector(self, query_vector: list[float], **kwargs: Any) -> list[Document]: + """ + Search the nearest neighbors to a vector. + + :param query_vector: The input vector to search for similar items. + :param top_k: The number of nearest neighbors to return, default is 5. + :return: List of Documents that are nearest to the query vector. + """ + top_k = kwargs.get("top_k", 4) + + if not isinstance(top_k, int) or top_k <= 0: + raise ValueError("top_k must be a positive integer") + with self._get_cursor() as cur: + cur.execute( + f"SELECT meta, text, embedding <=> %s AS distance FROM {self.table_name}" + f" ORDER BY distance LIMIT {top_k}", + (json.dumps(query_vector),), + ) + docs = [] + score_threshold = float(kwargs.get("score_threshold") or 0.0) + for record in cur: + metadata, text, distance = record + score = 1 - distance + metadata["score"] = score + if score > score_threshold: + docs.append(Document(page_content=text, metadata=metadata)) + return docs + + def search_by_full_text(self, query: str, **kwargs: Any) -> list[Document]: + top_k = kwargs.get("top_k", 5) + + if not isinstance(top_k, int) or top_k <= 0: + raise ValueError("top_k must be a positive integer") + with self._get_cursor() as cur: + cur.execute( + f"""SELECT meta, text, ts_rank(to_tsvector(coalesce(text, '')), plainto_tsquery(%s)) AS score + FROM {self.table_name} + WHERE to_tsvector(text) @@ plainto_tsquery(%s) + ORDER BY score DESC + LIMIT {top_k}""", + # f"'{query}'" is required in order to account for whitespace in query + (f"'{query}'", f"'{query}'"), + ) + + docs = [] + + for record in cur: + metadata, text, score = record + metadata["score"] = score + docs.append(Document(page_content=text, metadata=metadata)) + + return docs + + def delete(self) -> None: + with self._get_cursor() as cur: + cur.execute(f"DROP TABLE IF EXISTS {self.table_name}") + + def _create_collection(self, dimension: int): + cache_key = f"vector_indexing_{self._collection_name}" + lock_name = f"{cache_key}_lock" + with redis_client.lock(lock_name, timeout=20): + collection_exist_cache_key = f"vector_indexing_{self._collection_name}" + if redis_client.get(collection_exist_cache_key): + return + + with self._get_cursor() as cur: + cur.execute(SQL_CREATE_TABLE.format(table_name=self.table_name, dimension=dimension)) + # Vastbase 支持的向量维度取值范围为 [1,16000] + if dimension <= 16000: + cur.execute(SQL_CREATE_INDEX.format(table_name=self.table_name)) + redis_client.set(collection_exist_cache_key, 1, ex=3600) + + +class VastbaseVectorFactory(AbstractVectorFactory): + def init_vector(self, dataset: Dataset, attributes: list, embeddings: Embeddings) -> VastbaseVector: + if dataset.index_struct_dict: + class_prefix: str = dataset.index_struct_dict["vector_store"]["class_prefix"] + collection_name = class_prefix + else: + dataset_id = dataset.id + collection_name = Dataset.gen_collection_name_by_id(dataset_id) + dataset.index_struct = json.dumps(self.gen_index_struct_dict(VectorType.VASTBASE, collection_name)) + + return VastbaseVector( + collection_name=collection_name, + config=VastbaseVectorConfig( + host=dify_config.VASTBASE_HOST or "localhost", + port=dify_config.VASTBASE_PORT, + user=dify_config.VASTBASE_USER or "dify", + password=dify_config.VASTBASE_PASSWORD or "", + database=dify_config.VASTBASE_DATABASE or "dify", + min_connection=dify_config.VASTBASE_MIN_CONNECTION, + max_connection=dify_config.VASTBASE_MAX_CONNECTION, + ), + ) diff --git a/api/core/rag/datasource/vdb/qdrant/qdrant_vector.py b/api/core/rag/datasource/vdb/qdrant/qdrant_vector.py index 1e040f415e..8ce194c683 100644 --- a/api/core/rag/datasource/vdb/qdrant/qdrant_vector.py +++ b/api/core/rag/datasource/vdb/qdrant/qdrant_vector.py @@ -46,6 +46,7 @@ class QdrantConfig(BaseModel): root_path: Optional[str] = None grpc_port: int = 6334 prefer_grpc: bool = False + replication_factor: int = 1 def to_qdrant_params(self): if self.endpoint and self.endpoint.startswith("path:"): @@ -119,11 +120,13 @@ class QdrantVector(BaseVector): max_indexing_threads=0, on_disk=False, ) + self._client.create_collection( collection_name=collection_name, vectors_config=vectors_config, hnsw_config=hnsw_config, timeout=int(self._client_config.timeout), + replication_factor=self._client_config.replication_factor, ) # create group_id payload index @@ -466,5 +469,6 @@ class QdrantVectorFactory(AbstractVectorFactory): timeout=dify_config.QDRANT_CLIENT_TIMEOUT, grpc_port=dify_config.QDRANT_GRPC_PORT, prefer_grpc=dify_config.QDRANT_GRPC_ENABLED, + replication_factor=dify_config.QDRANT_REPLICATION_FACTOR, ), ) diff --git a/api/core/rag/datasource/vdb/tencent/tencent_vector.py b/api/core/rag/datasource/vdb/tencent/tencent_vector.py index e266659075..d2bf3eb92a 100644 --- a/api/core/rag/datasource/vdb/tencent/tencent_vector.py +++ b/api/core/rag/datasource/vdb/tencent/tencent_vector.py @@ -271,12 +271,15 @@ class TencentVector(BaseVector): for result in res[0]: meta = result.get(self.field_metadata) + if isinstance(meta, str): + # Compatible with version 1.1.3 and below. + meta = json.loads(meta) + score = 1 - result.get("score", 0.0) score = result.get("score", 0.0) if score > score_threshold: meta["score"] = score doc = Document(page_content=result.get(self.field_text), metadata=meta) docs.append(doc) - return docs def delete(self) -> None: diff --git a/api/core/rag/datasource/vdb/tidb_on_qdrant/tidb_on_qdrant_vector.py b/api/core/rag/datasource/vdb/tidb_on_qdrant/tidb_on_qdrant_vector.py index 6a61fe9496..6f895b12af 100644 --- a/api/core/rag/datasource/vdb/tidb_on_qdrant/tidb_on_qdrant_vector.py +++ b/api/core/rag/datasource/vdb/tidb_on_qdrant/tidb_on_qdrant_vector.py @@ -49,6 +49,7 @@ class TidbOnQdrantConfig(BaseModel): root_path: Optional[str] = None grpc_port: int = 6334 prefer_grpc: bool = False + replication_factor: int = 1 def to_qdrant_params(self): if self.endpoint and self.endpoint.startswith("path:"): @@ -134,6 +135,7 @@ class TidbOnQdrantVector(BaseVector): vectors_config=vectors_config, hnsw_config=hnsw_config, timeout=int(self._client_config.timeout), + replication_factor=self._client_config.replication_factor, ) # create group_id payload index @@ -484,6 +486,7 @@ class TidbOnQdrantVectorFactory(AbstractVectorFactory): timeout=dify_config.TIDB_ON_QDRANT_CLIENT_TIMEOUT, grpc_port=dify_config.TIDB_ON_QDRANT_GRPC_PORT, prefer_grpc=dify_config.TIDB_ON_QDRANT_GRPC_ENABLED, + replication_factor=dify_config.QDRANT_REPLICATION_FACTOR, ), ) diff --git a/api/core/rag/datasource/vdb/tidb_on_qdrant/tidb_service.py b/api/core/rag/datasource/vdb/tidb_on_qdrant/tidb_service.py index 3958280bd5..184b5f2142 100644 --- a/api/core/rag/datasource/vdb/tidb_on_qdrant/tidb_service.py +++ b/api/core/rag/datasource/vdb/tidb_on_qdrant/tidb_service.py @@ -245,4 +245,4 @@ class TidbService: return cluster_infos else: response.raise_for_status() - return [] # FIXME for mypy, This line will not be reached as raise_for_status() will raise an exception + return [] diff --git a/api/core/rag/datasource/vdb/tidb_vector/tidb_vector.py b/api/core/rag/datasource/vdb/tidb_vector/tidb_vector.py index 00229ce700..61c68b939e 100644 --- a/api/core/rag/datasource/vdb/tidb_vector/tidb_vector.py +++ b/api/core/rag/datasource/vdb/tidb_vector/tidb_vector.py @@ -205,9 +205,9 @@ class TiDBVector(BaseVector): with Session(self._engine) as session: select_statement = sql_text(f""" - SELECT meta, text, distance + SELECT meta, text, distance FROM ( - SELECT + SELECT meta, text, {tidb_dist_func}(vector, :query_vector_str) AS distance diff --git a/api/core/rag/datasource/vdb/vector_factory.py b/api/core/rag/datasource/vdb/vector_factory.py index 05158cc7ca..66e002312a 100644 --- a/api/core/rag/datasource/vdb/vector_factory.py +++ b/api/core/rag/datasource/vdb/vector_factory.py @@ -74,6 +74,10 @@ class Vector: from core.rag.datasource.vdb.pgvector.pgvector import PGVectorFactory return PGVectorFactory + case VectorType.VASTBASE: + from core.rag.datasource.vdb.pyvastbase.vastbase_vector import VastbaseVectorFactory + + return VastbaseVectorFactory case VectorType.PGVECTO_RS: from core.rag.datasource.vdb.pgvecto_rs.pgvecto_rs import PGVectoRSFactory diff --git a/api/core/rag/datasource/vdb/vector_type.py b/api/core/rag/datasource/vdb/vector_type.py index 0421be3458..7a81565e37 100644 --- a/api/core/rag/datasource/vdb/vector_type.py +++ b/api/core/rag/datasource/vdb/vector_type.py @@ -7,7 +7,9 @@ class VectorType(StrEnum): MILVUS = "milvus" MYSCALE = "myscale" PGVECTOR = "pgvector" + VASTBASE = "vastbase" PGVECTO_RS = "pgvecto-rs" + QDRANT = "qdrant" RELYT = "relyt" TIDB_VECTOR = "tidb_vector" diff --git a/api/core/rag/embedding/cached_embedding.py b/api/core/rag/embedding/cached_embedding.py index 42fad111ce..f50f9f6b60 100644 --- a/api/core/rag/embedding/cached_embedding.py +++ b/api/core/rag/embedding/cached_embedding.py @@ -139,4 +139,4 @@ class CacheEmbedding(Embeddings): logging.exception(f"Failed to add embedding to redis for the text '{text[:10]}...({len(text)} chars)'") raise ex - return embedding_results + return embedding_results # type: ignore diff --git a/api/core/rag/entities/citation_metadata.py b/api/core/rag/entities/citation_metadata.py new file mode 100644 index 0000000000..00120425c9 --- /dev/null +++ b/api/core/rag/entities/citation_metadata.py @@ -0,0 +1,23 @@ +from typing import Any, Optional + +from pydantic import BaseModel + + +class RetrievalSourceMetadata(BaseModel): + position: Optional[int] = None + dataset_id: Optional[str] = None + dataset_name: Optional[str] = None + document_id: Optional[str] = None + document_name: Optional[str] = None + data_source_type: Optional[str] = None + segment_id: Optional[str] = None + retriever_from: Optional[str] = None + score: Optional[float] = None + hit_count: Optional[int] = None + word_count: Optional[int] = None + segment_position: Optional[int] = None + index_node_hash: Optional[str] = None + content: Optional[str] = None + page: Optional[int] = None + doc_metadata: Optional[dict[str, Any]] = None + title: Optional[str] = None diff --git a/api/core/rag/extractor/entity/extract_setting.py b/api/core/rag/extractor/entity/extract_setting.py index 7c00c668dd..1593ad1475 100644 --- a/api/core/rag/extractor/entity/extract_setting.py +++ b/api/core/rag/extractor/entity/extract_setting.py @@ -27,6 +27,8 @@ class WebsiteInfo(BaseModel): website import info. """ + model_config = ConfigDict(arbitrary_types_allowed=True) + provider: str job_id: str url: str @@ -34,12 +36,6 @@ class WebsiteInfo(BaseModel): tenant_id: str only_main_content: bool = False - class Config: - arbitrary_types_allowed = True - - def __init__(self, **data) -> None: - super().__init__(**data) - class ExtractSetting(BaseModel): """ diff --git a/api/core/rag/extractor/notion_extractor.py b/api/core/rag/extractor/notion_extractor.py index 7ab248199a..4e14800d0a 100644 --- a/api/core/rag/extractor/notion_extractor.py +++ b/api/core/rag/extractor/notion_extractor.py @@ -317,7 +317,7 @@ class NotionExtractor(BaseExtractor): data_source_info["last_edited_time"] = last_edited_time update_params = {DocumentModel.data_source_info: json.dumps(data_source_info)} - DocumentModel.query.filter_by(id=document_model.id).update(update_params) + db.session.query(DocumentModel).filter_by(id=document_model.id).update(update_params) db.session.commit() def get_notion_last_edited_time(self) -> str: @@ -347,14 +347,18 @@ class NotionExtractor(BaseExtractor): @classmethod def _get_access_token(cls, tenant_id: str, notion_workspace_id: str) -> str: - data_source_binding = DataSourceOauthBinding.query.filter( - db.and_( - DataSourceOauthBinding.tenant_id == tenant_id, - DataSourceOauthBinding.provider == "notion", - DataSourceOauthBinding.disabled == False, - DataSourceOauthBinding.source_info["workspace_id"] == f'"{notion_workspace_id}"', + data_source_binding = ( + db.session.query(DataSourceOauthBinding) + .filter( + db.and_( + DataSourceOauthBinding.tenant_id == tenant_id, + DataSourceOauthBinding.provider == "notion", + DataSourceOauthBinding.disabled == False, + DataSourceOauthBinding.source_info["workspace_id"] == f'"{notion_workspace_id}"', + ) ) - ).first() + .first() + ) if not data_source_binding: raise Exception( diff --git a/api/core/rag/extractor/watercrawl/client.py b/api/core/rag/extractor/watercrawl/client.py index 6eaede7dbc..6d596e07d8 100644 --- a/api/core/rag/extractor/watercrawl/client.py +++ b/api/core/rag/extractor/watercrawl/client.py @@ -6,6 +6,12 @@ from urllib.parse import urljoin import requests from requests import Response +from core.rag.extractor.watercrawl.exceptions import ( + WaterCrawlAuthenticationError, + WaterCrawlBadRequestError, + WaterCrawlPermissionError, +) + class BaseAPIClient: def __init__(self, api_key, base_url): @@ -53,6 +59,15 @@ class WaterCrawlAPIClient(BaseAPIClient): yield data def process_response(self, response: Response) -> dict | bytes | list | None | Generator: + if response.status_code == 401: + raise WaterCrawlAuthenticationError(response) + + if response.status_code == 403: + raise WaterCrawlPermissionError(response) + + if 400 <= response.status_code < 500: + raise WaterCrawlBadRequestError(response) + response.raise_for_status() if response.status_code == 204: return None diff --git a/api/core/rag/extractor/watercrawl/exceptions.py b/api/core/rag/extractor/watercrawl/exceptions.py new file mode 100644 index 0000000000..e407a594e0 --- /dev/null +++ b/api/core/rag/extractor/watercrawl/exceptions.py @@ -0,0 +1,32 @@ +import json + + +class WaterCrawlError(Exception): + pass + + +class WaterCrawlBadRequestError(WaterCrawlError): + def __init__(self, response): + self.status_code = response.status_code + self.response = response + data = response.json() + self.message = data.get("message", "Unknown error occurred") + self.errors = data.get("errors", {}) + super().__init__(self.message) + + @property + def flat_errors(self): + return json.dumps(self.errors) + + def __str__(self): + return f"WaterCrawlBadRequestError: {self.message} \n {self.flat_errors}" + + +class WaterCrawlPermissionError(WaterCrawlBadRequestError): + def __str__(self): + return f"You are exceeding your WaterCrawl API limits. {self.message}" + + +class WaterCrawlAuthenticationError(WaterCrawlBadRequestError): + def __str__(self): + return "WaterCrawl API key is invalid or expired. Please check your API key and try again." diff --git a/api/core/rag/extractor/watercrawl/provider.py b/api/core/rag/extractor/watercrawl/provider.py index b8003b386b..21fbb2100f 100644 --- a/api/core/rag/extractor/watercrawl/provider.py +++ b/api/core/rag/extractor/watercrawl/provider.py @@ -20,7 +20,7 @@ class WaterCrawlProvider: } if options.get("crawl_sub_pages", True): spider_options["page_limit"] = options.get("limit", 1) - spider_options["max_depth"] = options.get("depth", 1) + spider_options["max_depth"] = options.get("max_depth", 1) spider_options["include_paths"] = options.get("includes", "").split(",") if options.get("includes") else [] spider_options["exclude_paths"] = options.get("excludes", "").split(",") if options.get("excludes") else [] diff --git a/api/core/rag/extractor/word_extractor.py b/api/core/rag/extractor/word_extractor.py index edaa8c92fa..bff0acc48f 100644 --- a/api/core/rag/extractor/word_extractor.py +++ b/api/core/rag/extractor/word_extractor.py @@ -19,7 +19,7 @@ from core.rag.extractor.extractor_base import BaseExtractor from core.rag.models.document import Document from extensions.ext_database import db from extensions.ext_storage import storage -from models.enums import CreatedByRole +from models.enums import CreatorUserRole from models.model import UploadFile logger = logging.getLogger(__name__) @@ -76,8 +76,7 @@ class WordExtractor(BaseExtractor): parsed = urlparse(url) return bool(parsed.netloc) and bool(parsed.scheme) - def _extract_images_from_docx(self, doc, image_folder): - os.makedirs(image_folder, exist_ok=True) + def _extract_images_from_docx(self, doc): image_count = 0 image_map = {} @@ -117,7 +116,7 @@ class WordExtractor(BaseExtractor): extension=str(image_ext), mime_type=mime_type or "", created_by=self.user_id, - created_by_role=CreatedByRole.ACCOUNT, + created_by_role=CreatorUserRole.ACCOUNT, created_at=datetime.datetime.now(datetime.UTC).replace(tzinfo=None), used=True, used_by=self.user_id, @@ -210,7 +209,7 @@ class WordExtractor(BaseExtractor): content = [] - image_map = self._extract_images_from_docx(doc, image_folder) + image_map = self._extract_images_from_docx(doc) hyperlinks_url = None url_pattern = re.compile(r"http://[^\s+]+//|https://[^\s+]+") @@ -225,7 +224,7 @@ class WordExtractor(BaseExtractor): xml = ElementTree.XML(run.element.xml) x_child = [c for c in xml.iter() if c is not None] for x in x_child: - if x_child is None: + if x is None: continue if x.tag.endswith("instrText"): if x.text is None: diff --git a/api/core/rag/index_processor/processor/qa_index_processor.py b/api/core/rag/index_processor/processor/qa_index_processor.py index 0055625e13..e778b2cec4 100644 --- a/api/core/rag/index_processor/processor/qa_index_processor.py +++ b/api/core/rag/index_processor/processor/qa_index_processor.py @@ -104,7 +104,7 @@ class QAIndexProcessor(BaseIndexProcessor): def format_by_template(self, file: FileStorage, **kwargs) -> list[Document]: # check file type - if not file.filename.endswith(".csv"): + if not file.filename or not file.filename.endswith(".csv"): raise ValueError("Invalid file type. Only CSV files are allowed") try: diff --git a/api/core/rag/models/document.py b/api/core/rag/models/document.py index 421cdc05df..04a3428ad8 100644 --- a/api/core/rag/models/document.py +++ b/api/core/rag/models/document.py @@ -45,13 +45,12 @@ class BaseDocumentTransformer(ABC): .. code-block:: python class EmbeddingsRedundantFilter(BaseDocumentTransformer, BaseModel): + model_config = ConfigDict(arbitrary_types_allowed=True) + embeddings: Embeddings similarity_fn: Callable = cosine_similarity similarity_threshold: float = 0.95 - class Config: - arbitrary_types_allowed = True - def transform_documents( self, documents: Sequence[Document], **kwargs: Any ) -> Sequence[Document]: diff --git a/api/core/rag/rerank/rerank_model.py b/api/core/rag/rerank/rerank_model.py index ac7a3f8bb8..693535413a 100644 --- a/api/core/rag/rerank/rerank_model.py +++ b/api/core/rag/rerank/rerank_model.py @@ -52,14 +52,16 @@ class RerankModelRunner(BaseRerankRunner): rerank_documents = [] for result in rerank_result.docs: - # format document - rerank_document = Document( - page_content=result.text, - metadata=documents[result.index].metadata, - provider=documents[result.index].provider, - ) - if rerank_document.metadata is not None: - rerank_document.metadata["score"] = result.score - rerank_documents.append(rerank_document) + if score_threshold is None or result.score >= score_threshold: + # format document + rerank_document = Document( + page_content=result.text, + metadata=documents[result.index].metadata, + provider=documents[result.index].provider, + ) + if rerank_document.metadata is not None: + rerank_document.metadata["score"] = result.score + rerank_documents.append(rerank_document) - return rerank_documents + rerank_documents.sort(key=lambda x: x.metadata.get("score", 0.0), reverse=True) + return rerank_documents[:top_n] if top_n else rerank_documents diff --git a/api/core/rag/retrieval/dataset_retrieval.py b/api/core/rag/retrieval/dataset_retrieval.py index 4869a21e80..6978860529 100644 --- a/api/core/rag/retrieval/dataset_retrieval.py +++ b/api/core/rag/retrieval/dataset_retrieval.py @@ -7,7 +7,7 @@ from collections.abc import Generator, Mapping from typing import Any, Optional, Union, cast from flask import Flask, current_app -from sqlalchemy import Integer, and_, or_, text +from sqlalchemy import Float, and_, or_, text from sqlalchemy import cast as sqlalchemy_cast from core.app.app_config.entities import ( @@ -35,6 +35,7 @@ from core.prompt.simple_prompt_transform import ModelMode from core.rag.data_post_processor.data_post_processor import DataPostProcessor from core.rag.datasource.keyword.jieba.jieba_keyword_table_handler import JiebaKeywordTableHandler from core.rag.datasource.retrieval_service import RetrievalService +from core.rag.entities.citation_metadata import RetrievalSourceMetadata from core.rag.entities.context_entities import DocumentContext from core.rag.entities.metadata_entities import Condition, MetadataCondition from core.rag.index_processor.constant.index_type import IndexType @@ -149,7 +150,7 @@ class DatasetRetrieval: else: inputs = {} available_datasets_ids = [dataset.id for dataset in available_datasets] - metadata_filter_document_ids, metadata_condition = self._get_metadata_filter_condition( + metadata_filter_document_ids, metadata_condition = self.get_metadata_filter_condition( available_datasets_ids, query, tenant_id, @@ -190,7 +191,7 @@ class DatasetRetrieval: retrieve_config.rerank_mode or "reranking_model", retrieve_config.reranking_model, retrieve_config.weights, - retrieve_config.reranking_enabled or True, + True if retrieve_config.reranking_enabled is None else retrieve_config.reranking_enabled, message_id, metadata_filter_document_ids, metadata_condition, @@ -198,21 +199,21 @@ class DatasetRetrieval: dify_documents = [item for item in all_documents if item.provider == "dify"] external_documents = [item for item in all_documents if item.provider == "external"] - document_context_list = [] - retrieval_resource_list = [] + document_context_list: list[DocumentContext] = [] + retrieval_resource_list: list[RetrievalSourceMetadata] = [] # deal with external documents for item in external_documents: document_context_list.append(DocumentContext(content=item.page_content, score=item.metadata.get("score"))) - source = { - "dataset_id": item.metadata.get("dataset_id"), - "dataset_name": item.metadata.get("dataset_name"), - "document_id": item.metadata.get("document_id") or item.metadata.get("title"), - "document_name": item.metadata.get("title"), - "data_source_type": "external", - "retriever_from": invoke_from.to_source(), - "score": item.metadata.get("score"), - "content": item.page_content, - } + source = RetrievalSourceMetadata( + dataset_id=item.metadata.get("dataset_id"), + dataset_name=item.metadata.get("dataset_name"), + document_id=item.metadata.get("document_id") or item.metadata.get("title"), + document_name=item.metadata.get("title"), + data_source_type="external", + retriever_from=invoke_from.to_source(), + score=item.metadata.get("score"), + content=item.page_content, + ) retrieval_resource_list.append(source) # deal with dify documents if dify_documents: @@ -237,39 +238,43 @@ class DatasetRetrieval: if show_retrieve_source: for record in records: segment = record.segment - dataset = Dataset.query.filter_by(id=segment.dataset_id).first() - document = DatasetDocument.query.filter( - DatasetDocument.id == segment.document_id, - DatasetDocument.enabled == True, - DatasetDocument.archived == False, - ).first() + dataset = db.session.query(Dataset).filter_by(id=segment.dataset_id).first() + document = ( + db.session.query(DatasetDocument) + .filter( + DatasetDocument.id == segment.document_id, + DatasetDocument.enabled == True, + DatasetDocument.archived == False, + ) + .first() + ) if dataset and document: - source = { - "dataset_id": dataset.id, - "dataset_name": dataset.name, - "document_id": document.id, - "document_name": document.name, - "data_source_type": document.data_source_type, - "segment_id": segment.id, - "retriever_from": invoke_from.to_source(), - "score": record.score or 0.0, - "doc_metadata": document.doc_metadata, - } + source = RetrievalSourceMetadata( + dataset_id=dataset.id, + dataset_name=dataset.name, + document_id=document.id, + document_name=document.name, + data_source_type=document.data_source_type, + segment_id=segment.id, + retriever_from=invoke_from.to_source(), + score=record.score or 0.0, + doc_metadata=document.doc_metadata, + ) if invoke_from.to_source() == "dev": - source["hit_count"] = segment.hit_count - source["word_count"] = segment.word_count - source["segment_position"] = segment.position - source["index_node_hash"] = segment.index_node_hash + source.hit_count = segment.hit_count + source.word_count = segment.word_count + source.segment_position = segment.position + source.index_node_hash = segment.index_node_hash if segment.answer: - source["content"] = f"question:{segment.content} \nanswer:{segment.answer}" + source.content = f"question:{segment.content} \nanswer:{segment.answer}" else: - source["content"] = segment.content + source.content = segment.content retrieval_resource_list.append(source) if hit_callback and retrieval_resource_list: - retrieval_resource_list = sorted(retrieval_resource_list, key=lambda x: x.get("score") or 0.0, reverse=True) + retrieval_resource_list = sorted(retrieval_resource_list, key=lambda x: x.score or 0.0, reverse=True) for position, item in enumerate(retrieval_resource_list, start=1): - item["position"] = position + item.position = position hit_callback.return_retriever_resource_info(retrieval_resource_list) if document_context_list: document_context_list = sorted(document_context_list, key=lambda x: x.score or 0.0, reverse=True) @@ -506,19 +511,30 @@ class DatasetRetrieval: dify_documents = [document for document in documents if document.provider == "dify"] for document in dify_documents: if document.metadata is not None: - dataset_document = DatasetDocument.query.filter( - DatasetDocument.id == document.metadata["document_id"] - ).first() + dataset_document = ( + db.session.query(DatasetDocument) + .filter(DatasetDocument.id == document.metadata["document_id"]) + .first() + ) if dataset_document: if dataset_document.doc_form == IndexType.PARENT_CHILD_INDEX: - child_chunk = ChildChunk.query.filter( - ChildChunk.index_node_id == document.metadata["doc_id"], - ChildChunk.dataset_id == dataset_document.dataset_id, - ChildChunk.document_id == dataset_document.id, - ).first() + child_chunk = ( + db.session.query(ChildChunk) + .filter( + ChildChunk.index_node_id == document.metadata["doc_id"], + ChildChunk.dataset_id == dataset_document.dataset_id, + ChildChunk.document_id == dataset_document.id, + ) + .first() + ) if child_chunk: - segment = DocumentSegment.query.filter(DocumentSegment.id == child_chunk.segment_id).update( - {DocumentSegment.hit_count: DocumentSegment.hit_count + 1}, synchronize_session=False + segment = ( + db.session.query(DocumentSegment) + .filter(DocumentSegment.id == child_chunk.segment_id) + .update( + {DocumentSegment.hit_count: DocumentSegment.hit_count + 1}, + synchronize_session=False, + ) ) db.session.commit() else: @@ -649,6 +665,8 @@ class DatasetRetrieval: return_resource: bool, invoke_from: InvokeFrom, hit_callback: DatasetIndexToolCallbackHandler, + user_id: str, + inputs: dict, ) -> Optional[list[DatasetRetrieverBaseTool]]: """ A dataset tool is a tool that can be used to retrieve information from a dataset @@ -706,6 +724,9 @@ class DatasetRetrieval: hit_callbacks=[hit_callback], return_resource=return_resource, retriever_from=invoke_from.to_source(), + retrieve_config=retrieve_config, + user_id=user_id, + inputs=inputs, ) tools.append(tool) @@ -826,7 +847,7 @@ class DatasetRetrieval: ) return filter_documents[:top_k] if top_k else filter_documents - def _get_metadata_filter_condition( + def get_metadata_filter_condition( self, dataset_ids: list, query: str, @@ -876,20 +897,31 @@ class DatasetRetrieval: ) elif metadata_filtering_mode == "manual": if metadata_filtering_conditions: - metadata_condition = MetadataCondition(**metadata_filtering_conditions.model_dump()) + conditions = [] for sequence, condition in enumerate(metadata_filtering_conditions.conditions): # type: ignore metadata_name = condition.name expected_value = condition.value - if expected_value is not None or condition.comparison_operator in ("empty", "not empty"): + if expected_value is not None and condition.comparison_operator not in ("empty", "not empty"): if isinstance(expected_value, str): expected_value = self._replace_metadata_filter_value(expected_value, inputs) - filters = self._process_metadata_filter_func( - sequence, - condition.comparison_operator, - metadata_name, - expected_value, - filters, + conditions.append( + Condition( + name=metadata_name, + comparison_operator=condition.comparison_operator, + value=expected_value, ) + ) + filters = self._process_metadata_filter_func( + sequence, + condition.comparison_operator, + metadata_name, + expected_value, + filters, + ) + metadata_condition = MetadataCondition( + logical_operator=metadata_filtering_conditions.logical_operator, + conditions=conditions, + ) else: raise ValueError("Invalid metadata filtering mode") if filters: @@ -905,6 +937,9 @@ class DatasetRetrieval: return metadata_filter_document_ids, metadata_condition def _replace_metadata_filter_value(self, text: str, inputs: dict) -> str: + if not inputs: + return text + def replacer(match): key = match.group(1) return str(inputs.get(key, f"{{{{{key}}}}}")) @@ -1005,28 +1040,24 @@ class DatasetRetrieval: if isinstance(value, str): filters.append(DatasetDocument.doc_metadata[metadata_name] == f'"{value}"') else: - filters.append( - sqlalchemy_cast(DatasetDocument.doc_metadata[metadata_name].astext, Integer) == value - ) + filters.append(sqlalchemy_cast(DatasetDocument.doc_metadata[metadata_name].astext, Float) == value) case "is not" | "≠": if isinstance(value, str): filters.append(DatasetDocument.doc_metadata[metadata_name] != f'"{value}"') else: - filters.append( - sqlalchemy_cast(DatasetDocument.doc_metadata[metadata_name].astext, Integer) != value - ) + filters.append(sqlalchemy_cast(DatasetDocument.doc_metadata[metadata_name].astext, Float) != value) case "empty": filters.append(DatasetDocument.doc_metadata[metadata_name].is_(None)) case "not empty": filters.append(DatasetDocument.doc_metadata[metadata_name].isnot(None)) case "before" | "<": - filters.append(sqlalchemy_cast(DatasetDocument.doc_metadata[metadata_name].astext, Integer) < value) + filters.append(sqlalchemy_cast(DatasetDocument.doc_metadata[metadata_name].astext, Float) < value) case "after" | ">": - filters.append(sqlalchemy_cast(DatasetDocument.doc_metadata[metadata_name].astext, Integer) > value) + filters.append(sqlalchemy_cast(DatasetDocument.doc_metadata[metadata_name].astext, Float) > value) case "≤" | "<=": - filters.append(sqlalchemy_cast(DatasetDocument.doc_metadata[metadata_name].astext, Integer) <= value) + filters.append(sqlalchemy_cast(DatasetDocument.doc_metadata[metadata_name].astext, Float) <= value) case "≥" | ">=": - filters.append(sqlalchemy_cast(DatasetDocument.doc_metadata[metadata_name].astext, Integer) >= value) + filters.append(sqlalchemy_cast(DatasetDocument.doc_metadata[metadata_name].astext, Float) >= value) case _: pass return filters diff --git a/api/core/rag/retrieval/router/multi_dataset_react_route.py b/api/core/rag/retrieval/router/multi_dataset_react_route.py index f0426ace1f..33a283771d 100644 --- a/api/core/rag/retrieval/router/multi_dataset_react_route.py +++ b/api/core/rag/retrieval/router/multi_dataset_react_route.py @@ -9,7 +9,7 @@ from core.prompt.advanced_prompt_transform import AdvancedPromptTransform from core.prompt.entities.advanced_prompt_entities import ChatModelMessage, CompletionModelPromptTemplate from core.rag.retrieval.output_parser.react_output import ReactAction from core.rag.retrieval.output_parser.structured_chat import StructuredChatOutputParser -from core.workflow.nodes.llm import LLMNode +from core.workflow.nodes.llm import llm_utils PREFIX = """Respond to the human as helpfully and accurately as possible. You have access to the following tools:""" @@ -165,7 +165,7 @@ class ReactMultiDatasetRouter: text, usage = self._handle_invoke_result(invoke_result=invoke_result) # deduct quota - LLMNode.deduct_llm_quota(tenant_id=tenant_id, model_instance=model_instance, usage=usage) + llm_utils.deduct_llm_quota(tenant_id=tenant_id, model_instance=model_instance, usage=usage) return text, usage diff --git a/api/core/rag/retrieval/template_prompts.py b/api/core/rag/retrieval/template_prompts.py index 7abd55d798..9c945e2f52 100644 --- a/api/core/rag/retrieval/template_prompts.py +++ b/api/core/rag/retrieval/template_prompts.py @@ -2,7 +2,7 @@ METADATA_FILTER_SYSTEM_PROMPT = """ ### Job Description', You are a text metadata extract engine that extract text's metadata based on user input and set the metadata value ### Task - Your task is to ONLY extract the metadatas that exist in the input text from the provided metadata list and Use the following operators ["=", "!=", ">", "<", ">=", "<="] to express logical relationships, then return result in JSON format with the key "metadata_fields" and value "metadata_field_value" and comparison operator "comparison_operator". + Your task is to ONLY extract the metadatas that exist in the input text from the provided metadata list and Use the following operators ["contains", "not contains", "start with", "end with", "is", "is not", "empty", "not empty", "=", "≠", ">", "<", "≥", "≤", "before", "after"] to express logical relationships, then return result in JSON format with the key "metadata_fields" and value "metadata_field_value" and comparison operator "comparison_operator". ### Format The input text is in the variable input_text. Metadata are specified as a list in the variable metadata_fields. ### Constraint @@ -50,7 +50,7 @@ You are a text metadata extract engine that extract text's metadata based on use # Your task is to ONLY extract the metadatas that exist in the input text from the provided metadata list and Use the following operators ["=", "!=", ">", "<", ">=", "<="] to express logical relationships, then return result in JSON format with the key "metadata_fields" and value "metadata_field_value" and comparison operator "comparison_operator". ### Format The input text is in the variable input_text. Metadata are specified as a list in the variable metadata_fields. -### Constraint +### Constraint DO NOT include anything other than the JSON array in your response. ### Example Here is the chat example between human and assistant, inside XML tags. @@ -59,7 +59,7 @@ User:{{"input_text": ["I want to know which company’s email address test@examp Assistant:{{"metadata_map": [{{"metadata_field_name": "email", "metadata_field_value": "test@example.com", "comparison_operator": "="}}]}} User:{{"input_text": "What are the movies with a score of more than 9 in 2024?", "metadata_fields": ["name", "year", "rating", "country"]}} Assistant:{{"metadata_map": [{{"metadata_field_name": "year", "metadata_field_value": "2024", "comparison_operator": "="}, {{"metadata_field_name": "rating", "metadata_field_value": "9", "comparison_operator": ">"}}]}} - + ### User Input {{"input_text" : "{input_text}", "metadata_fields" : {metadata_fields}}} ### Assistant Output diff --git a/api/core/rag/splitter/text_splitter.py b/api/core/rag/splitter/text_splitter.py index 34b4056cf5..b711e8434a 100644 --- a/api/core/rag/splitter/text_splitter.py +++ b/api/core/rag/splitter/text_splitter.py @@ -159,50 +159,6 @@ class TextSplitter(BaseDocumentTransformer, ABC): ) return cls(length_function=lambda x: [_huggingface_tokenizer_length(text) for text in x], **kwargs) - @classmethod - def from_tiktoken_encoder( - cls: type[TS], - encoding_name: str = "gpt2", - model_name: Optional[str] = None, - allowed_special: Union[Literal["all"], Set[str]] = set(), - disallowed_special: Union[Literal["all"], Collection[str]] = "all", - **kwargs: Any, - ) -> TS: - """Text splitter that uses tiktoken encoder to count length.""" - try: - import tiktoken - except ImportError: - raise ImportError( - "Could not import tiktoken python package. " - "This is needed in order to calculate max_tokens_for_prompt. " - "Please install it with `pip install tiktoken`." - ) - - if model_name is not None: - enc = tiktoken.encoding_for_model(model_name) - else: - enc = tiktoken.get_encoding(encoding_name) - - def _tiktoken_encoder(text: str) -> int: - return len( - enc.encode( - text, - allowed_special=allowed_special, - disallowed_special=disallowed_special, - ) - ) - - if issubclass(cls, TokenTextSplitter): - extra_kwargs = { - "encoding_name": encoding_name, - "model_name": model_name, - "allowed_special": allowed_special, - "disallowed_special": disallowed_special, - } - kwargs = {**kwargs, **extra_kwargs} - - return cls(length_function=lambda x: [_tiktoken_encoder(text) for text in x], **kwargs) - def transform_documents(self, documents: Sequence[Document], **kwargs: Any) -> Sequence[Document]: """Transform sequence of documents by splitting them.""" return self.split_documents(list(documents)) diff --git a/api/core/repositories/__init__.py b/api/core/repositories/__init__.py new file mode 100644 index 0000000000..6452317120 --- /dev/null +++ b/api/core/repositories/__init__.py @@ -0,0 +1,12 @@ +""" +Repository implementations for data access. + +This package contains concrete implementations of the repository interfaces +defined in the core.workflow.repository package. +""" + +from core.repositories.sqlalchemy_workflow_node_execution_repository import SQLAlchemyWorkflowNodeExecutionRepository + +__all__ = [ + "SQLAlchemyWorkflowNodeExecutionRepository", +] diff --git a/api/core/repositories/sqlalchemy_workflow_execution_repository.py b/api/core/repositories/sqlalchemy_workflow_execution_repository.py new file mode 100644 index 0000000000..e5ead9dc56 --- /dev/null +++ b/api/core/repositories/sqlalchemy_workflow_execution_repository.py @@ -0,0 +1,256 @@ +""" +SQLAlchemy implementation of the WorkflowExecutionRepository. +""" + +import json +import logging +from typing import Optional, Union + +from sqlalchemy import func, select +from sqlalchemy.engine import Engine +from sqlalchemy.orm import sessionmaker + +from core.workflow.entities.workflow_execution import ( + WorkflowExecution, + WorkflowExecutionStatus, + WorkflowType, +) +from core.workflow.repositories.workflow_execution_repository import WorkflowExecutionRepository +from models import ( + Account, + CreatorUserRole, + EndUser, + WorkflowRun, +) +from models.enums import WorkflowRunTriggeredFrom + +logger = logging.getLogger(__name__) + + +class SQLAlchemyWorkflowExecutionRepository(WorkflowExecutionRepository): + """ + SQLAlchemy implementation of the WorkflowExecutionRepository interface. + + This implementation supports multi-tenancy by filtering operations based on tenant_id. + Each method creates its own session, handles the transaction, and commits changes + to the database. This prevents long-running connections in the workflow core. + + This implementation also includes an in-memory cache for workflow executions to improve + performance by reducing database queries. + """ + + def __init__( + self, + session_factory: sessionmaker | Engine, + user: Union[Account, EndUser], + app_id: Optional[str], + triggered_from: Optional[WorkflowRunTriggeredFrom], + ): + """ + Initialize the repository with a SQLAlchemy sessionmaker or engine and context information. + + Args: + session_factory: SQLAlchemy sessionmaker or engine for creating sessions + user: Account or EndUser object containing tenant_id, user ID, and role information + app_id: App ID for filtering by application (can be None) + triggered_from: Source of the execution trigger (DEBUGGING or APP_RUN) + """ + # If an engine is provided, create a sessionmaker from it + if isinstance(session_factory, Engine): + self._session_factory = sessionmaker(bind=session_factory, expire_on_commit=False) + elif isinstance(session_factory, sessionmaker): + self._session_factory = session_factory + else: + raise ValueError( + f"Invalid session_factory type {type(session_factory).__name__}; expected sessionmaker or Engine" + ) + + # Extract tenant_id from user + tenant_id: str | None = user.tenant_id if isinstance(user, EndUser) else user.current_tenant_id + if not tenant_id: + raise ValueError("User must have a tenant_id or current_tenant_id") + self._tenant_id = tenant_id + + # Store app context + self._app_id = app_id + + # Extract user context + self._triggered_from = triggered_from + self._creator_user_id = user.id + + # Determine user role based on user type + self._creator_user_role = CreatorUserRole.ACCOUNT if isinstance(user, Account) else CreatorUserRole.END_USER + + # Initialize in-memory cache for workflow executions + # Key: execution_id, Value: WorkflowRun (DB model) + self._execution_cache: dict[str, WorkflowRun] = {} + + def _to_domain_model(self, db_model: WorkflowRun) -> WorkflowExecution: + """ + Convert a database model to a domain model. + + Args: + db_model: The database model to convert + + Returns: + The domain model + """ + # Parse JSON fields + inputs = db_model.inputs_dict + outputs = db_model.outputs_dict + graph = db_model.graph_dict + + # Convert status to domain enum + status = WorkflowExecutionStatus(db_model.status) + + return WorkflowExecution( + id_=db_model.id, + workflow_id=db_model.workflow_id, + workflow_type=WorkflowType(db_model.type), + workflow_version=db_model.version, + graph=graph, + inputs=inputs, + outputs=outputs, + status=status, + error_message=db_model.error or "", + total_tokens=db_model.total_tokens, + total_steps=db_model.total_steps, + exceptions_count=db_model.exceptions_count, + started_at=db_model.created_at, + finished_at=db_model.finished_at, + ) + + def _to_db_model(self, domain_model: WorkflowExecution) -> WorkflowRun: + """ + Convert a domain model to a database model. + + Args: + domain_model: The domain model to convert + + Returns: + The database model + """ + # Use values from constructor if provided + if not self._triggered_from: + raise ValueError("triggered_from is required in repository constructor") + if not self._creator_user_id: + raise ValueError("created_by is required in repository constructor") + if not self._creator_user_role: + raise ValueError("created_by_role is required in repository constructor") + + db_model = WorkflowRun() + db_model.id = domain_model.id_ + db_model.tenant_id = self._tenant_id + if self._app_id is not None: + db_model.app_id = self._app_id + db_model.workflow_id = domain_model.workflow_id + db_model.triggered_from = self._triggered_from + + # Check if this is a new record + with self._session_factory() as session: + existing = session.scalar(select(WorkflowRun).where(WorkflowRun.id == domain_model.id_)) + if not existing: + # For new records, get the next sequence number + stmt = select(func.max(WorkflowRun.sequence_number)).where( + WorkflowRun.app_id == self._app_id, + WorkflowRun.tenant_id == self._tenant_id, + ) + max_sequence = session.scalar(stmt) + db_model.sequence_number = (max_sequence or 0) + 1 + else: + # For updates, keep the existing sequence number + db_model.sequence_number = existing.sequence_number + + db_model.type = domain_model.workflow_type + db_model.version = domain_model.workflow_version + db_model.graph = json.dumps(domain_model.graph) if domain_model.graph else None + db_model.inputs = json.dumps(domain_model.inputs) if domain_model.inputs else None + db_model.outputs = json.dumps(domain_model.outputs) if domain_model.outputs else None + db_model.status = domain_model.status + db_model.error = domain_model.error_message if domain_model.error_message else None + db_model.total_tokens = domain_model.total_tokens + db_model.total_steps = domain_model.total_steps + db_model.exceptions_count = domain_model.exceptions_count + db_model.created_by_role = self._creator_user_role + db_model.created_by = self._creator_user_id + db_model.created_at = domain_model.started_at + db_model.finished_at = domain_model.finished_at + + # Calculate elapsed time if finished_at is available + if domain_model.finished_at: + db_model.elapsed_time = (domain_model.finished_at - domain_model.started_at).total_seconds() + else: + db_model.elapsed_time = 0 + + return db_model + + def save(self, execution: WorkflowExecution) -> None: + """ + Save or update a WorkflowExecution domain entity to the database. + + This method serves as a domain-to-database adapter that: + 1. Converts the domain entity to its database representation + 2. Persists the database model using SQLAlchemy's merge operation + 3. Maintains proper multi-tenancy by including tenant context during conversion + 4. Updates the in-memory cache for faster subsequent lookups + + The method handles both creating new records and updating existing ones through + SQLAlchemy's merge operation. + + Args: + execution: The WorkflowExecution domain entity to persist + """ + # Convert domain model to database model using tenant context and other attributes + db_model = self._to_db_model(execution) + + # Create a new database session + with self._session_factory() as session: + # SQLAlchemy merge intelligently handles both insert and update operations + # based on the presence of the primary key + session.merge(db_model) + session.commit() + + # Update the in-memory cache for faster subsequent lookups + logger.debug(f"Updating cache for execution_id: {db_model.id}") + self._execution_cache[db_model.id] = db_model + + def get(self, execution_id: str) -> Optional[WorkflowExecution]: + """ + Retrieve a WorkflowExecution by its ID. + + First checks the in-memory cache, and if not found, queries the database. + If found in the database, adds it to the cache for future lookups. + + Args: + execution_id: The workflow execution ID + + Returns: + The WorkflowExecution instance if found, None otherwise + """ + # First check the cache + if execution_id in self._execution_cache: + logger.debug(f"Cache hit for execution_id: {execution_id}") + # Convert cached DB model to domain model + cached_db_model = self._execution_cache[execution_id] + return self._to_domain_model(cached_db_model) + + # If not in cache, query the database + logger.debug(f"Cache miss for execution_id: {execution_id}, querying database") + with self._session_factory() as session: + stmt = select(WorkflowRun).where( + WorkflowRun.id == execution_id, + WorkflowRun.tenant_id == self._tenant_id, + ) + + if self._app_id: + stmt = stmt.where(WorkflowRun.app_id == self._app_id) + + db_model = session.scalar(stmt) + if db_model: + # Add DB model to cache + self._execution_cache[execution_id] = db_model + + # Convert to domain model and return + return self._to_domain_model(db_model) + + return None diff --git a/api/core/repositories/sqlalchemy_workflow_node_execution_repository.py b/api/core/repositories/sqlalchemy_workflow_node_execution_repository.py new file mode 100644 index 0000000000..2f27442616 --- /dev/null +++ b/api/core/repositories/sqlalchemy_workflow_node_execution_repository.py @@ -0,0 +1,400 @@ +""" +SQLAlchemy implementation of the WorkflowNodeExecutionRepository. +""" + +import json +import logging +from collections.abc import Sequence +from typing import Optional, Union + +from sqlalchemy import UnaryExpression, asc, delete, desc, select +from sqlalchemy.engine import Engine +from sqlalchemy.orm import sessionmaker + +from core.model_runtime.utils.encoders import jsonable_encoder +from core.workflow.entities.workflow_node_execution import ( + WorkflowNodeExecution, + WorkflowNodeExecutionMetadataKey, + WorkflowNodeExecutionStatus, +) +from core.workflow.nodes.enums import NodeType +from core.workflow.repositories.workflow_node_execution_repository import OrderConfig, WorkflowNodeExecutionRepository +from models import ( + Account, + CreatorUserRole, + EndUser, + WorkflowNodeExecutionModel, + WorkflowNodeExecutionTriggeredFrom, +) + +logger = logging.getLogger(__name__) + + +class SQLAlchemyWorkflowNodeExecutionRepository(WorkflowNodeExecutionRepository): + """ + SQLAlchemy implementation of the WorkflowNodeExecutionRepository interface. + + This implementation supports multi-tenancy by filtering operations based on tenant_id. + Each method creates its own session, handles the transaction, and commits changes + to the database. This prevents long-running connections in the workflow core. + + This implementation also includes an in-memory cache for node executions to improve + performance by reducing database queries. + """ + + def __init__( + self, + session_factory: sessionmaker | Engine, + user: Union[Account, EndUser], + app_id: Optional[str], + triggered_from: Optional[WorkflowNodeExecutionTriggeredFrom], + ): + """ + Initialize the repository with a SQLAlchemy sessionmaker or engine and context information. + + Args: + session_factory: SQLAlchemy sessionmaker or engine for creating sessions + user: Account or EndUser object containing tenant_id, user ID, and role information + app_id: App ID for filtering by application (can be None) + triggered_from: Source of the execution trigger (SINGLE_STEP or WORKFLOW_RUN) + """ + # If an engine is provided, create a sessionmaker from it + if isinstance(session_factory, Engine): + self._session_factory = sessionmaker(bind=session_factory, expire_on_commit=False) + elif isinstance(session_factory, sessionmaker): + self._session_factory = session_factory + else: + raise ValueError( + f"Invalid session_factory type {type(session_factory).__name__}; expected sessionmaker or Engine" + ) + + # Extract tenant_id from user + tenant_id: str | None = user.tenant_id if isinstance(user, EndUser) else user.current_tenant_id + if not tenant_id: + raise ValueError("User must have a tenant_id or current_tenant_id") + self._tenant_id = tenant_id + + # Store app context + self._app_id = app_id + + # Extract user context + self._triggered_from = triggered_from + self._creator_user_id = user.id + + # Determine user role based on user type + self._creator_user_role = CreatorUserRole.ACCOUNT if isinstance(user, Account) else CreatorUserRole.END_USER + + # Initialize in-memory cache for node executions + # Key: node_execution_id, Value: WorkflowNodeExecution (DB model) + self._node_execution_cache: dict[str, WorkflowNodeExecutionModel] = {} + + def _to_domain_model(self, db_model: WorkflowNodeExecutionModel) -> WorkflowNodeExecution: + """ + Convert a database model to a domain model. + + Args: + db_model: The database model to convert + + Returns: + The domain model + """ + # Parse JSON fields + inputs = db_model.inputs_dict + process_data = db_model.process_data_dict + outputs = db_model.outputs_dict + metadata = {WorkflowNodeExecutionMetadataKey(k): v for k, v in db_model.execution_metadata_dict.items()} + + # Convert status to domain enum + status = WorkflowNodeExecutionStatus(db_model.status) + + return WorkflowNodeExecution( + id=db_model.id, + node_execution_id=db_model.node_execution_id, + workflow_id=db_model.workflow_id, + workflow_execution_id=db_model.workflow_run_id, + index=db_model.index, + predecessor_node_id=db_model.predecessor_node_id, + node_id=db_model.node_id, + node_type=NodeType(db_model.node_type), + title=db_model.title, + inputs=inputs, + process_data=process_data, + outputs=outputs, + status=status, + error=db_model.error, + elapsed_time=db_model.elapsed_time, + metadata=metadata, + created_at=db_model.created_at, + finished_at=db_model.finished_at, + ) + + def to_db_model(self, domain_model: WorkflowNodeExecution) -> WorkflowNodeExecutionModel: + """ + Convert a domain model to a database model. + + Args: + domain_model: The domain model to convert + + Returns: + The database model + """ + # Use values from constructor if provided + if not self._triggered_from: + raise ValueError("triggered_from is required in repository constructor") + if not self._creator_user_id: + raise ValueError("created_by is required in repository constructor") + if not self._creator_user_role: + raise ValueError("created_by_role is required in repository constructor") + + db_model = WorkflowNodeExecutionModel() + db_model.id = domain_model.id + db_model.tenant_id = self._tenant_id + if self._app_id is not None: + db_model.app_id = self._app_id + db_model.workflow_id = domain_model.workflow_id + db_model.triggered_from = self._triggered_from + db_model.workflow_run_id = domain_model.workflow_execution_id + db_model.index = domain_model.index + db_model.predecessor_node_id = domain_model.predecessor_node_id + db_model.node_execution_id = domain_model.node_execution_id + db_model.node_id = domain_model.node_id + db_model.node_type = domain_model.node_type + db_model.title = domain_model.title + db_model.inputs = json.dumps(domain_model.inputs) if domain_model.inputs else None + db_model.process_data = json.dumps(domain_model.process_data) if domain_model.process_data else None + db_model.outputs = json.dumps(domain_model.outputs) if domain_model.outputs else None + db_model.status = domain_model.status + db_model.error = domain_model.error + db_model.elapsed_time = domain_model.elapsed_time + db_model.execution_metadata = ( + json.dumps(jsonable_encoder(domain_model.metadata)) if domain_model.metadata else None + ) + db_model.created_at = domain_model.created_at + db_model.created_by_role = self._creator_user_role + db_model.created_by = self._creator_user_id + db_model.finished_at = domain_model.finished_at + return db_model + + def save(self, execution: WorkflowNodeExecution) -> None: + """ + Save or update a NodeExecution domain entity to the database. + + This method serves as a domain-to-database adapter that: + 1. Converts the domain entity to its database representation + 2. Persists the database model using SQLAlchemy's merge operation + 3. Maintains proper multi-tenancy by including tenant context during conversion + 4. Updates the in-memory cache for faster subsequent lookups + + The method handles both creating new records and updating existing ones through + SQLAlchemy's merge operation. + + Args: + execution: The NodeExecution domain entity to persist + """ + # Convert domain model to database model using tenant context and other attributes + db_model = self.to_db_model(execution) + + # Create a new database session + with self._session_factory() as session: + # SQLAlchemy merge intelligently handles both insert and update operations + # based on the presence of the primary key + session.merge(db_model) + session.commit() + + # Update the in-memory cache for faster subsequent lookups + # Only cache if we have a node_execution_id to use as the cache key + if db_model.node_execution_id: + logger.debug(f"Updating cache for node_execution_id: {db_model.node_execution_id}") + self._node_execution_cache[db_model.node_execution_id] = db_model + + def get_by_node_execution_id(self, node_execution_id: str) -> Optional[WorkflowNodeExecution]: + """ + Retrieve a NodeExecution by its node_execution_id. + + First checks the in-memory cache, and if not found, queries the database. + If found in the database, adds it to the cache for future lookups. + + Args: + node_execution_id: The node execution ID + + Returns: + The NodeExecution instance if found, None otherwise + """ + # First check the cache + if node_execution_id in self._node_execution_cache: + logger.debug(f"Cache hit for node_execution_id: {node_execution_id}") + # Convert cached DB model to domain model + cached_db_model = self._node_execution_cache[node_execution_id] + return self._to_domain_model(cached_db_model) + + # If not in cache, query the database + logger.debug(f"Cache miss for node_execution_id: {node_execution_id}, querying database") + with self._session_factory() as session: + stmt = select(WorkflowNodeExecutionModel).where( + WorkflowNodeExecutionModel.node_execution_id == node_execution_id, + WorkflowNodeExecutionModel.tenant_id == self._tenant_id, + ) + + if self._app_id: + stmt = stmt.where(WorkflowNodeExecutionModel.app_id == self._app_id) + + db_model = session.scalar(stmt) + if db_model: + # Add DB model to cache + self._node_execution_cache[node_execution_id] = db_model + + # Convert to domain model and return + return self._to_domain_model(db_model) + + return None + + def get_db_models_by_workflow_run( + self, + workflow_run_id: str, + order_config: Optional[OrderConfig] = None, + ) -> Sequence[WorkflowNodeExecutionModel]: + """ + Retrieve all WorkflowNodeExecution database models for a specific workflow run. + + This method directly returns database models without converting to domain models, + which is useful when you need to access database-specific fields like triggered_from. + It also updates the in-memory cache with the retrieved models. + + Args: + workflow_run_id: The workflow run ID + order_config: Optional configuration for ordering results + order_config.order_by: List of fields to order by (e.g., ["index", "created_at"]) + order_config.order_direction: Direction to order ("asc" or "desc") + + Returns: + A list of WorkflowNodeExecution database models + """ + with self._session_factory() as session: + stmt = select(WorkflowNodeExecutionModel).where( + WorkflowNodeExecutionModel.workflow_run_id == workflow_run_id, + WorkflowNodeExecutionModel.tenant_id == self._tenant_id, + WorkflowNodeExecutionModel.triggered_from == WorkflowNodeExecutionTriggeredFrom.WORKFLOW_RUN, + ) + + if self._app_id: + stmt = stmt.where(WorkflowNodeExecutionModel.app_id == self._app_id) + + # Apply ordering if provided + if order_config and order_config.order_by: + order_columns: list[UnaryExpression] = [] + for field in order_config.order_by: + column = getattr(WorkflowNodeExecutionModel, field, None) + if not column: + continue + if order_config.order_direction == "desc": + order_columns.append(desc(column)) + else: + order_columns.append(asc(column)) + + if order_columns: + stmt = stmt.order_by(*order_columns) + + db_models = session.scalars(stmt).all() + + # Update the cache with the retrieved DB models + for model in db_models: + if model.node_execution_id: + self._node_execution_cache[model.node_execution_id] = model + + return db_models + + def get_by_workflow_run( + self, + workflow_run_id: str, + order_config: Optional[OrderConfig] = None, + ) -> Sequence[WorkflowNodeExecution]: + """ + Retrieve all NodeExecution instances for a specific workflow run. + + This method always queries the database to ensure complete and ordered results, + but updates the cache with any retrieved executions. + + Args: + workflow_run_id: The workflow run ID + order_config: Optional configuration for ordering results + order_config.order_by: List of fields to order by (e.g., ["index", "created_at"]) + order_config.order_direction: Direction to order ("asc" or "desc") + + Returns: + A list of NodeExecution instances + """ + # Get the database models using the new method + db_models = self.get_db_models_by_workflow_run(workflow_run_id, order_config) + + # Convert database models to domain models + domain_models = [] + for model in db_models: + domain_model = self._to_domain_model(model) + domain_models.append(domain_model) + + return domain_models + + def get_running_executions(self, workflow_run_id: str) -> Sequence[WorkflowNodeExecution]: + """ + Retrieve all running NodeExecution instances for a specific workflow run. + + This method queries the database directly and updates the cache with any + retrieved executions that have a node_execution_id. + + Args: + workflow_run_id: The workflow run ID + + Returns: + A list of running NodeExecution instances + """ + with self._session_factory() as session: + stmt = select(WorkflowNodeExecutionModel).where( + WorkflowNodeExecutionModel.workflow_run_id == workflow_run_id, + WorkflowNodeExecutionModel.tenant_id == self._tenant_id, + WorkflowNodeExecutionModel.status == WorkflowNodeExecutionStatus.RUNNING, + WorkflowNodeExecutionModel.triggered_from == WorkflowNodeExecutionTriggeredFrom.WORKFLOW_RUN, + ) + + if self._app_id: + stmt = stmt.where(WorkflowNodeExecutionModel.app_id == self._app_id) + + db_models = session.scalars(stmt).all() + domain_models = [] + + for model in db_models: + # Update cache if node_execution_id is present + if model.node_execution_id: + self._node_execution_cache[model.node_execution_id] = model + + # Convert to domain model + domain_model = self._to_domain_model(model) + domain_models.append(domain_model) + + return domain_models + + def clear(self) -> None: + """ + Clear all WorkflowNodeExecution records for the current tenant_id and app_id. + + This method deletes all WorkflowNodeExecution records that match the tenant_id + and app_id (if provided) associated with this repository instance. + It also clears the in-memory cache. + """ + with self._session_factory() as session: + stmt = delete(WorkflowNodeExecutionModel).where(WorkflowNodeExecutionModel.tenant_id == self._tenant_id) + + if self._app_id: + stmt = stmt.where(WorkflowNodeExecutionModel.app_id == self._app_id) + + result = session.execute(stmt) + session.commit() + + deleted_count = result.rowcount + logger.info( + f"Cleared {deleted_count} workflow node execution records for tenant {self._tenant_id}" + + (f" and app {self._app_id}" if self._app_id else "") + ) + + # Clear the in-memory cache + self._node_execution_cache.clear() + logger.info("Cleared in-memory node execution cache") diff --git a/api/core/repository/repository_factory.py b/api/core/repository/repository_factory.py deleted file mode 100644 index 7da7e49055..0000000000 --- a/api/core/repository/repository_factory.py +++ /dev/null @@ -1,97 +0,0 @@ -""" -Repository factory for creating repository instances. - -This module provides a simple factory interface for creating repository instances. -It does not contain any implementation details or dependencies on specific repositories. -""" - -from collections.abc import Callable, Mapping -from typing import Any, Literal, Optional, cast - -from core.repository.workflow_node_execution_repository import WorkflowNodeExecutionRepository - -# Type for factory functions - takes a dict of parameters and returns any repository type -RepositoryFactoryFunc = Callable[[Mapping[str, Any]], Any] - -# Type for workflow node execution factory function -WorkflowNodeExecutionFactoryFunc = Callable[[Mapping[str, Any]], WorkflowNodeExecutionRepository] - -# Repository type literals -_RepositoryType = Literal["workflow_node_execution"] - - -class RepositoryFactory: - """ - Factory class for creating repository instances. - - This factory delegates the actual repository creation to implementation-specific - factory functions that are registered with the factory at runtime. - """ - - # Dictionary to store factory functions - _factory_functions: dict[str, RepositoryFactoryFunc] = {} - - @classmethod - def _register_factory(cls, repository_type: _RepositoryType, factory_func: RepositoryFactoryFunc) -> None: - """ - Register a factory function for a specific repository type. - This is a private method and should not be called directly. - - Args: - repository_type: The type of repository (e.g., 'workflow_node_execution') - factory_func: A function that takes parameters and returns a repository instance - """ - cls._factory_functions[repository_type] = factory_func - - @classmethod - def _create_repository(cls, repository_type: _RepositoryType, params: Optional[Mapping[str, Any]] = None) -> Any: - """ - Create a new repository instance with the provided parameters. - This is a private method and should not be called directly. - - Args: - repository_type: The type of repository to create - params: A dictionary of parameters to pass to the factory function - - Returns: - A new instance of the requested repository - - Raises: - ValueError: If no factory function is registered for the repository type - """ - if repository_type not in cls._factory_functions: - raise ValueError(f"No factory function registered for repository type '{repository_type}'") - - # Use empty dict if params is None - params = params or {} - - return cls._factory_functions[repository_type](params) - - @classmethod - def register_workflow_node_execution_factory(cls, factory_func: WorkflowNodeExecutionFactoryFunc) -> None: - """ - Register a factory function for the workflow node execution repository. - - Args: - factory_func: A function that takes parameters and returns a WorkflowNodeExecutionRepository instance - """ - cls._register_factory("workflow_node_execution", factory_func) - - @classmethod - def create_workflow_node_execution_repository( - cls, params: Optional[Mapping[str, Any]] = None - ) -> WorkflowNodeExecutionRepository: - """ - Create a new WorkflowNodeExecutionRepository instance with the provided parameters. - - Args: - params: A dictionary of parameters to pass to the factory function - - Returns: - A new instance of the WorkflowNodeExecutionRepository - - Raises: - ValueError: If no factory function is registered for the workflow_node_execution repository type - """ - # We can safely cast here because we've registered a WorkflowNodeExecutionFactoryFunc - return cast(WorkflowNodeExecutionRepository, cls._create_repository("workflow_node_execution", params)) diff --git a/api/core/tools/builtin_tool/_position.yaml b/api/core/tools/builtin_tool/_position.yaml index b5875e2075..0e811de311 100644 --- a/api/core/tools/builtin_tool/_position.yaml +++ b/api/core/tools/builtin_tool/_position.yaml @@ -1,3 +1,4 @@ +- audio - code - time -- qrcode +- webscraper diff --git a/api/core/tools/builtin_tool/provider.py b/api/core/tools/builtin_tool/provider.py index 4f733f0ea1..cf75bd3d7e 100644 --- a/api/core/tools/builtin_tool/provider.py +++ b/api/core/tools/builtin_tool/provider.py @@ -35,8 +35,9 @@ class BuiltinToolProviderController(ToolProviderController): provider_yaml["credentials_for_provider"][credential_name]["name"] = credential_name credentials_schema = [] - for credential in provider_yaml.get("credentials_for_provider", {}).values(): - credentials_schema.append(credential) + for credential in provider_yaml.get("credentials_for_provider", {}): + credential_dict = provider_yaml.get("credentials_for_provider", {}).get(credential, {}) + credentials_schema.append(credential_dict) super().__init__( entity=ToolProviderEntity( diff --git a/api/core/tools/builtin_tool/tool.py b/api/core/tools/builtin_tool/tool.py index 7f37f98d0c..724a2291c6 100644 --- a/api/core/tools/builtin_tool/tool.py +++ b/api/core/tools/builtin_tool/tool.py @@ -6,8 +6,8 @@ from core.tools.entities.tool_entities import ToolProviderType from core.tools.utils.model_invocation_utils import ModelInvocationUtils _SUMMARY_PROMPT = """You are a professional language researcher, you are interested in the language -and you can quickly aimed at the main point of an webpage and reproduce it in your own words but -retain the original meaning and keep the key points. +and you can quickly aimed at the main point of an webpage and reproduce it in your own words but +retain the original meaning and keep the key points. however, the text you got is too long, what you got is possible a part of the text. Please summarize the text you got. """ diff --git a/api/core/tools/custom_tool/tool.py b/api/core/tools/custom_tool/tool.py index 2f2f1ebbdd..2f5cc6d4c0 100644 --- a/api/core/tools/custom_tool/tool.py +++ b/api/core/tools/custom_tool/tool.py @@ -168,7 +168,7 @@ class ApiTool(Tool): cookies[parameter["name"]] = value elif parameter["in"] == "header": - headers[parameter["name"]] = value + headers[parameter["name"]] = str(value) # check if there is a request body and handle it if "requestBody" in self.api_bundle.openapi and self.api_bundle.openapi["requestBody"] is not None: diff --git a/api/core/tools/entities/tool_entities.py b/api/core/tools/entities/tool_entities.py index 37375f4a71..03047c0545 100644 --- a/api/core/tools/entities/tool_entities.py +++ b/api/core/tools/entities/tool_entities.py @@ -279,7 +279,6 @@ class ToolParameter(PluginParameter): :param options: the options of the parameter """ # convert options to ToolParameterOption - # FIXME fix the type error if options: option_objs = [ PluginParameterOption(value=option, label=I18nObject(en_US=option, zh_Hans=option)) diff --git a/api/core/tools/plugin_tool/provider.py b/api/core/tools/plugin_tool/provider.py index 3616e426b9..494b8e209c 100644 --- a/api/core/tools/plugin_tool/provider.py +++ b/api/core/tools/plugin_tool/provider.py @@ -1,6 +1,6 @@ from typing import Any -from core.plugin.manager.tool import PluginToolManager +from core.plugin.impl.tool import PluginToolManager from core.tools.__base.tool_runtime import ToolRuntime from core.tools.builtin_tool.provider import BuiltinToolProviderController from core.tools.entities.tool_entities import ToolProviderEntityWithPlugin, ToolProviderType diff --git a/api/core/tools/plugin_tool/tool.py b/api/core/tools/plugin_tool/tool.py index f31a9a0d3e..d21e3d7d1c 100644 --- a/api/core/tools/plugin_tool/tool.py +++ b/api/core/tools/plugin_tool/tool.py @@ -1,7 +1,7 @@ from collections.abc import Generator from typing import Any, Optional -from core.plugin.manager.tool import PluginToolManager +from core.plugin.impl.tool import PluginToolManager from core.plugin.utils.converter import convert_parameters_to_plugin_format from core.tools.__base.tool import Tool from core.tools.__base.tool_runtime import ToolRuntime diff --git a/api/core/tools/signature.py b/api/core/tools/signature.py new file mode 100644 index 0000000000..e80005d7bf --- /dev/null +++ b/api/core/tools/signature.py @@ -0,0 +1,41 @@ +import base64 +import hashlib +import hmac +import os +import time + +from configs import dify_config + + +def sign_tool_file(tool_file_id: str, extension: str) -> str: + """ + sign file to get a temporary url + """ + base_url = dify_config.FILES_URL + file_preview_url = f"{base_url}/files/tools/{tool_file_id}{extension}" + + timestamp = str(int(time.time())) + nonce = os.urandom(16).hex() + data_to_sign = f"file-preview|{tool_file_id}|{timestamp}|{nonce}" + secret_key = dify_config.SECRET_KEY.encode() if dify_config.SECRET_KEY else b"" + sign = hmac.new(secret_key, data_to_sign.encode(), hashlib.sha256).digest() + encoded_sign = base64.urlsafe_b64encode(sign).decode() + + return f"{file_preview_url}?timestamp={timestamp}&nonce={nonce}&sign={encoded_sign}" + + +def verify_tool_file_signature(file_id: str, timestamp: str, nonce: str, sign: str) -> bool: + """ + verify signature + """ + data_to_sign = f"file-preview|{file_id}|{timestamp}|{nonce}" + secret_key = dify_config.SECRET_KEY.encode() if dify_config.SECRET_KEY else b"" + recalculated_sign = hmac.new(secret_key, data_to_sign.encode(), hashlib.sha256).digest() + recalculated_encoded_sign = base64.urlsafe_b64encode(recalculated_sign).decode() + + # verify signature + if sign != recalculated_encoded_sign: + return False + + current_time = int(time.time()) + return current_time - int(timestamp) <= dify_config.FILES_ACCESS_TIMEOUT diff --git a/api/core/tools/tool_engine.py b/api/core/tools/tool_engine.py index 997917f31c..178f2b9689 100644 --- a/api/core/tools/tool_engine.py +++ b/api/core/tools/tool_engine.py @@ -32,7 +32,7 @@ from core.tools.errors import ( from core.tools.utils.message_transformer import ToolFileMessageTransformer from core.tools.workflow_as_tool.tool import WorkflowTool from extensions.ext_database import db -from models.enums import CreatedByRole +from models.enums import CreatorUserRole from models.model import Message, MessageFile @@ -246,7 +246,7 @@ class ToolEngine: + "you do not need to create it, just tell the user to check it now." ) elif response.type == ToolInvokeMessage.MessageType.JSON: - result = json.dumps( + result += json.dumps( cast(ToolInvokeMessage.JsonMessage, response.message).json_object, ensure_ascii=False ) else: @@ -339,9 +339,9 @@ class ToolEngine: url=message.url, upload_file_id=tool_file_id, created_by_role=( - CreatedByRole.ACCOUNT + CreatorUserRole.ACCOUNT if invoke_from in {InvokeFrom.EXPLORE, InvokeFrom.DEBUGGER} - else CreatedByRole.END_USER + else CreatorUserRole.END_USER ), created_by=user_id, ) diff --git a/api/core/tools/tool_file_manager.py b/api/core/tools/tool_file_manager.py index 7e8d4280d4..b849f51064 100644 --- a/api/core/tools/tool_file_manager.py +++ b/api/core/tools/tool_file_manager.py @@ -4,23 +4,34 @@ import hmac import logging import os import time +from collections.abc import Generator from mimetypes import guess_extension, guess_type from typing import Optional, Union from uuid import uuid4 import httpx +from sqlalchemy.orm import Session from configs import dify_config from core.helper import ssrf_proxy -from extensions.ext_database import db +from extensions.ext_database import db as global_db from extensions.ext_storage import storage from models.model import MessageFile from models.tools import ToolFile logger = logging.getLogger(__name__) +from sqlalchemy.engine import Engine + class ToolFileManager: + _engine: Engine + + def __init__(self, engine: Engine | None = None): + if engine is None: + engine = global_db.engine + self._engine = engine + @staticmethod def sign_file(tool_file_id: str, extension: str) -> str: """ @@ -55,8 +66,8 @@ class ToolFileManager: current_time = int(time.time()) return current_time - int(timestamp) <= dify_config.FILES_ACCESS_TIMEOUT - @staticmethod def create_file_by_raw( + self, *, user_id: str, tenant_id: str, @@ -77,24 +88,25 @@ class ToolFileManager: filepath = f"tools/{tenant_id}/{unique_filename}" storage.save(filepath, file_binary) - tool_file = ToolFile( - user_id=user_id, - tenant_id=tenant_id, - conversation_id=conversation_id, - file_key=filepath, - mimetype=mimetype, - name=present_filename, - size=len(file_binary), - ) + with Session(self._engine, expire_on_commit=False) as session: + tool_file = ToolFile( + user_id=user_id, + tenant_id=tenant_id, + conversation_id=conversation_id, + file_key=filepath, + mimetype=mimetype, + name=present_filename, + size=len(file_binary), + ) - db.session.add(tool_file) - db.session.commit() - db.session.refresh(tool_file) + session.add(tool_file) + session.commit() + session.refresh(tool_file) return tool_file - @staticmethod def create_file_by_url( + self, user_id: str, tenant_id: str, file_url: str, @@ -119,24 +131,24 @@ class ToolFileManager: filepath = f"tools/{tenant_id}/{filename}" storage.save(filepath, blob) - tool_file = ToolFile( - user_id=user_id, - tenant_id=tenant_id, - conversation_id=conversation_id, - file_key=filepath, - mimetype=mimetype, - original_url=file_url, - name=filename, - size=len(blob), - ) + with Session(self._engine, expire_on_commit=False) as session: + tool_file = ToolFile( + user_id=user_id, + tenant_id=tenant_id, + conversation_id=conversation_id, + file_key=filepath, + mimetype=mimetype, + original_url=file_url, + name=filename, + size=len(blob), + ) - db.session.add(tool_file) - db.session.commit() + session.add(tool_file) + session.commit() return tool_file - @staticmethod - def get_file_binary(id: str) -> Union[tuple[bytes, str], None]: + def get_file_binary(self, id: str) -> Union[tuple[bytes, str], None]: """ get file binary @@ -144,13 +156,14 @@ class ToolFileManager: :return: the binary of the file, mime type """ - tool_file: ToolFile | None = ( - db.session.query(ToolFile) - .filter( - ToolFile.id == id, + with Session(self._engine, expire_on_commit=False) as session: + tool_file: ToolFile | None = ( + session.query(ToolFile) + .filter( + ToolFile.id == id, + ) + .first() ) - .first() - ) if not tool_file: return None @@ -159,8 +172,7 @@ class ToolFileManager: return blob, tool_file.mimetype - @staticmethod - def get_file_binary_by_message_file_id(id: str) -> Union[tuple[bytes, str], None]: + def get_file_binary_by_message_file_id(self, id: str) -> Union[tuple[bytes, str], None]: """ get file binary @@ -168,33 +180,34 @@ class ToolFileManager: :return: the binary of the file, mime type """ - message_file: MessageFile | None = ( - db.session.query(MessageFile) - .filter( - MessageFile.id == id, + with Session(self._engine, expire_on_commit=False) as session: + message_file: MessageFile | None = ( + session.query(MessageFile) + .filter( + MessageFile.id == id, + ) + .first() ) - .first() - ) - # Check if message_file is not None - if message_file is not None: - # get tool file id - if message_file.url is not None: - tool_file_id = message_file.url.split("/")[-1] - # trim extension - tool_file_id = tool_file_id.split(".")[0] + # Check if message_file is not None + if message_file is not None: + # get tool file id + if message_file.url is not None: + tool_file_id = message_file.url.split("/")[-1] + # trim extension + tool_file_id = tool_file_id.split(".")[0] + else: + tool_file_id = None else: tool_file_id = None - else: - tool_file_id = None - tool_file: ToolFile | None = ( - db.session.query(ToolFile) - .filter( - ToolFile.id == tool_file_id, + tool_file: ToolFile | None = ( + session.query(ToolFile) + .filter( + ToolFile.id == tool_file_id, + ) + .first() ) - .first() - ) if not tool_file: return None @@ -203,8 +216,7 @@ class ToolFileManager: return blob, tool_file.mimetype - @staticmethod - def get_file_generator_by_tool_file_id(tool_file_id: str): + def get_file_generator_by_tool_file_id(self, tool_file_id: str) -> tuple[Optional[Generator], Optional[ToolFile]]: """ get file binary @@ -212,13 +224,14 @@ class ToolFileManager: :return: the binary of the file, mime type """ - tool_file: ToolFile | None = ( - db.session.query(ToolFile) - .filter( - ToolFile.id == tool_file_id, + with Session(self._engine, expire_on_commit=False) as session: + tool_file: ToolFile | None = ( + session.query(ToolFile) + .filter( + ToolFile.id == tool_file_id, + ) + .first() ) - .first() - ) if not tool_file: return None, None @@ -229,6 +242,11 @@ class ToolFileManager: # init tool_file_parser -from core.file.tool_file_parser import tool_file_manager +from core.file.tool_file_parser import set_tool_file_manager_factory + + +def _factory() -> ToolFileManager: + return ToolFileManager() + -tool_file_manager["manager"] = ToolFileManager +set_tool_file_manager_factory(_factory) diff --git a/api/core/tools/tool_manager.py b/api/core/tools/tool_manager.py index f2d0b74f7c..0bfe6329b1 100644 --- a/api/core/tools/tool_manager.py +++ b/api/core/tools/tool_manager.py @@ -10,7 +10,7 @@ from yarl import URL import contexts from core.plugin.entities.plugin import ToolProviderID -from core.plugin.manager.tool import PluginToolManager +from core.plugin.impl.tool import PluginToolManager from core.tools.__base.tool_provider import ToolProviderController from core.tools.__base.tool_runtime import ToolRuntime from core.tools.plugin_tool.provider import PluginToolProviderController @@ -528,7 +528,7 @@ class ToolManager: yield provider except Exception: - logger.exception(f"load builtin provider {provider}") + logger.exception(f"load builtin provider {provider_path}") continue # set builtin providers loaded cls._builtin_providers_loaded = True @@ -644,10 +644,10 @@ class ToolManager: ) workflow_provider_controllers: list[WorkflowToolProviderController] = [] - for provider in workflow_providers: + for workflow_provider in workflow_providers: try: workflow_provider_controllers.append( - ToolTransformService.workflow_provider_to_controller(db_provider=provider) + ToolTransformService.workflow_provider_to_controller(db_provider=workflow_provider) ) except Exception: # app has been deleted diff --git a/api/core/tools/utils/dataset_retriever/dataset_multi_retriever_tool.py b/api/core/tools/utils/dataset_retriever/dataset_multi_retriever_tool.py index 032274b87e..2cbc4b9821 100644 --- a/api/core/tools/utils/dataset_retriever/dataset_multi_retriever_tool.py +++ b/api/core/tools/utils/dataset_retriever/dataset_multi_retriever_tool.py @@ -8,6 +8,7 @@ from core.callback_handler.index_tool_callback_handler import DatasetIndexToolCa from core.model_manager import ModelManager from core.model_runtime.entities.model_entities import ModelType from core.rag.datasource.retrieval_service import RetrievalService +from core.rag.entities.citation_metadata import RetrievalSourceMetadata from core.rag.models.document import Document as RagDocument from core.rag.rerank.rerank_model import RerankModelRunner from core.rag.retrieval.retrieval_methods import RetrievalMethod @@ -84,13 +85,17 @@ class DatasetMultiRetrieverTool(DatasetRetrieverBaseTool): document_context_list = [] index_node_ids = [document.metadata["doc_id"] for document in all_documents if document.metadata] - segments = DocumentSegment.query.filter( - DocumentSegment.dataset_id.in_(self.dataset_ids), - DocumentSegment.completed_at.isnot(None), - DocumentSegment.status == "completed", - DocumentSegment.enabled == True, - DocumentSegment.index_node_id.in_(index_node_ids), - ).all() + segments = ( + db.session.query(DocumentSegment) + .filter( + DocumentSegment.dataset_id.in_(self.dataset_ids), + DocumentSegment.completed_at.isnot(None), + DocumentSegment.status == "completed", + DocumentSegment.enabled == True, + DocumentSegment.index_node_id.in_(index_node_ids), + ) + .all() + ) if segments: index_node_id_to_position = {id: position for position, id in enumerate(index_node_ids)} @@ -103,38 +108,42 @@ class DatasetMultiRetrieverTool(DatasetRetrieverBaseTool): else: document_context_list.append(segment.get_sign_content()) if self.return_resource: - context_list = [] + context_list: list[RetrievalSourceMetadata] = [] resource_number = 1 for segment in sorted_segments: - dataset = Dataset.query.filter_by(id=segment.dataset_id).first() - document = Document.query.filter( - Document.id == segment.document_id, - Document.enabled == True, - Document.archived == False, - ).first() + dataset = db.session.query(Dataset).filter_by(id=segment.dataset_id).first() + document = ( + db.session.query(Document) + .filter( + Document.id == segment.document_id, + Document.enabled == True, + Document.archived == False, + ) + .first() + ) if dataset and document: - source = { - "position": resource_number, - "dataset_id": dataset.id, - "dataset_name": dataset.name, - "document_id": document.id, - "document_name": document.name, - "data_source_type": document.data_source_type, - "segment_id": segment.id, - "retriever_from": self.retriever_from, - "score": document_score_list.get(segment.index_node_id, None), - "doc_metadata": document.doc_metadata, - } + source = RetrievalSourceMetadata( + position=resource_number, + dataset_id=dataset.id, + dataset_name=dataset.name, + document_id=document.id, + document_name=document.name, + data_source_type=document.data_source_type, + segment_id=segment.id, + retriever_from=self.retriever_from, + score=document_score_list.get(segment.index_node_id, None), + doc_metadata=document.doc_metadata, + ) if self.retriever_from == "dev": - source["hit_count"] = segment.hit_count - source["word_count"] = segment.word_count - source["segment_position"] = segment.position - source["index_node_hash"] = segment.index_node_hash + source.hit_count = segment.hit_count + source.word_count = segment.word_count + source.segment_position = segment.position + source.index_node_hash = segment.index_node_hash if segment.answer: - source["content"] = f"question:{segment.content} \nanswer:{segment.answer}" + source.content = f"question:{segment.content} \nanswer:{segment.answer}" else: - source["content"] = segment.content + source.content = segment.content context_list.append(source) resource_number += 1 @@ -144,8 +153,6 @@ class DatasetMultiRetrieverTool(DatasetRetrieverBaseTool): return str("\n".join(document_context_list)) return "" - raise RuntimeError("not segments found") - def _retriever( self, flask_app: Flask, diff --git a/api/core/tools/utils/dataset_retriever/dataset_retriever_tool.py b/api/core/tools/utils/dataset_retriever/dataset_retriever_tool.py index f5838c3b76..ff1d9021ce 100644 --- a/api/core/tools/utils/dataset_retriever/dataset_retriever_tool.py +++ b/api/core/tools/utils/dataset_retriever/dataset_retriever_tool.py @@ -1,10 +1,13 @@ -from typing import Any +from typing import Any, Optional, cast from pydantic import BaseModel, Field +from core.app.app_config.entities import DatasetRetrieveConfigEntity, ModelConfig from core.rag.datasource.retrieval_service import RetrievalService +from core.rag.entities.citation_metadata import RetrievalSourceMetadata from core.rag.entities.context_entities import DocumentContext from core.rag.models.document import Document as RetrievalDocument +from core.rag.retrieval.dataset_retrieval import DatasetRetrieval from core.rag.retrieval.retrieval_methods import RetrievalMethod from core.tools.utils.dataset_retriever.dataset_retriever_base_tool import DatasetRetrieverBaseTool from extensions.ext_database import db @@ -12,7 +15,7 @@ from models.dataset import Dataset from models.dataset import Document as DatasetDocument from services.external_knowledge_service import ExternalDatasetService -default_retrieval_model = { +default_retrieval_model: dict[str, Any] = { "search_method": RetrievalMethod.SEMANTIC_SEARCH.value, "reranking_enable": False, "reranking_model": {"reranking_provider_name": "", "reranking_model_name": ""}, @@ -33,6 +36,9 @@ class DatasetRetrieverTool(DatasetRetrieverBaseTool): args_schema: type[BaseModel] = DatasetRetrieverToolInput description: str = "use this to retrieve a dataset. " dataset_id: str + user_id: Optional[str] = None + retrieve_config: DatasetRetrieveConfigEntity + inputs: dict @classmethod def from_dataset(cls, dataset: Dataset, **kwargs): @@ -58,13 +64,29 @@ class DatasetRetrieverTool(DatasetRetrieverBaseTool): return "" for hit_callback in self.hit_callbacks: hit_callback.on_query(query, dataset.id) + dataset_retrieval = DatasetRetrieval() + metadata_filter_document_ids, metadata_condition = dataset_retrieval.get_metadata_filter_condition( + [dataset.id], + query, + self.tenant_id, + self.user_id or "unknown", + cast(str, self.retrieve_config.metadata_filtering_mode), + cast(ModelConfig, self.retrieve_config.metadata_model_config), + self.retrieve_config.metadata_filtering_conditions, + self.inputs, + ) + if metadata_filter_document_ids: + document_ids_filter = metadata_filter_document_ids.get(dataset.id, []) + else: + document_ids_filter = None if dataset.provider == "external": - results = [] + results: list[RetrievalDocument] = [] external_documents = ExternalDatasetService.fetch_external_knowledge_retrieval( tenant_id=dataset.tenant_id, dataset_id=dataset.id, query=query, external_retrieval_parameters=dataset.retrieval_model, + metadata_condition=metadata_condition, ) for external_document in external_documents: document = RetrievalDocument( @@ -79,33 +101,40 @@ class DatasetRetrieverTool(DatasetRetrieverBaseTool): document.metadata["dataset_name"] = dataset.name results.append(document) # deal with external documents - context_list = [] + context_list: list[RetrievalSourceMetadata] = [] for position, item in enumerate(results, start=1): if item.metadata is not None: - source = { - "position": position, - "dataset_id": item.metadata.get("dataset_id"), - "dataset_name": item.metadata.get("dataset_name"), - "document_id": item.metadata.get("document_id") or item.metadata.get("title"), - "document_name": item.metadata.get("title"), - "data_source_type": "external", - "retriever_from": self.retriever_from, - "score": item.metadata.get("score"), - "title": item.metadata.get("title"), - "content": item.page_content, - } + source = RetrievalSourceMetadata( + position=position, + dataset_id=item.metadata.get("dataset_id"), + dataset_name=item.metadata.get("dataset_name"), + document_id=item.metadata.get("document_id") or item.metadata.get("title"), + document_name=item.metadata.get("title"), + data_source_type="external", + retriever_from=self.retriever_from, + score=item.metadata.get("score"), + title=item.metadata.get("title"), + content=item.page_content, + ) context_list.append(source) for hit_callback in self.hit_callbacks: hit_callback.return_retriever_resource_info(context_list) return str("\n".join([item.page_content for item in results])) else: + if metadata_condition and not document_ids_filter: + return "" # get retrieval model , if the model is not setting , using default retrieval_model: dict[str, Any] = dataset.retrieval_model or default_retrieval_model + retrieval_resource_list: list[RetrievalSourceMetadata] = [] if dataset.indexing_technique == "economy": # use keyword table query documents = RetrievalService.retrieve( - retrieval_method="keyword_search", dataset_id=dataset.id, query=query, top_k=self.top_k + retrieval_method="keyword_search", + dataset_id=dataset.id, + query=query, + top_k=self.top_k, + document_ids_filter=document_ids_filter, ) return str("\n".join([document.page_content for document in documents])) else: @@ -124,6 +153,7 @@ class DatasetRetrieverTool(DatasetRetrieverBaseTool): else None, reranking_mode=retrieval_model.get("reranking_mode") or "reranking_model", weights=retrieval_model.get("weights"), + document_ids_filter=document_ids_filter, ) else: documents = [] @@ -134,7 +164,7 @@ class DatasetRetrieverTool(DatasetRetrieverBaseTool): for item in documents: if item.metadata is not None and item.metadata.get("score"): document_score_list[item.metadata["doc_id"]] = item.metadata["score"] - document_context_list = [] + document_context_list: list[DocumentContext] = [] records = RetrievalService.format_retrieval_documents(documents) if records: for record in records: @@ -153,48 +183,52 @@ class DatasetRetrieverTool(DatasetRetrieverBaseTool): score=record.score, ) ) - retrieval_resource_list = [] + if self.return_resource: for record in records: segment = record.segment - dataset = Dataset.query.filter_by(id=segment.dataset_id).first() - document = DatasetDocument.query.filter( - DatasetDocument.id == segment.document_id, - DatasetDocument.enabled == True, - DatasetDocument.archived == False, - ).first() + dataset = db.session.query(Dataset).filter_by(id=segment.dataset_id).first() + document = ( + db.session.query(DatasetDocument) # type: ignore + .filter( + DatasetDocument.id == segment.document_id, + DatasetDocument.enabled == True, + DatasetDocument.archived == False, + ) + .first() + ) if dataset and document: - source = { - "dataset_id": dataset.id, - "dataset_name": dataset.name, - "document_id": document.id, # type: ignore - "document_name": document.name, # type: ignore - "data_source_type": document.data_source_type, # type: ignore - "segment_id": segment.id, - "retriever_from": self.retriever_from, - "score": record.score or 0.0, - "doc_metadata": document.doc_metadata, # type: ignore - } + source = RetrievalSourceMetadata( + dataset_id=dataset.id, + dataset_name=dataset.name, + document_id=document.id, # type: ignore + document_name=document.name, # type: ignore + data_source_type=document.data_source_type, # type: ignore + segment_id=segment.id, + retriever_from=self.retriever_from, + score=record.score or 0.0, + doc_metadata=document.doc_metadata, # type: ignore + ) if self.retriever_from == "dev": - source["hit_count"] = segment.hit_count - source["word_count"] = segment.word_count - source["segment_position"] = segment.position - source["index_node_hash"] = segment.index_node_hash + source.hit_count = segment.hit_count + source.word_count = segment.word_count + source.segment_position = segment.position + source.index_node_hash = segment.index_node_hash if segment.answer: - source["content"] = f"question:{segment.content} \nanswer:{segment.answer}" + source.content = f"question:{segment.content} \nanswer:{segment.answer}" else: - source["content"] = segment.content + source.content = segment.content retrieval_resource_list.append(source) if self.return_resource and retrieval_resource_list: retrieval_resource_list = sorted( retrieval_resource_list, - key=lambda x: x.get("score") or 0.0, + key=lambda x: x.score or 0.0, reverse=True, ) for position, item in enumerate(retrieval_resource_list, start=1): # type: ignore - item["position"] = position # type: ignore + item.position = position # type: ignore for hit_callback in self.hit_callbacks: hit_callback.return_retriever_resource_info(retrieval_resource_list) if document_context_list: diff --git a/api/core/tools/utils/dataset_retriever_tool.py b/api/core/tools/utils/dataset_retriever_tool.py index b73dec4ebc..ec0575f6c3 100644 --- a/api/core/tools/utils/dataset_retriever_tool.py +++ b/api/core/tools/utils/dataset_retriever_tool.py @@ -34,6 +34,8 @@ class DatasetRetrieverTool(Tool): return_resource: bool, invoke_from: InvokeFrom, hit_callback: DatasetIndexToolCallbackHandler, + user_id: str, + inputs: dict, ) -> list["DatasetRetrieverTool"]: """ get dataset tool @@ -57,6 +59,8 @@ class DatasetRetrieverTool(Tool): return_resource=return_resource, invoke_from=invoke_from, hit_callback=hit_callback, + user_id=user_id, + inputs=inputs, ) if retrieval_tools is None or len(retrieval_tools) == 0: return [] diff --git a/api/core/tools/utils/message_transformer.py b/api/core/tools/utils/message_transformer.py index 6fd0c201e3..9998de0465 100644 --- a/api/core/tools/utils/message_transformer.py +++ b/api/core/tools/utils/message_transformer.py @@ -31,15 +31,15 @@ class ToolFileMessageTransformer: # try to download image try: assert isinstance(message.message, ToolInvokeMessage.TextMessage) - - file = ToolFileManager.create_file_by_url( + tool_file_manager = ToolFileManager() + tool_file = tool_file_manager.create_file_by_url( user_id=user_id, tenant_id=tenant_id, file_url=message.message.text, conversation_id=conversation_id, ) - url = f"/files/tools/{file.id}{guess_extension(file.mimetype) or '.png'}" + url = f"/files/tools/{tool_file.id}{guess_extension(tool_file.mimetype) or '.png'}" yield ToolInvokeMessage( type=ToolInvokeMessage.MessageType.IMAGE_LINK, @@ -60,15 +60,15 @@ class ToolFileMessageTransformer: mimetype = meta.get("mime_type", "application/octet-stream") # get filename from meta - filename = meta.get("file_name", None) + filename = meta.get("filename", None) # if message is str, encode it to bytes if not isinstance(message.message, ToolInvokeMessage.BlobMessage): raise ValueError("unexpected message type") - # FIXME: should do a type check here. assert isinstance(message.message.blob, bytes) - file = ToolFileManager.create_file_by_raw( + tool_file_manager = ToolFileManager() + tool_file = tool_file_manager.create_file_by_raw( user_id=user_id, tenant_id=tenant_id, conversation_id=conversation_id, @@ -77,7 +77,7 @@ class ToolFileMessageTransformer: filename=filename, ) - url = cls.get_tool_file_url(tool_file_id=file.id, extension=guess_extension(file.mimetype)) + url = cls.get_tool_file_url(tool_file_id=tool_file.id, extension=guess_extension(tool_file.mimetype)) # check if file is image if "image" in mimetype: diff --git a/api/core/tools/utils/parser.py b/api/core/tools/utils/parser.py index f72291783a..3f844e8234 100644 --- a/api/core/tools/utils/parser.py +++ b/api/core/tools/utils/parser.py @@ -55,6 +55,13 @@ class ApiBasedToolSchemaParser: # convert parameters parameters = [] if "parameters" in interface["operation"]: + for i, parameter in enumerate(interface["operation"]["parameters"]): + if "$ref" in parameter: + root = openapi + reference = parameter["$ref"].split("/")[1:] + for ref in reference: + root = root[ref] + interface["operation"]["parameters"][i] = root for parameter in interface["operation"]["parameters"]: tool_parameter = ToolParameter( name=parameter["name"], diff --git a/api/core/tools/utils/web_reader_tool.py b/api/core/tools/utils/web_reader_tool.py index d42fd99fce..cbd06fc186 100644 --- a/api/core/tools/utils/web_reader_tool.py +++ b/api/core/tools/utils/web_reader_tool.py @@ -1,21 +1,13 @@ -import hashlib -import json import mimetypes -import os import re -import site -import subprocess -import tempfile -import unicodedata -from contextlib import contextmanager -from pathlib import Path -from typing import Any, Literal, Optional, cast +from collections.abc import Sequence +from dataclasses import dataclass +from typing import Any, Optional, cast from urllib.parse import unquote import chardet import cloudscraper # type: ignore -from bs4 import BeautifulSoup, CData, Comment, NavigableString # type: ignore -from regex import regex # type: ignore +from readabilipy import simple_json_from_html_string # type: ignore from core.helper import ssrf_proxy from core.rag.extractor import extract_processor @@ -23,9 +15,7 @@ from core.rag.extractor.extract_processor import ExtractProcessor FULL_TEMPLATE = """ TITLE: {title} -AUTHORS: {authors} -PUBLISH DATE: {publish_date} -TOP_IMAGE_URL: {top_image} +AUTHOR: {author} TEXT: {text} @@ -73,8 +63,8 @@ def get_url(url: str, user_agent: Optional[str] = None) -> str: response = ssrf_proxy.get(url, headers=headers, follow_redirects=True, timeout=(120, 300)) elif response.status_code == 403: scraper = cloudscraper.create_scraper() - scraper.perform_request = ssrf_proxy.make_request - response = scraper.get(url, headers=headers, follow_redirects=True, timeout=(120, 300)) + scraper.perform_request = ssrf_proxy.make_request # type: ignore + response = scraper.get(url, headers=headers, follow_redirects=True, timeout=(120, 300)) # type: ignore if response.status_code != 200: return "URL returned status code {}.".format(response.status_code) @@ -90,273 +80,36 @@ def get_url(url: str, user_agent: Optional[str] = None) -> str: else: content = response.text - a = extract_using_readabilipy(content) + article = extract_using_readabilipy(content) - if not a["plain_text"] or not a["plain_text"].strip(): + if not article.text: return "" res = FULL_TEMPLATE.format( - title=a["title"], - authors=a["byline"], - publish_date=a["date"], - top_image="", - text=a["plain_text"] or "", + title=article.title, + author=article.auther, + text=article.text, ) return res -def extract_using_readabilipy(html): - with tempfile.NamedTemporaryFile(delete=False, mode="w+") as f_html: - f_html.write(html) - f_html.close() - html_path = f_html.name +@dataclass +class Article: + title: str + auther: str + text: Sequence[dict] - # Call Mozilla's Readability.js Readability.parse() function via node, writing output to a temporary file - article_json_path = html_path + ".json" - jsdir = os.path.join(find_module_path("readabilipy"), "javascript") - with chdir(jsdir): - subprocess.check_call(["node", "ExtractArticle.js", "-i", html_path, "-o", article_json_path]) - # Read output of call to Readability.parse() from JSON file and return as Python dictionary - input_json = json.loads(Path(article_json_path).read_text(encoding="utf-8")) - - # Deleting files after processing - os.unlink(article_json_path) - os.unlink(html_path) - - article_json: dict[str, Any] = { - "title": None, - "byline": None, - "date": None, - "content": None, - "plain_content": None, - "plain_text": None, - } - # Populate article fields from readability fields where present - if input_json: - if input_json.get("title"): - article_json["title"] = input_json["title"] - if input_json.get("byline"): - article_json["byline"] = input_json["byline"] - if input_json.get("date"): - article_json["date"] = input_json["date"] - if input_json.get("content"): - article_json["content"] = input_json["content"] - article_json["plain_content"] = plain_content(article_json["content"], False, False) - article_json["plain_text"] = extract_text_blocks_as_plain_text(article_json["plain_content"]) - if input_json.get("textContent"): - article_json["plain_text"] = input_json["textContent"] - article_json["plain_text"] = re.sub(r"\n\s*\n", "\n", article_json["plain_text"]) - - return article_json - - -def find_module_path(module_name): - for package_path in site.getsitepackages(): - potential_path = os.path.join(package_path, module_name) - if os.path.exists(potential_path): - return potential_path - - return None - - -@contextmanager -def chdir(path): - """Change directory in context and return to original on exit""" - # From https://stackoverflow.com/a/37996581, couldn't find a built-in - original_path = os.getcwd() - os.chdir(path) - try: - yield - finally: - os.chdir(original_path) - - -def extract_text_blocks_as_plain_text(paragraph_html): - # Load article as DOM - soup = BeautifulSoup(paragraph_html, "html.parser") - # Select all lists - list_elements = soup.find_all(["ul", "ol"]) - # Prefix text in all list items with "* " and make lists paragraphs - for list_element in list_elements: - plain_items = "".join( - list(filter(None, [plain_text_leaf_node(li)["text"] for li in list_element.find_all("li")])) - ) - list_element.string = plain_items - list_element.name = "p" - # Select all text blocks - text_blocks = [s.parent for s in soup.find_all(string=True)] - text_blocks = [plain_text_leaf_node(block) for block in text_blocks] - # Drop empty paragraphs - text_blocks = list(filter(lambda p: p["text"] is not None, text_blocks)) - return text_blocks - - -def plain_text_leaf_node(element): - # Extract all text, stripped of any child HTML elements and normalize it - plain_text = normalize_text(element.get_text()) - if plain_text != "" and element.name == "li": - plain_text = "* {}, ".format(plain_text) - if plain_text == "": - plain_text = None - if "data-node-index" in element.attrs: - plain = {"node_index": element["data-node-index"], "text": plain_text} - else: - plain = {"text": plain_text} - return plain - - -def plain_content(readability_content, content_digests, node_indexes): - # Load article as DOM - soup = BeautifulSoup(readability_content, "html.parser") - # Make all elements plain - elements = plain_elements(soup.contents, content_digests, node_indexes) - if node_indexes: - # Add node index attributes to nodes - elements = [add_node_indexes(element) for element in elements] - # Replace article contents with plain elements - soup.contents = elements - return str(soup) - - -def plain_elements(elements, content_digests, node_indexes): - # Get plain content versions of all elements - elements = [plain_element(element, content_digests, node_indexes) for element in elements] - if content_digests: - # Add content digest attribute to nodes - elements = [add_content_digest(element) for element in elements] - return elements - - -def plain_element(element, content_digests, node_indexes): - # For lists, we make each item plain text - if is_leaf(element): - # For leaf node elements, extract the text content, discarding any HTML tags - # 1. Get element contents as text - plain_text = element.get_text() - # 2. Normalize the extracted text string to a canonical representation - plain_text = normalize_text(plain_text) - # 3. Update element content to be plain text - element.string = plain_text - elif is_text(element): - if is_non_printing(element): - # The simplified HTML may have come from Readability.js so might - # have non-printing text (e.g. Comment or CData). In this case, we - # keep the structure, but ensure that the string is empty. - element = type(element)("") - else: - plain_text = element.string - plain_text = normalize_text(plain_text) - element = type(element)(plain_text) - else: - # If not a leaf node or leaf type call recursively on child nodes, replacing - element.contents = plain_elements(element.contents, content_digests, node_indexes) - return element - - -def add_node_indexes(element, node_index="0"): - # Can't add attributes to string types - if is_text(element): - return element - # Add index to current element - element["data-node-index"] = node_index - # Add index to child elements - for local_idx, child in enumerate([c for c in element.contents if not is_text(c)], start=1): - # Can't add attributes to leaf string types - child_index = "{stem}.{local}".format(stem=node_index, local=local_idx) - add_node_indexes(child, node_index=child_index) - return element - - -def normalize_text(text): - """Normalize unicode and whitespace.""" - # Normalize unicode first to try and standardize whitespace characters as much as possible before normalizing them - text = strip_control_characters(text) - text = normalize_unicode(text) - text = normalize_whitespace(text) - return text - - -def strip_control_characters(text): - """Strip out unicode control characters which might break the parsing.""" - # Unicode control characters - # [Cc]: Other, Control [includes new lines] - # [Cf]: Other, Format - # [Cn]: Other, Not Assigned - # [Co]: Other, Private Use - # [Cs]: Other, Surrogate - control_chars = {"Cc", "Cf", "Cn", "Co", "Cs"} - retained_chars = ["\t", "\n", "\r", "\f"] - - # Remove non-printing control characters - return "".join( - [ - "" if (unicodedata.category(char) in control_chars) and (char not in retained_chars) else char - for char in text - ] +def extract_using_readabilipy(html: str): + json_article: dict[str, Any] = simple_json_from_html_string(html, use_readability=True) + article = Article( + title=json_article.get("title") or "", + auther=json_article.get("byline") or "", + text=json_article.get("plain_text") or [], ) - -def normalize_unicode(text): - """Normalize unicode such that things that are visually equivalent map to the same unicode string where possible.""" - normal_form: Literal["NFC", "NFD", "NFKC", "NFKD"] = "NFKC" - text = unicodedata.normalize(normal_form, text) - return text - - -def normalize_whitespace(text): - """Replace runs of whitespace characters with a single space as this is what happens when HTML text is displayed.""" - text = regex.sub(r"\s+", " ", text) - # Remove leading and trailing whitespace - text = text.strip() - return text - - -def is_leaf(element): - return element.name in {"p", "li"} - - -def is_text(element): - return isinstance(element, NavigableString) - - -def is_non_printing(element): - return any(isinstance(element, _e) for _e in [Comment, CData]) - - -def add_content_digest(element): - if not is_text(element): - element["data-content-digest"] = content_digest(element) - return element - - -def content_digest(element): - digest: Any - if is_text(element): - # Hash - trimmed_string = element.string.strip() - if trimmed_string == "": - digest = "" - else: - digest = hashlib.sha256(trimmed_string.encode("utf-8")).hexdigest() - else: - contents = element.contents - num_contents = len(contents) - if num_contents == 0: - # No hash when no child elements exist - digest = "" - elif num_contents == 1: - # If single child, use digest of child - digest = content_digest(contents[0]) - else: - # Build content digest from the "non-empty" digests of child nodes - digest = hashlib.sha256() - child_digests = list(filter(lambda x: x != "", [content_digest(content) for content in contents])) - for child in child_digests: - digest.update(child.encode("utf-8")) - digest = digest.hexdigest() - return digest + return article def get_image_upload_file_ids(content): diff --git a/api/core/tools/workflow_as_tool/tool.py b/api/core/tools/workflow_as_tool/tool.py index 241b4a94de..57c93d1d45 100644 --- a/api/core/tools/workflow_as_tool/tool.py +++ b/api/core/tools/workflow_as_tool/tool.py @@ -1,7 +1,9 @@ import json import logging from collections.abc import Generator -from typing import Any, Optional, Union, cast +from typing import Any, Optional, cast + +from flask_login import current_user from core.file import FILE_MODEL_IDENTITY, File, FileTransferMethod from core.tools.__base.tool import Tool @@ -87,7 +89,7 @@ class WorkflowTool(Tool): result = generator.generate( app_model=app, workflow=workflow, - user=self._get_user(user_id), + user=cast("Account | EndUser", current_user), args={"inputs": tool_parameters, "files": files}, invoke_from=self.runtime.invoke_from, streaming=False, @@ -111,20 +113,6 @@ class WorkflowTool(Tool): yield self.create_text_message(json.dumps(outputs, ensure_ascii=False)) yield self.create_json_message(outputs) - def _get_user(self, user_id: str) -> Union[EndUser, Account]: - """ - get the user by user id - """ - - user = db.session.query(EndUser).filter(EndUser.id == user_id).first() - if not user: - user = db.session.query(Account).filter(Account.id == user_id).first() - - if not user: - raise ValueError("user not found") - - return user - def fork_tool_runtime(self, runtime: ToolRuntime) -> "WorkflowTool": """ fork a new tool with metadata diff --git a/api/core/variables/consts.py b/api/core/variables/consts.py new file mode 100644 index 0000000000..03b277d619 --- /dev/null +++ b/api/core/variables/consts.py @@ -0,0 +1,7 @@ +# The minimal selector length for valid variables. +# +# The first element of the selector is the node id, and the second element is the variable name. +# +# If the selector length is more than 2, the remaining parts are the keys / indexes paths used +# to extract part of the variable value. +MIN_SELECTORS_LENGTH = 2 diff --git a/api/core/variables/utils.py b/api/core/variables/utils.py new file mode 100644 index 0000000000..e5d222af7d --- /dev/null +++ b/api/core/variables/utils.py @@ -0,0 +1,8 @@ +from collections.abc import Iterable, Sequence + + +def to_selector(node_id: str, name: str, paths: Iterable[str] = ()) -> Sequence[str]: + selectors = [node_id, name] + if paths: + selectors.extend(paths) + return selectors diff --git a/api/core/variables/variables.py b/api/core/variables/variables.py index c32815b24d..b650b1682e 100644 --- a/api/core/variables/variables.py +++ b/api/core/variables/variables.py @@ -30,7 +30,7 @@ class Variable(Segment): """ id: str = Field( - default=lambda _: str(uuid4()), + default_factory=lambda: str(uuid4()), description="Unique identity for variable.", ) name: str diff --git a/api/core/workflow/entities/node_entities.py b/api/core/workflow/entities/node_entities.py index 82fd6cdc30..687ec8e47c 100644 --- a/api/core/workflow/entities/node_entities.py +++ b/api/core/workflow/entities/node_entities.py @@ -1,36 +1,10 @@ from collections.abc import Mapping -from enum import StrEnum from typing import Any, Optional from pydantic import BaseModel from core.model_runtime.entities.llm_entities import LLMUsage -from models.workflow import WorkflowNodeExecutionStatus - - -class NodeRunMetadataKey(StrEnum): - """ - Node Run Metadata Key. - """ - - TOTAL_TOKENS = "total_tokens" - TOTAL_PRICE = "total_price" - CURRENCY = "currency" - TOOL_INFO = "tool_info" - AGENT_LOG = "agent_log" - ITERATION_ID = "iteration_id" - ITERATION_INDEX = "iteration_index" - LOOP_ID = "loop_id" - LOOP_INDEX = "loop_index" - PARALLEL_ID = "parallel_id" - PARALLEL_START_NODE_ID = "parallel_start_node_id" - PARENT_PARALLEL_ID = "parent_parallel_id" - PARENT_PARALLEL_START_NODE_ID = "parent_parallel_start_node_id" - PARALLEL_MODE_RUN_ID = "parallel_mode_run_id" - ITERATION_DURATION_MAP = "iteration_duration_map" # single iteration duration if iteration node runs - LOOP_DURATION_MAP = "loop_duration_map" # single loop duration if loop node runs - ERROR_STRATEGY = "error_strategy" # node in continue on error mode return the field - LOOP_VARIABLE_MAP = "loop_variable_map" # single loop variable output +from core.workflow.entities.workflow_node_execution import WorkflowNodeExecutionMetadataKey, WorkflowNodeExecutionStatus class NodeRunResult(BaseModel): @@ -43,7 +17,7 @@ class NodeRunResult(BaseModel): inputs: Optional[Mapping[str, Any]] = None # node inputs process_data: Optional[Mapping[str, Any]] = None # process data outputs: Optional[Mapping[str, Any]] = None # node outputs - metadata: Optional[Mapping[NodeRunMetadataKey, Any]] = None # node metadata + metadata: Optional[Mapping[WorkflowNodeExecutionMetadataKey, Any]] = None # node metadata llm_usage: Optional[LLMUsage] = None # llm usage edge_source_handle: Optional[str] = None # source handle id of node with multiple branches diff --git a/api/core/workflow/entities/workflow_execution.py b/api/core/workflow/entities/workflow_execution.py new file mode 100644 index 0000000000..781be4b3c6 --- /dev/null +++ b/api/core/workflow/entities/workflow_execution.py @@ -0,0 +1,87 @@ +""" +Domain entities for workflow execution. + +Models are independent of the storage mechanism and don't contain +implementation details like tenant_id, app_id, etc. +""" + +from collections.abc import Mapping +from datetime import UTC, datetime +from enum import StrEnum +from typing import Any, Optional + +from pydantic import BaseModel, Field + + +class WorkflowType(StrEnum): + """ + Workflow Type Enum for domain layer + """ + + WORKFLOW = "workflow" + CHAT = "chat" + + +class WorkflowExecutionStatus(StrEnum): + RUNNING = "running" + SUCCEEDED = "succeeded" + FAILED = "failed" + STOPPED = "stopped" + PARTIAL_SUCCEEDED = "partial-succeeded" + + +class WorkflowExecution(BaseModel): + """ + Domain model for workflow execution based on WorkflowRun but without + user, tenant, and app attributes. + """ + + id_: str = Field(...) + workflow_id: str = Field(...) + workflow_version: str = Field(...) + workflow_type: WorkflowType = Field(...) + graph: Mapping[str, Any] = Field(...) + + inputs: Mapping[str, Any] = Field(...) + outputs: Optional[Mapping[str, Any]] = None + + status: WorkflowExecutionStatus = WorkflowExecutionStatus.RUNNING + error_message: str = Field(default="") + total_tokens: int = Field(default=0) + total_steps: int = Field(default=0) + exceptions_count: int = Field(default=0) + + started_at: datetime = Field(...) + finished_at: Optional[datetime] = None + + @property + def elapsed_time(self) -> float: + """ + Calculate elapsed time in seconds. + If workflow is not finished, use current time. + """ + end_time = self.finished_at or datetime.now(UTC).replace(tzinfo=None) + return (end_time - self.started_at).total_seconds() + + @classmethod + def new( + cls, + *, + id_: str, + workflow_id: str, + workflow_type: WorkflowType, + workflow_version: str, + graph: Mapping[str, Any], + inputs: Mapping[str, Any], + started_at: datetime, + ) -> "WorkflowExecution": + return WorkflowExecution( + id_=id_, + workflow_id=workflow_id, + workflow_type=workflow_type, + workflow_version=workflow_version, + graph=graph, + inputs=inputs, + status=WorkflowExecutionStatus.RUNNING, + started_at=started_at, + ) diff --git a/api/core/workflow/entities/workflow_node_execution.py b/api/core/workflow/entities/workflow_node_execution.py new file mode 100644 index 0000000000..773f5b777b --- /dev/null +++ b/api/core/workflow/entities/workflow_node_execution.py @@ -0,0 +1,122 @@ +""" +Domain entities for workflow node execution. + +This module contains the domain model for workflow node execution, which is used +by the core workflow module. These models are independent of the storage mechanism +and don't contain implementation details like tenant_id, app_id, etc. +""" + +from collections.abc import Mapping +from datetime import datetime +from enum import StrEnum +from typing import Any, Optional + +from pydantic import BaseModel, Field + +from core.workflow.nodes.enums import NodeType + + +class WorkflowNodeExecutionMetadataKey(StrEnum): + """ + Node Run Metadata Key. + """ + + TOTAL_TOKENS = "total_tokens" + TOTAL_PRICE = "total_price" + CURRENCY = "currency" + TOOL_INFO = "tool_info" + AGENT_LOG = "agent_log" + ITERATION_ID = "iteration_id" + ITERATION_INDEX = "iteration_index" + LOOP_ID = "loop_id" + LOOP_INDEX = "loop_index" + PARALLEL_ID = "parallel_id" + PARALLEL_START_NODE_ID = "parallel_start_node_id" + PARENT_PARALLEL_ID = "parent_parallel_id" + PARENT_PARALLEL_START_NODE_ID = "parent_parallel_start_node_id" + PARALLEL_MODE_RUN_ID = "parallel_mode_run_id" + ITERATION_DURATION_MAP = "iteration_duration_map" # single iteration duration if iteration node runs + LOOP_DURATION_MAP = "loop_duration_map" # single loop duration if loop node runs + ERROR_STRATEGY = "error_strategy" # node in continue on error mode return the field + LOOP_VARIABLE_MAP = "loop_variable_map" # single loop variable output + + +class WorkflowNodeExecutionStatus(StrEnum): + """ + Node Execution Status Enum. + """ + + RUNNING = "running" + SUCCEEDED = "succeeded" + FAILED = "failed" + EXCEPTION = "exception" + RETRY = "retry" + + +class WorkflowNodeExecution(BaseModel): + """ + Domain model for workflow node execution. + + This model represents the core business entity of a node execution, + without implementation details like tenant_id, app_id, etc. + + Note: User/context-specific fields (triggered_from, created_by, created_by_role) + have been moved to the repository implementation to keep the domain model clean. + These fields are still accepted in the constructor for backward compatibility, + but they are not stored in the model. + """ + + # Core identification fields + id: str # Unique identifier for this execution record + node_execution_id: Optional[str] = None # Optional secondary ID for cross-referencing + workflow_id: str # ID of the workflow this node belongs to + workflow_execution_id: Optional[str] = None # ID of the specific workflow run (null for single-step debugging) + + # Execution positioning and flow + index: int # Sequence number for ordering in trace visualization + predecessor_node_id: Optional[str] = None # ID of the node that executed before this one + node_id: str # ID of the node being executed + node_type: NodeType # Type of node (e.g., start, llm, knowledge) + title: str # Display title of the node + + # Execution data + inputs: Optional[Mapping[str, Any]] = None # Input variables used by this node + process_data: Optional[Mapping[str, Any]] = None # Intermediate processing data + outputs: Optional[Mapping[str, Any]] = None # Output variables produced by this node + + # Execution state + status: WorkflowNodeExecutionStatus = WorkflowNodeExecutionStatus.RUNNING # Current execution status + error: Optional[str] = None # Error message if execution failed + elapsed_time: float = Field(default=0.0) # Time taken for execution in seconds + + # Additional metadata + metadata: Optional[Mapping[WorkflowNodeExecutionMetadataKey, Any]] = None # Execution metadata (tokens, cost, etc.) + + # Timing information + created_at: datetime # When execution started + finished_at: Optional[datetime] = None # When execution completed + + def update_from_mapping( + self, + inputs: Optional[Mapping[str, Any]] = None, + process_data: Optional[Mapping[str, Any]] = None, + outputs: Optional[Mapping[str, Any]] = None, + metadata: Optional[Mapping[WorkflowNodeExecutionMetadataKey, Any]] = None, + ) -> None: + """ + Update the model from mappings. + + Args: + inputs: The inputs to update + process_data: The process data to update + outputs: The outputs to update + metadata: The metadata to update + """ + if inputs is not None: + self.inputs = dict(inputs) + if process_data is not None: + self.process_data = dict(process_data) + if outputs is not None: + self.outputs = dict(outputs) + if metadata is not None: + self.metadata = dict(metadata) diff --git a/api/core/workflow/enums.py b/api/core/workflow/enums.py index 9642efa1a5..b52a2b0e6e 100644 --- a/api/core/workflow/enums.py +++ b/api/core/workflow/enums.py @@ -13,4 +13,4 @@ class SystemVariableKey(StrEnum): DIALOGUE_COUNT = "dialogue_count" APP_ID = "app_id" WORKFLOW_ID = "workflow_id" - WORKFLOW_RUN_ID = "workflow_run_id" + WORKFLOW_EXECUTION_ID = "workflow_run_id" diff --git a/api/core/workflow/graph_engine/entities/event.py b/api/core/workflow/graph_engine/entities/event.py index 689a07c4f6..9a4939502e 100644 --- a/api/core/workflow/graph_engine/entities/event.py +++ b/api/core/workflow/graph_engine/entities/event.py @@ -1,9 +1,10 @@ -from collections.abc import Mapping +from collections.abc import Mapping, Sequence from datetime import datetime from typing import Any, Optional from pydantic import BaseModel, Field +from core.rag.entities.citation_metadata import RetrievalSourceMetadata from core.workflow.entities.node_entities import AgentNodeStrategyInit from core.workflow.graph_engine.entities.runtime_route_state import RouteNodeState from core.workflow.nodes import NodeType @@ -82,7 +83,7 @@ class NodeRunStreamChunkEvent(BaseNodeEvent): class NodeRunRetrieverResourceEvent(BaseNodeEvent): - retriever_resources: list[dict] = Field(..., description="retriever resources") + retriever_resources: Sequence[RetrievalSourceMetadata] = Field(..., description="retriever resources") context: str = Field(..., description="context") diff --git a/api/core/workflow/graph_engine/entities/graph.py b/api/core/workflow/graph_engine/entities/graph.py index 5c672c985b..8e5b1e7142 100644 --- a/api/core/workflow/graph_engine/entities/graph.py +++ b/api/core/workflow/graph_engine/entities/graph.py @@ -36,7 +36,7 @@ class Graph(BaseModel): root_node_id: str = Field(..., description="root node id of the graph") node_ids: list[str] = Field(default_factory=list, description="graph node ids") node_id_config_mapping: dict[str, dict] = Field( - default_factory=list, description="node configs mapping (node id: node config)" + default_factory=dict, description="node configs mapping (node id: node config)" ) edge_mapping: dict[str, list[GraphEdge]] = Field( default_factory=dict, description="graph edge mapping (source node id: edges)" diff --git a/api/core/workflow/graph_engine/entities/runtime_route_state.py b/api/core/workflow/graph_engine/entities/runtime_route_state.py index 7683dcc9dc..f2d9c98936 100644 --- a/api/core/workflow/graph_engine/entities/runtime_route_state.py +++ b/api/core/workflow/graph_engine/entities/runtime_route_state.py @@ -6,7 +6,7 @@ from typing import Optional from pydantic import BaseModel, Field from core.workflow.entities.node_entities import NodeRunResult -from models.workflow import WorkflowNodeExecutionStatus +from core.workflow.entities.workflow_node_execution import WorkflowNodeExecutionStatus class RouteNodeState(BaseModel): diff --git a/api/core/workflow/graph_engine/graph_engine.py b/api/core/workflow/graph_engine/graph_engine.py index 36273d8ec1..363b2ee920 100644 --- a/api/core/workflow/graph_engine/graph_engine.py +++ b/api/core/workflow/graph_engine/graph_engine.py @@ -9,13 +9,14 @@ from copy import copy, deepcopy from datetime import UTC, datetime from typing import Any, Optional, cast -from flask import Flask, current_app +from flask import Flask, current_app, has_request_context from configs import dify_config from core.app.apps.base_app_queue_manager import GenerateTaskStoppedError from core.app.entities.app_invoke_entities import InvokeFrom -from core.workflow.entities.node_entities import AgentNodeStrategyInit, NodeRunMetadataKey, NodeRunResult +from core.workflow.entities.node_entities import AgentNodeStrategyInit, NodeRunResult from core.workflow.entities.variable_pool import VariablePool, VariableValue +from core.workflow.entities.workflow_node_execution import WorkflowNodeExecutionMetadataKey, WorkflowNodeExecutionStatus from core.workflow.graph_engine.condition_handlers.condition_manager import ConditionManager from core.workflow.graph_engine.entities.event import ( BaseAgentEvent, @@ -52,9 +53,8 @@ from core.workflow.nodes.end.end_stream_processor import EndStreamProcessor from core.workflow.nodes.enums import ErrorStrategy, FailBranchSourceHandle from core.workflow.nodes.event import RunCompletedEvent, RunRetrieverResourceEvent, RunStreamChunkEvent from core.workflow.nodes.node_mapping import NODE_TYPE_CLASSES_MAPPING -from extensions.ext_database import db from models.enums import UserFrom -from models.workflow import WorkflowNodeExecutionStatus, WorkflowType +from models.workflow import WorkflowType logger = logging.getLogger(__name__) @@ -540,8 +540,21 @@ class GraphEngine: for var, val in context.items(): var.set(val) + # FIXME(-LAN-): Save current user before entering new app context + from flask import g + + saved_user = None + if has_request_context() and hasattr(g, "_login_user"): + saved_user = g._login_user + with flask_app.app_context(): try: + # Restore user in new app context + if saved_user is not None: + from flask import g + + g._login_user = saved_user + q.put( ParallelBranchRunStartedEvent( parallel_id=parallel_id, @@ -593,8 +606,6 @@ class GraphEngine: error=str(e), ) ) - finally: - db.session.remove() def _run_node( self, @@ -632,7 +643,6 @@ class GraphEngine: agent_strategy=agent_strategy, ) - db.session.close() max_retries = node_instance.node_data.retry_config.max_retries retry_interval = node_instance.node_data.retry_config.retry_interval_seconds retries = 0 @@ -746,10 +756,12 @@ class GraphEngine: and node_instance.node_data.error_strategy is ErrorStrategy.FAIL_BRANCH ): run_result.edge_source_handle = FailBranchSourceHandle.SUCCESS - if run_result.metadata and run_result.metadata.get(NodeRunMetadataKey.TOTAL_TOKENS): + if run_result.metadata and run_result.metadata.get( + WorkflowNodeExecutionMetadataKey.TOTAL_TOKENS + ): # plus state total_tokens self.graph_runtime_state.total_tokens += int( - run_result.metadata.get(NodeRunMetadataKey.TOTAL_TOKENS) # type: ignore[arg-type] + run_result.metadata.get(WorkflowNodeExecutionMetadataKey.TOTAL_TOKENS) # type: ignore[arg-type] ) if run_result.llm_usage: @@ -772,13 +784,17 @@ class GraphEngine: if parallel_id and parallel_start_node_id: metadata_dict = dict(run_result.metadata) - metadata_dict[NodeRunMetadataKey.PARALLEL_ID] = parallel_id - metadata_dict[NodeRunMetadataKey.PARALLEL_START_NODE_ID] = parallel_start_node_id + metadata_dict[WorkflowNodeExecutionMetadataKey.PARALLEL_ID] = parallel_id + metadata_dict[WorkflowNodeExecutionMetadataKey.PARALLEL_START_NODE_ID] = ( + parallel_start_node_id + ) if parent_parallel_id and parent_parallel_start_node_id: - metadata_dict[NodeRunMetadataKey.PARENT_PARALLEL_ID] = parent_parallel_id - metadata_dict[NodeRunMetadataKey.PARENT_PARALLEL_START_NODE_ID] = ( - parent_parallel_start_node_id + metadata_dict[WorkflowNodeExecutionMetadataKey.PARENT_PARALLEL_ID] = ( + parent_parallel_id ) + metadata_dict[ + WorkflowNodeExecutionMetadataKey.PARENT_PARALLEL_START_NODE_ID + ] = parent_parallel_start_node_id run_result.metadata = metadata_dict yield NodeRunSucceededEvent( @@ -843,8 +859,6 @@ class GraphEngine: except Exception as e: logger.exception(f"Node {node_instance.node_data.title} run failed") raise e - finally: - db.session.close() def _append_variables_recursively(self, node_id: str, variable_key_list: list[str], variable_value: VariableValue): """ @@ -910,7 +924,7 @@ class GraphEngine: "error": error_result.error, "inputs": error_result.inputs, "metadata": { - NodeRunMetadataKey.ERROR_STRATEGY: node_instance.node_data.error_strategy, + WorkflowNodeExecutionMetadataKey.ERROR_STRATEGY: node_instance.node_data.error_strategy, }, } diff --git a/api/core/workflow/nodes/agent/agent_node.py b/api/core/workflow/nodes/agent/agent_node.py index da40cbcdea..faa8f90bea 100644 --- a/api/core/workflow/nodes/agent/agent_node.py +++ b/api/core/workflow/nodes/agent/agent_node.py @@ -2,19 +2,23 @@ import json from collections.abc import Generator, Mapping, Sequence from typing import Any, Optional, cast +from sqlalchemy import select +from sqlalchemy.orm import Session + from core.agent.entities import AgentToolEntity from core.agent.plugin_entities import AgentStrategyParameter from core.memory.token_buffer_memory import TokenBufferMemory from core.model_manager import ModelInstance, ModelManager from core.model_runtime.entities.model_entities import AIModelEntity, ModelType -from core.plugin.manager.exc import PluginDaemonClientSideError -from core.plugin.manager.plugin import PluginInstallationManager +from core.plugin.impl.exc import PluginDaemonClientSideError +from core.plugin.impl.plugin import PluginInstaller from core.provider_manager import ProviderManager from core.tools.entities.tool_entities import ToolParameter, ToolProviderType from core.tools.tool_manager import ToolManager from core.variables.segments import StringSegment from core.workflow.entities.node_entities import NodeRunResult from core.workflow.entities.variable_pool import VariablePool +from core.workflow.entities.workflow_node_execution import WorkflowNodeExecutionStatus from core.workflow.enums import SystemVariableKey from core.workflow.nodes.agent.entities import AgentNodeData, AgentOldVersionModelFeatures, ParamsAutoGenerated from core.workflow.nodes.base.entities import BaseNodeData @@ -25,7 +29,6 @@ from core.workflow.utils.variable_template_parser import VariableTemplateParser from extensions.ext_database import db from factories.agent_factory import get_plugin_agent_strategy from models.model import Conversation -from models.workflow import WorkflowNodeExecutionStatus class AgentNode(ToolNode): @@ -297,7 +300,7 @@ class AgentNode(ToolNode): Get agent strategy icon :return: """ - manager = PluginInstallationManager() + manager = PluginInstaller() plugins = manager.list_plugins(self.tenant_id) try: current_plugin = next( @@ -320,15 +323,12 @@ class AgentNode(ToolNode): return None conversation_id = conversation_id_variable.value - # get conversation - conversation = ( - db.session.query(Conversation) - .filter(Conversation.app_id == self.app_id, Conversation.id == conversation_id) - .first() - ) + with Session(db.engine, expire_on_commit=False) as session: + stmt = select(Conversation).where(Conversation.app_id == self.app_id, Conversation.id == conversation_id) + conversation = session.scalar(stmt) - if not conversation: - return None + if not conversation: + return None memory = TokenBufferMemory(conversation=conversation, model_instance=model_instance) @@ -356,7 +356,9 @@ class AgentNode(ToolNode): def _remove_unsupported_model_features_for_old_version(self, model_schema: AIModelEntity) -> AIModelEntity: if model_schema.features: - for feature in model_schema.features: - if feature.value not in AgentOldVersionModelFeatures: + for feature in model_schema.features[:]: # Create a copy to safely modify during iteration + try: + AgentOldVersionModelFeatures(feature.value) # Try to create enum member from value + except ValueError: model_schema.features.remove(feature) return model_schema diff --git a/api/core/workflow/nodes/agent/entities.py b/api/core/workflow/nodes/agent/entities.py index 77e94375bf..075a41fb2f 100644 --- a/api/core/workflow/nodes/agent/entities.py +++ b/api/core/workflow/nodes/agent/entities.py @@ -1,4 +1,4 @@ -from enum import Enum +from enum import Enum, StrEnum from typing import Any, Literal, Union from pydantic import BaseModel @@ -26,7 +26,7 @@ class ParamsAutoGenerated(Enum): OPEN = 1 -class AgentOldVersionModelFeatures(Enum): +class AgentOldVersionModelFeatures(StrEnum): """ Enum class for old SDK version llm feature. """ diff --git a/api/core/workflow/nodes/answer/answer_node.py b/api/core/workflow/nodes/answer/answer_node.py index 520cbdbb60..aa030870e2 100644 --- a/api/core/workflow/nodes/answer/answer_node.py +++ b/api/core/workflow/nodes/answer/answer_node.py @@ -3,6 +3,7 @@ from typing import Any, cast from core.variables import ArrayFileSegment, FileSegment from core.workflow.entities.node_entities import NodeRunResult +from core.workflow.entities.workflow_node_execution import WorkflowNodeExecutionStatus from core.workflow.nodes.answer.answer_stream_generate_router import AnswerStreamGeneratorRouter from core.workflow.nodes.answer.entities import ( AnswerNodeData, @@ -13,7 +14,6 @@ from core.workflow.nodes.answer.entities import ( from core.workflow.nodes.base import BaseNode from core.workflow.nodes.enums import NodeType from core.workflow.utils.variable_template_parser import VariableTemplateParser -from models.workflow import WorkflowNodeExecutionStatus class AnswerNode(BaseNode[AnswerNodeData]): diff --git a/api/core/workflow/nodes/answer/base_stream_processor.py b/api/core/workflow/nodes/answer/base_stream_processor.py index e4f2478890..6671ff0746 100644 --- a/api/core/workflow/nodes/answer/base_stream_processor.py +++ b/api/core/workflow/nodes/answer/base_stream_processor.py @@ -95,7 +95,12 @@ class StreamProcessor(ABC): if node_id not in self.rest_node_ids: return + if node_id in reachable_node_ids: + return + self.rest_node_ids.remove(node_id) + self.rest_node_ids.extend(set(reachable_node_ids) - set(self.rest_node_ids)) + for edge in self.graph.edge_mapping.get(node_id, []): if edge.target_node_id in reachable_node_ids: continue diff --git a/api/core/workflow/nodes/base/node.py b/api/core/workflow/nodes/base/node.py index e566770870..7da0c19740 100644 --- a/api/core/workflow/nodes/base/node.py +++ b/api/core/workflow/nodes/base/node.py @@ -4,9 +4,9 @@ from collections.abc import Generator, Mapping, Sequence from typing import TYPE_CHECKING, Any, Generic, Optional, TypeVar, Union, cast from core.workflow.entities.node_entities import NodeRunResult +from core.workflow.entities.workflow_node_execution import WorkflowNodeExecutionStatus from core.workflow.nodes.enums import CONTINUE_ON_ERROR_NODE_TYPE, RETRY_ON_ERROR_NODE_TYPE, NodeType from core.workflow.nodes.event import NodeEvent, RunCompletedEvent -from models.workflow import WorkflowNodeExecutionStatus from .entities import BaseNodeData diff --git a/api/core/workflow/nodes/code/code_node.py b/api/core/workflow/nodes/code/code_node.py index 3c34c5b4e7..61c08a7d71 100644 --- a/api/core/workflow/nodes/code/code_node.py +++ b/api/core/workflow/nodes/code/code_node.py @@ -8,10 +8,10 @@ from core.helper.code_executor.javascript.javascript_code_provider import Javasc from core.helper.code_executor.python3.python3_code_provider import Python3CodeProvider from core.variables.segments import ArrayFileSegment from core.workflow.entities.node_entities import NodeRunResult +from core.workflow.entities.workflow_node_execution import WorkflowNodeExecutionStatus from core.workflow.nodes.base import BaseNode from core.workflow.nodes.code.entities import CodeNodeData from core.workflow.nodes.enums import NodeType -from models.workflow import WorkflowNodeExecutionStatus from .exc import ( CodeNodeError, @@ -127,7 +127,7 @@ class CodeNode(BaseNode[CodeNodeData]): depth: int = 1, ): if depth > dify_config.CODE_MAX_DEPTH: - raise DepthLimitError(f"Depth limit ${dify_config.CODE_MAX_DEPTH} reached, object too deep.") + raise DepthLimitError(f"Depth limit {dify_config.CODE_MAX_DEPTH} reached, object too deep.") transformed_result: dict[str, Any] = {} if output_schema is None: @@ -167,8 +167,11 @@ class CodeNode(BaseNode[CodeNodeData]): value=value, variable=f"{prefix}.{output_name}[{i}]" if prefix else f"{output_name}[{i}]", ) - elif isinstance(first_element, dict) and all( - value is None or isinstance(value, dict) for value in output_value + elif ( + isinstance(first_element, dict) + and all(value is None or isinstance(value, dict) for value in output_value) + or isinstance(first_element, list) + and all(value is None or isinstance(value, list) for value in output_value) ): for i, value in enumerate(output_value): if value is not None: diff --git a/api/core/workflow/nodes/document_extractor/node.py b/api/core/workflow/nodes/document_extractor/node.py index 960d0c3961..429fed2d04 100644 --- a/api/core/workflow/nodes/document_extractor/node.py +++ b/api/core/workflow/nodes/document_extractor/node.py @@ -7,10 +7,12 @@ import tempfile from collections.abc import Mapping, Sequence from typing import Any, cast +import chardet import docx import pandas as pd import pypandoc # type: ignore import pypdfium2 # type: ignore +import webvtt # type: ignore import yaml # type: ignore from docx.document import Document from docx.oxml.table import CT_Tbl @@ -24,9 +26,9 @@ from core.helper import ssrf_proxy from core.variables import ArrayFileSegment from core.variables.segments import FileSegment from core.workflow.entities.node_entities import NodeRunResult +from core.workflow.entities.workflow_node_execution import WorkflowNodeExecutionStatus from core.workflow.nodes.base import BaseNode from core.workflow.nodes.enums import NodeType -from models.workflow import WorkflowNodeExecutionStatus from .entities import DocumentExtractorNodeData from .exc import DocumentExtractorError, FileDownloadError, TextExtractionError, UnsupportedFileTypeError @@ -132,6 +134,10 @@ def _extract_text_by_mime_type(*, file_content: bytes, mime_type: str) -> str: return _extract_text_from_json(file_content) case "application/x-yaml" | "text/yaml": return _extract_text_from_yaml(file_content) + case "text/vtt": + return _extract_text_from_vtt(file_content) + case "text/properties": + return _extract_text_from_properties(file_content) case _: raise UnsupportedFileTypeError(f"Unsupported MIME type: {mime_type}") @@ -139,7 +145,7 @@ def _extract_text_by_mime_type(*, file_content: bytes, mime_type: str) -> str: def _extract_text_by_file_extension(*, file_content: bytes, file_extension: str) -> str: """Extract text from a file based on its file extension.""" match file_extension: - case ".txt" | ".markdown" | ".md" | ".html" | ".htm" | ".xml" | ".vtt": + case ".txt" | ".markdown" | ".md" | ".html" | ".htm" | ".xml": return _extract_text_from_plain_text(file_content) case ".json": return _extract_text_from_json(file_content) @@ -165,32 +171,74 @@ def _extract_text_by_file_extension(*, file_content: bytes, file_extension: str) return _extract_text_from_eml(file_content) case ".msg": return _extract_text_from_msg(file_content) + case ".vtt": + return _extract_text_from_vtt(file_content) + case ".properties": + return _extract_text_from_properties(file_content) case _: raise UnsupportedFileTypeError(f"Unsupported Extension Type: {file_extension}") def _extract_text_from_plain_text(file_content: bytes) -> str: try: - return file_content.decode("utf-8", "ignore") - except UnicodeDecodeError as e: - raise TextExtractionError("Failed to decode plain text file") from e + # Detect encoding using chardet + result = chardet.detect(file_content) + encoding = result["encoding"] + + # Fallback to utf-8 if detection fails + if not encoding: + encoding = "utf-8" + + return file_content.decode(encoding, errors="ignore") + except (UnicodeDecodeError, LookupError) as e: + # If decoding fails, try with utf-8 as last resort + try: + return file_content.decode("utf-8", errors="ignore") + except UnicodeDecodeError: + raise TextExtractionError(f"Failed to decode plain text file: {e}") from e def _extract_text_from_json(file_content: bytes) -> str: try: - json_data = json.loads(file_content.decode("utf-8", "ignore")) + # Detect encoding using chardet + result = chardet.detect(file_content) + encoding = result["encoding"] + + # Fallback to utf-8 if detection fails + if not encoding: + encoding = "utf-8" + + json_data = json.loads(file_content.decode(encoding, errors="ignore")) return json.dumps(json_data, indent=2, ensure_ascii=False) - except (UnicodeDecodeError, json.JSONDecodeError) as e: - raise TextExtractionError(f"Failed to decode or parse JSON file: {e}") from e + except (UnicodeDecodeError, LookupError, json.JSONDecodeError) as e: + # If decoding fails, try with utf-8 as last resort + try: + json_data = json.loads(file_content.decode("utf-8", errors="ignore")) + return json.dumps(json_data, indent=2, ensure_ascii=False) + except (UnicodeDecodeError, json.JSONDecodeError): + raise TextExtractionError(f"Failed to decode or parse JSON file: {e}") from e def _extract_text_from_yaml(file_content: bytes) -> str: """Extract the content from yaml file""" try: - yaml_data = yaml.safe_load_all(file_content.decode("utf-8", "ignore")) + # Detect encoding using chardet + result = chardet.detect(file_content) + encoding = result["encoding"] + + # Fallback to utf-8 if detection fails + if not encoding: + encoding = "utf-8" + + yaml_data = yaml.safe_load_all(file_content.decode(encoding, errors="ignore")) return cast(str, yaml.dump_all(yaml_data, allow_unicode=True, sort_keys=False)) - except (UnicodeDecodeError, yaml.YAMLError) as e: - raise TextExtractionError(f"Failed to decode or parse YAML file: {e}") from e + except (UnicodeDecodeError, LookupError, yaml.YAMLError) as e: + # If decoding fails, try with utf-8 as last resort + try: + yaml_data = yaml.safe_load_all(file_content.decode("utf-8", errors="ignore")) + return cast(str, yaml.dump_all(yaml_data, allow_unicode=True, sort_keys=False)) + except (UnicodeDecodeError, yaml.YAMLError): + raise TextExtractionError(f"Failed to decode or parse YAML file: {e}") from e def _extract_text_from_pdf(file_content: bytes) -> str: @@ -214,8 +262,8 @@ def _extract_text_from_doc(file_content: bytes) -> str: """ from unstructured.partition.api import partition_via_api - if not (dify_config.UNSTRUCTURED_API_URL and dify_config.UNSTRUCTURED_API_KEY): - raise TextExtractionError("UNSTRUCTURED_API_URL and UNSTRUCTURED_API_KEY must be set") + if not dify_config.UNSTRUCTURED_API_URL: + raise TextExtractionError("UNSTRUCTURED_API_URL must be set") try: with tempfile.NamedTemporaryFile(suffix=".doc", delete=False) as temp_file: @@ -226,7 +274,7 @@ def _extract_text_from_doc(file_content: bytes) -> str: file=file, metadata_filename=temp_file.name, api_url=dify_config.UNSTRUCTURED_API_URL, - api_key=dify_config.UNSTRUCTURED_API_KEY, + api_key=dify_config.UNSTRUCTURED_API_KEY, # type: ignore ) os.unlink(temp_file.name) return "\n".join([getattr(element, "text", "") for element in elements]) @@ -329,26 +377,64 @@ def _extract_text_from_file(file: File): def _extract_text_from_csv(file_content: bytes) -> str: try: - csv_file = io.StringIO(file_content.decode("utf-8", "ignore")) + # Detect encoding using chardet + result = chardet.detect(file_content) + encoding = result["encoding"] + + # Fallback to utf-8 if detection fails + if not encoding: + encoding = "utf-8" + + try: + csv_file = io.StringIO(file_content.decode(encoding, errors="ignore")) + except (UnicodeDecodeError, LookupError): + # If decoding fails, try with utf-8 as last resort + csv_file = io.StringIO(file_content.decode("utf-8", errors="ignore")) + csv_reader = csv.reader(csv_file) rows = list(csv_reader) if not rows: return "" + # Combine multi-line text in the header row + header_row = [cell.replace("\n", " ").replace("\r", "") for cell in rows[0]] + # Create Markdown table - markdown_table = "| " + " | ".join(rows[0]) + " |\n" - markdown_table += "| " + " | ".join(["---"] * len(rows[0])) + " |\n" + markdown_table = "| " + " | ".join(header_row) + " |\n" + markdown_table += "| " + " | ".join(["-" * len(col) for col in rows[0]]) + " |\n" + + # Process each data row and combine multi-line text in each cell for row in rows[1:]: - markdown_table += "| " + " | ".join(row) + " |\n" + processed_row = [cell.replace("\n", " ").replace("\r", "") for cell in row] + markdown_table += "| " + " | ".join(processed_row) + " |\n" - return markdown_table.strip() + return markdown_table except Exception as e: raise TextExtractionError(f"Failed to extract text from CSV: {str(e)}") from e def _extract_text_from_excel(file_content: bytes) -> str: """Extract text from an Excel file using pandas.""" + + def _construct_markdown_table(df: pd.DataFrame) -> str: + """Manually construct a Markdown table from a DataFrame.""" + # Construct the header row + header_row = "| " + " | ".join(df.columns) + " |" + + # Construct the separator row + separator_row = "| " + " | ".join(["-" * len(col) for col in df.columns]) + " |" + + # Construct the data rows + data_rows = [] + for _, row in df.iterrows(): + data_row = "| " + " | ".join(map(str, row)) + " |" + data_rows.append(data_row) + + # Combine all rows into a single string + markdown_table = "\n".join([header_row, separator_row] + data_rows) + return markdown_table + try: excel_file = pd.ExcelFile(io.BytesIO(file_content)) markdown_table = "" @@ -356,8 +442,15 @@ def _extract_text_from_excel(file_content: bytes) -> str: try: df = excel_file.parse(sheet_name=sheet_name) df.dropna(how="all", inplace=True) - # Create Markdown table two times to separate tables with a newline - markdown_table += df.to_markdown(index=False) + "\n\n" + + # Combine multi-line text in each cell into a single line + df = df.applymap(lambda x: " ".join(str(x).splitlines()) if isinstance(x, str) else x) # type: ignore + + # Combine multi-line text in column names into a single line + df.columns = pd.Index([" ".join(col.splitlines()) for col in df.columns]) + + # Manually construct the Markdown table + markdown_table += _construct_markdown_table(df) + "\n\n" except Exception as e: continue return markdown_table @@ -462,3 +555,68 @@ def _extract_text_from_msg(file_content: bytes) -> str: return "\n".join([str(element) for element in elements]) except Exception as e: raise TextExtractionError(f"Failed to extract text from MSG: {str(e)}") from e + + +def _extract_text_from_vtt(vtt_bytes: bytes) -> str: + text = _extract_text_from_plain_text(vtt_bytes) + + # remove bom + text = text.lstrip("\ufeff") + + raw_results = [] + for caption in webvtt.from_string(text): + raw_results.append((caption.voice, caption.text)) + + # Merge consecutive utterances by the same speaker + merged_results = [] + if raw_results: + current_speaker, current_text = raw_results[0] + + for i in range(1, len(raw_results)): + spk, txt = raw_results[i] + if spk == None: + merged_results.append((None, current_text)) + continue + + if spk == current_speaker: + # If it is the same speaker, merge the utterances (joined by space) + current_text += " " + txt + else: + # If the speaker changes, register the utterance so far and move on + merged_results.append((current_speaker, current_text)) + current_speaker, current_text = spk, txt + + # Add the last element + merged_results.append((current_speaker, current_text)) + else: + merged_results = raw_results + + # Return the result in the specified format: Speaker "text" style + formatted = [f'{spk or ""} "{txt}"' for spk, txt in merged_results] + return "\n".join(formatted) + + +def _extract_text_from_properties(file_content: bytes) -> str: + try: + text = _extract_text_from_plain_text(file_content) + lines = text.splitlines() + result = [] + for line in lines: + line = line.strip() + # Preserve comments and empty lines + if not line or line.startswith("#") or line.startswith("!"): + result.append(line) + continue + + if "=" in line: + key, value = line.split("=", 1) + elif ":" in line: + key, value = line.split(":", 1) + else: + key, value = line, "" + + result.append(f"{key.strip()}: {value.strip()}") + + return "\n".join(result) + except Exception as e: + raise TextExtractionError(f"Failed to extract text from properties file: {str(e)}") from e diff --git a/api/core/workflow/nodes/end/end_node.py b/api/core/workflow/nodes/end/end_node.py index 6acc915ab5..0e9756b243 100644 --- a/api/core/workflow/nodes/end/end_node.py +++ b/api/core/workflow/nodes/end/end_node.py @@ -1,8 +1,8 @@ from core.workflow.entities.node_entities import NodeRunResult +from core.workflow.entities.workflow_node_execution import WorkflowNodeExecutionStatus from core.workflow.nodes.base import BaseNode from core.workflow.nodes.end.entities import EndNodeData from core.workflow.nodes.enums import NodeType -from models.workflow import WorkflowNodeExecutionStatus class EndNode(BaseNode[EndNodeData]): diff --git a/api/core/workflow/nodes/event/event.py b/api/core/workflow/nodes/event/event.py index 9fea3fbda3..3ebe80f245 100644 --- a/api/core/workflow/nodes/event/event.py +++ b/api/core/workflow/nodes/event/event.py @@ -1,10 +1,11 @@ +from collections.abc import Sequence from datetime import datetime from pydantic import BaseModel, Field from core.model_runtime.entities.llm_entities import LLMUsage +from core.rag.entities.citation_metadata import RetrievalSourceMetadata from core.workflow.entities.node_entities import NodeRunResult -from models.workflow import WorkflowNodeExecutionStatus class RunCompletedEvent(BaseModel): @@ -17,7 +18,7 @@ class RunStreamChunkEvent(BaseModel): class RunRetrieverResourceEvent(BaseModel): - retriever_resources: list[dict] = Field(..., description="retriever resources") + retriever_resources: Sequence[RetrievalSourceMetadata] = Field(..., description="retriever resources") context: str = Field(..., description="context") @@ -37,11 +38,3 @@ class RunRetryEvent(BaseModel): error: str = Field(..., description="error") retry_index: int = Field(..., description="Retry attempt number") start_at: datetime = Field(..., description="Retry start time") - - -class SingleStepRetryEvent(NodeRunResult): - """Single step retry event""" - - status: WorkflowNodeExecutionStatus = WorkflowNodeExecutionStatus.RETRY - - elapsed_time: float = Field(..., description="elapsed time") diff --git a/api/core/workflow/nodes/http_request/executor.py b/api/core/workflow/nodes/http_request/executor.py index 5d466e645f..2c83b00d4a 100644 --- a/api/core/workflow/nodes/http_request/executor.py +++ b/api/core/workflow/nodes/http_request/executor.py @@ -1,8 +1,9 @@ import base64 import json +import secrets +import string from collections.abc import Mapping from copy import deepcopy -from random import randint from typing import Any, Literal from urllib.parse import urlencode, urlparse @@ -235,6 +236,10 @@ class Executor: files[key].append(file_tuple) # convert files to list for httpx request + # If there are no actual files, we still need to force httpx to use `multipart/form-data`. + # This is achieved by inserting a harmless placeholder file that will be ignored by the server. + if not files: + self.files = [("__multipart_placeholder__", ("", b"", "application/octet-stream"))] if files: self.files = [] for key, file_tuples in files.items(): @@ -262,7 +267,10 @@ class Executor: headers[authorization.config.header] = f"Bearer {authorization.config.api_key}" elif self.auth.config.type == "basic": credentials = authorization.config.api_key - encoded_credentials = base64.b64encode(credentials.encode("utf-8")).decode("utf-8") + if ":" in credentials: + encoded_credentials = base64.b64encode(credentials.encode("utf-8")).decode("utf-8") + else: + encoded_credentials = credentials headers[authorization.config.header] = f"Basic {encoded_credentials}" elif self.auth.config.type == "custom": headers[authorization.config.header] = authorization.config.api_key or "" @@ -370,7 +378,10 @@ class Executor: raw += f"{k}: {v}\r\n" body_string = "" - if self.files: + # Only log actual files if present. + # '__multipart_placeholder__' is inserted to force multipart encoding but is not a real file. + # This prevents logging meaningless placeholder entries. + if self.files and not all(f[0] == "__multipart_placeholder__" for f in self.files): for key, (filename, content, mime_type) in self.files: body_string += f"--{boundary}\r\n" body_string += f'Content-Disposition: form-data; name="{key}"\r\n\r\n' @@ -424,4 +435,4 @@ def _generate_random_string(n: int) -> str: >>> _generate_random_string(5) 'abcde' """ - return "".join([chr(randint(97, 122)) for _ in range(n)]) + return "".join(secrets.choice(string.ascii_lowercase) for _ in range(n)) diff --git a/api/core/workflow/nodes/http_request/node.py b/api/core/workflow/nodes/http_request/node.py index fd2b0f9ae8..6b1ac57c06 100644 --- a/api/core/workflow/nodes/http_request/node.py +++ b/api/core/workflow/nodes/http_request/node.py @@ -8,12 +8,12 @@ from core.file import File, FileTransferMethod from core.tools.tool_file_manager import ToolFileManager from core.workflow.entities.node_entities import NodeRunResult from core.workflow.entities.variable_entities import VariableSelector +from core.workflow.entities.workflow_node_execution import WorkflowNodeExecutionStatus from core.workflow.nodes.base import BaseNode from core.workflow.nodes.enums import NodeType from core.workflow.nodes.http_request.executor import Executor from core.workflow.utils import variable_template_parser from factories import file_factory -from models.workflow import WorkflowNodeExecutionStatus from .entities import ( HttpRequestNodeData, @@ -191,8 +191,9 @@ class HttpRequestNode(BaseNode[HttpRequestNodeData]): mime_type = ( content_disposition_type or content_type or mimetypes.guess_type(filename)[0] or "application/octet-stream" ) + tool_file_manager = ToolFileManager() - tool_file = ToolFileManager.create_file_by_raw( + tool_file = tool_file_manager.create_file_by_raw( user_id=self.user_id, tenant_id=self.tenant_id, conversation_id=None, diff --git a/api/core/workflow/nodes/if_else/if_else_node.py b/api/core/workflow/nodes/if_else/if_else_node.py index cb51b1ddd5..976922f75d 100644 --- a/api/core/workflow/nodes/if_else/if_else_node.py +++ b/api/core/workflow/nodes/if_else/if_else_node.py @@ -4,12 +4,12 @@ from typing_extensions import deprecated from core.workflow.entities.node_entities import NodeRunResult from core.workflow.entities.variable_pool import VariablePool +from core.workflow.entities.workflow_node_execution import WorkflowNodeExecutionStatus from core.workflow.nodes.base import BaseNode from core.workflow.nodes.enums import NodeType from core.workflow.nodes.if_else.entities import IfElseNodeData from core.workflow.utils.condition.entities import Condition from core.workflow.utils.condition.processor import ConditionProcessor -from models.workflow import WorkflowNodeExecutionStatus class IfElseNode(BaseNode[IfElseNodeData]): diff --git a/api/core/workflow/nodes/iteration/iteration_node.py b/api/core/workflow/nodes/iteration/iteration_node.py index a7d0aefc6d..2592823540 100644 --- a/api/core/workflow/nodes/iteration/iteration_node.py +++ b/api/core/workflow/nodes/iteration/iteration_node.py @@ -7,15 +7,15 @@ from datetime import UTC, datetime from queue import Empty, Queue from typing import TYPE_CHECKING, Any, Optional, cast -from flask import Flask, current_app +from flask import Flask, current_app, has_request_context from configs import dify_config from core.variables import ArrayVariable, IntegerVariable, NoneVariable from core.workflow.entities.node_entities import ( - NodeRunMetadataKey, NodeRunResult, ) from core.workflow.entities.variable_pool import VariablePool +from core.workflow.entities.workflow_node_execution import WorkflowNodeExecutionMetadataKey, WorkflowNodeExecutionStatus from core.workflow.graph_engine.entities.event import ( BaseGraphEvent, BaseNodeEvent, @@ -37,7 +37,6 @@ from core.workflow.nodes.base import BaseNode from core.workflow.nodes.enums import NodeType from core.workflow.nodes.event import NodeEvent, RunCompletedEvent from core.workflow.nodes.iteration.entities import ErrorHandleMode, IterationNodeData -from models.workflow import WorkflowNodeExecutionStatus from .exc import ( InvalidIteratorValueError, @@ -249,8 +248,8 @@ class IterationNode(BaseNode[IterationNodeData]): status=WorkflowNodeExecutionStatus.SUCCEEDED, outputs={"output": outputs}, metadata={ - NodeRunMetadataKey.ITERATION_DURATION_MAP: iter_run_map, - NodeRunMetadataKey.TOTAL_TOKENS: graph_engine.graph_runtime_state.total_tokens, + WorkflowNodeExecutionMetadataKey.ITERATION_DURATION_MAP: iter_run_map, + WorkflowNodeExecutionMetadataKey.TOTAL_TOKENS: graph_engine.graph_runtime_state.total_tokens, }, ) ) @@ -353,27 +352,26 @@ class IterationNode(BaseNode[IterationNodeData]): ) -> NodeRunStartedEvent | BaseNodeEvent | InNodeEvent: """ add iteration metadata to event. + ensures iteration context (ID, index/parallel_run_id) is added to metadata, """ if not isinstance(event, BaseNodeEvent): return event if self.node_data.is_parallel and isinstance(event, NodeRunStartedEvent): event.parallel_mode_run_id = parallel_mode_run_id - return event + + iter_metadata = { + WorkflowNodeExecutionMetadataKey.ITERATION_ID: self.node_id, + WorkflowNodeExecutionMetadataKey.ITERATION_INDEX: iter_run_index, + } + if parallel_mode_run_id: + # for parallel, the specific branch ID is more important than the sequential index + iter_metadata[WorkflowNodeExecutionMetadataKey.PARALLEL_MODE_RUN_ID] = parallel_mode_run_id + if event.route_node_state.node_run_result: - metadata = event.route_node_state.node_run_result.metadata - if not metadata: - metadata = {} - if NodeRunMetadataKey.ITERATION_ID not in metadata: - metadata = { - **metadata, - NodeRunMetadataKey.ITERATION_ID: self.node_id, - NodeRunMetadataKey.PARALLEL_MODE_RUN_ID - if self.node_data.is_parallel - else NodeRunMetadataKey.ITERATION_INDEX: parallel_mode_run_id - if self.node_data.is_parallel - else iter_run_index, - } - event.route_node_state.node_run_result.metadata = metadata + current_metadata = event.route_node_state.node_run_result.metadata or {} + if WorkflowNodeExecutionMetadataKey.ITERATION_ID not in current_metadata: + event.route_node_state.node_run_result.metadata = {**current_metadata, **iter_metadata} + return event def _run_single_iter( @@ -587,7 +585,21 @@ class IterationNode(BaseNode[IterationNodeData]): """ for var, val in context.items(): var.set(val) + + # FIXME(-LAN-): Save current user before entering new app context + from flask import g + + saved_user = None + if has_request_context() and hasattr(g, "_login_user"): + saved_user = g._login_user + with flask_app.app_context(): + # Restore user in new app context + if saved_user is not None: + from flask import g + + g._login_user = saved_user + parallel_mode_run_id = uuid.uuid4().hex graph_engine_copy = graph_engine.create_copy() variable_pool_copy = graph_engine_copy.graph_runtime_state.variable_pool diff --git a/api/core/workflow/nodes/iteration/iteration_start_node.py b/api/core/workflow/nodes/iteration/iteration_start_node.py index fe955e47d1..bee481ebdb 100644 --- a/api/core/workflow/nodes/iteration/iteration_start_node.py +++ b/api/core/workflow/nodes/iteration/iteration_start_node.py @@ -1,8 +1,8 @@ from core.workflow.entities.node_entities import NodeRunResult +from core.workflow.entities.workflow_node_execution import WorkflowNodeExecutionStatus from core.workflow.nodes.base import BaseNode from core.workflow.nodes.enums import NodeType from core.workflow.nodes.iteration.entities import IterationStartNodeData -from models.workflow import WorkflowNodeExecutionStatus class IterationStartNode(BaseNode[IterationStartNodeData]): diff --git a/api/core/workflow/nodes/knowledge_retrieval/entities.py b/api/core/workflow/nodes/knowledge_retrieval/entities.py index d2e5a15545..19bdee4fe2 100644 --- a/api/core/workflow/nodes/knowledge_retrieval/entities.py +++ b/api/core/workflow/nodes/knowledge_retrieval/entities.py @@ -132,3 +132,12 @@ class KnowledgeRetrievalNodeData(BaseNodeData): metadata_model_config: Optional[ModelConfig] = None metadata_filtering_conditions: Optional[MetadataFilteringCondition] = None vision: VisionConfig = Field(default_factory=VisionConfig) + + @property + def structured_output_enabled(self) -> bool: + # NOTE(QuantumGhost): Temporary workaround for issue #20725 + # (https://github.com/langgenius/dify/issues/20725). + # + # The proper fix would be to make `KnowledgeRetrievalNode` inherit + # from `BaseNode` instead of `LLMNode`. + return False diff --git a/api/core/workflow/nodes/knowledge_retrieval/knowledge_retrieval_node.py b/api/core/workflow/nodes/knowledge_retrieval/knowledge_retrieval_node.py index 4ec033572c..5cf5848d54 100644 --- a/api/core/workflow/nodes/knowledge_retrieval/knowledge_retrieval_node.py +++ b/api/core/workflow/nodes/knowledge_retrieval/knowledge_retrieval_node.py @@ -6,8 +6,9 @@ from collections import defaultdict from collections.abc import Mapping, Sequence from typing import Any, Optional, cast -from sqlalchemy import Integer, and_, func, or_, text +from sqlalchemy import Float, and_, func, or_, text from sqlalchemy import cast as sqlalchemy_cast +from sqlalchemy.orm import Session from core.app.app_config.entities import DatasetRetrieveConfigEntity from core.app.entities.app_invoke_entities import ModelConfigWithCredentialsEntity @@ -24,6 +25,7 @@ from core.rag.retrieval.dataset_retrieval import DatasetRetrieval from core.rag.retrieval.retrieval_methods import RetrievalMethod from core.variables import StringSegment from core.workflow.entities.node_entities import NodeRunResult +from core.workflow.entities.workflow_node_execution import WorkflowNodeExecutionStatus from core.workflow.nodes.enums import NodeType from core.workflow.nodes.event.event import ModelInvokeCompletedEvent from core.workflow.nodes.knowledge_retrieval.template_prompts import ( @@ -32,16 +34,15 @@ from core.workflow.nodes.knowledge_retrieval.template_prompts import ( METADATA_FILTER_COMPLETION_PROMPT, METADATA_FILTER_SYSTEM_PROMPT, METADATA_FILTER_USER_PROMPT_1, + METADATA_FILTER_USER_PROMPT_2, METADATA_FILTER_USER_PROMPT_3, ) from core.workflow.nodes.llm.entities import LLMNodeChatModelMessage, LLMNodeCompletionModelPromptTemplate from core.workflow.nodes.llm.node import LLMNode -from core.workflow.nodes.question_classifier.template_prompts import QUESTION_CLASSIFIER_USER_PROMPT_2 from extensions.ext_database import db from extensions.ext_redis import redis_client from libs.json_in_md_parser import parse_and_check_json_markdown from models.dataset import Dataset, DatasetMetadata, Document, RateLimitLog -from models.workflow import WorkflowNodeExecutionStatus from services.feature_service import FeatureService from .entities import KnowledgeRetrievalNodeData, ModelConfig @@ -85,30 +86,31 @@ class KnowledgeRetrievalNode(LLMNode): return NodeRunResult( status=WorkflowNodeExecutionStatus.FAILED, inputs=variables, error="Query is required." ) + # TODO(-LAN-): Move this check outside. # check rate limit - if self.tenant_id: - knowledge_rate_limit = FeatureService.get_knowledge_rate_limit(self.tenant_id) - if knowledge_rate_limit.enabled: - current_time = int(time.time() * 1000) - key = f"rate_limit_{self.tenant_id}" - redis_client.zadd(key, {current_time: current_time}) - redis_client.zremrangebyscore(key, 0, current_time - 60000) - request_count = redis_client.zcard(key) - if request_count > knowledge_rate_limit.limit: + knowledge_rate_limit = FeatureService.get_knowledge_rate_limit(self.tenant_id) + if knowledge_rate_limit.enabled: + current_time = int(time.time() * 1000) + key = f"rate_limit_{self.tenant_id}" + redis_client.zadd(key, {current_time: current_time}) + redis_client.zremrangebyscore(key, 0, current_time - 60000) + request_count = redis_client.zcard(key) + if request_count > knowledge_rate_limit.limit: + with Session(db.engine) as session: # add ratelimit record rate_limit_log = RateLimitLog( tenant_id=self.tenant_id, subscription_plan=knowledge_rate_limit.subscription_plan, operation="knowledge", ) - db.session.add(rate_limit_log) - db.session.commit() - return NodeRunResult( - status=WorkflowNodeExecutionStatus.FAILED, - inputs=variables, - error="Sorry, you have reached the knowledge base request rate limit of your subscription.", - error_type="RateLimitExceeded", - ) + session.add(rate_limit_log) + session.commit() + return NodeRunResult( + status=WorkflowNodeExecutionStatus.FAILED, + inputs=variables, + error="Sorry, you have reached the knowledge base request rate limit of your subscription.", + error_type="RateLimitExceeded", + ) # retrieve knowledge try: @@ -173,7 +175,9 @@ class KnowledgeRetrievalNode(LLMNode): dataset_retrieval = DatasetRetrieval() if node_data.retrieval_mode == DatasetRetrieveConfigEntity.RetrieveStrategy.SINGLE.value: # fetch model config - model_instance, model_config = self._fetch_model_config(node_data.single_retrieval_config.model) # type: ignore + if node_data.single_retrieval_config is None: + raise ValueError("single_retrieval_config is required") + model_instance, model_config = self.get_model_config(node_data.single_retrieval_config.model) # check model is support tool calling model_type_instance = model_config.provider_model_bundle.model_type_instance model_type_instance = cast(LargeLanguageModel, model_type_instance) @@ -264,6 +268,7 @@ class KnowledgeRetrievalNode(LLMNode): "data_source_type": "external", "retriever_from": "workflow", "score": item.metadata.get("score"), + "doc_metadata": item.metadata, }, "title": item.metadata.get("title"), "content": item.page_content, @@ -275,12 +280,16 @@ class KnowledgeRetrievalNode(LLMNode): if records: for record in records: segment = record.segment - dataset = Dataset.query.filter_by(id=segment.dataset_id).first() - document = Document.query.filter( - Document.id == segment.document_id, - Document.enabled == True, - Document.archived == False, - ).first() + dataset = db.session.query(Dataset).filter_by(id=segment.dataset_id).first() # type: ignore + document = ( + db.session.query(Document) + .filter( + Document.id == segment.document_id, + Document.enabled == True, + Document.archived == False, + ) + .first() + ) if dataset and document: source = { "metadata": { @@ -289,7 +298,7 @@ class KnowledgeRetrievalNode(LLMNode): "dataset_name": dataset.name, "document_id": document.id, "document_name": document.name, - "document_data_source_type": document.data_source_type, + "data_source_type": document.data_source_type, "segment_id": segment.id, "retriever_from": "workflow", "score": record.score or 0.0, @@ -356,12 +365,12 @@ class KnowledgeRetrievalNode(LLMNode): ) elif node_data.metadata_filtering_mode == "manual": if node_data.metadata_filtering_conditions: - metadata_condition = MetadataCondition(**node_data.metadata_filtering_conditions.model_dump()) + conditions = [] if node_data.metadata_filtering_conditions: for sequence, condition in enumerate(node_data.metadata_filtering_conditions.conditions): # type: ignore metadata_name = condition.name expected_value = condition.value - if expected_value is not None or condition.comparison_operator in ("empty", "not empty"): + if expected_value is not None and condition.comparison_operator not in ("empty", "not empty"): if isinstance(expected_value, str): expected_value = self.graph_runtime_state.variable_pool.convert_template( expected_value @@ -372,13 +381,24 @@ class KnowledgeRetrievalNode(LLMNode): expected_value = re.sub(r"[\r\n\t]+", " ", expected_value.text).strip() # type: ignore else: raise ValueError("Invalid expected metadata value type") - filters = self._process_metadata_filter_func( - sequence, - condition.comparison_operator, - metadata_name, - expected_value, - filters, + conditions.append( + Condition( + name=metadata_name, + comparison_operator=condition.comparison_operator, + value=expected_value, ) + ) + filters = self._process_metadata_filter_func( + sequence, + condition.comparison_operator, + metadata_name, + expected_value, + filters, + ) + metadata_condition = MetadataCondition( + logical_operator=node_data.metadata_filtering_conditions.logical_operator, + conditions=conditions, + ) else: raise ValueError("Invalid metadata filtering mode") if filters: @@ -408,7 +428,7 @@ class KnowledgeRetrievalNode(LLMNode): raise ValueError("metadata_model_config is required") # get metadata model instance # fetch model config - model_instance, model_config = self._fetch_model_config(node_data.metadata_model_config) # type: ignore + model_instance, model_config = self.get_model_config(metadata_model_config) # fetch prompt messages prompt_template = self._get_prompt_template( node_data=node_data, @@ -493,24 +513,24 @@ class KnowledgeRetrievalNode(LLMNode): if isinstance(value, str): filters.append(Document.doc_metadata[metadata_name] == f'"{value}"') else: - filters.append(sqlalchemy_cast(Document.doc_metadata[metadata_name].astext, Integer) == value) + filters.append(sqlalchemy_cast(Document.doc_metadata[metadata_name].astext, Float) == value) case "is not" | "≠": if isinstance(value, str): filters.append(Document.doc_metadata[metadata_name] != f'"{value}"') else: - filters.append(sqlalchemy_cast(Document.doc_metadata[metadata_name].astext, Integer) != value) + filters.append(sqlalchemy_cast(Document.doc_metadata[metadata_name].astext, Float) != value) case "empty": filters.append(Document.doc_metadata[metadata_name].is_(None)) case "not empty": filters.append(Document.doc_metadata[metadata_name].isnot(None)) case "before" | "<": - filters.append(sqlalchemy_cast(Document.doc_metadata[metadata_name].astext, Integer) < value) + filters.append(sqlalchemy_cast(Document.doc_metadata[metadata_name].astext, Float) < value) case "after" | ">": - filters.append(sqlalchemy_cast(Document.doc_metadata[metadata_name].astext, Integer) > value) - case "≤" | ">=": - filters.append(sqlalchemy_cast(Document.doc_metadata[metadata_name].astext, Integer) <= value) + filters.append(sqlalchemy_cast(Document.doc_metadata[metadata_name].astext, Float) > value) + case "≤" | "<=": + filters.append(sqlalchemy_cast(Document.doc_metadata[metadata_name].astext, Float) <= value) case "≥" | ">=": - filters.append(sqlalchemy_cast(Document.doc_metadata[metadata_name].astext, Integer) >= value) + filters.append(sqlalchemy_cast(Document.doc_metadata[metadata_name].astext, Float) >= value) case _: pass return filters @@ -534,14 +554,7 @@ class KnowledgeRetrievalNode(LLMNode): variable_mapping[node_id + ".query"] = node_data.query_variable_selector return variable_mapping - def _fetch_model_config(self, model: ModelConfig) -> tuple[ModelInstance, ModelConfigWithCredentialsEntity]: # type: ignore - """ - Fetch model config - :param model: model - :return: - """ - if model is None: - raise ValueError("model is required") + def get_model_config(self, model: ModelConfig) -> tuple[ModelInstance, ModelConfigWithCredentialsEntity]: model_name = model.name provider_name = model.provider @@ -618,7 +631,7 @@ class KnowledgeRetrievalNode(LLMNode): ) prompt_messages.append(assistant_prompt_message_1) user_prompt_message_2 = LLMNodeChatModelMessage( - role=PromptMessageRole.USER, text=QUESTION_CLASSIFIER_USER_PROMPT_2 + role=PromptMessageRole.USER, text=METADATA_FILTER_USER_PROMPT_2 ) prompt_messages.append(user_prompt_message_2) assistant_prompt_message_2 = LLMNodeChatModelMessage( diff --git a/api/core/workflow/nodes/knowledge_retrieval/template_prompts.py b/api/core/workflow/nodes/knowledge_retrieval/template_prompts.py index 7abd55d798..9c945e2f52 100644 --- a/api/core/workflow/nodes/knowledge_retrieval/template_prompts.py +++ b/api/core/workflow/nodes/knowledge_retrieval/template_prompts.py @@ -2,7 +2,7 @@ METADATA_FILTER_SYSTEM_PROMPT = """ ### Job Description', You are a text metadata extract engine that extract text's metadata based on user input and set the metadata value ### Task - Your task is to ONLY extract the metadatas that exist in the input text from the provided metadata list and Use the following operators ["=", "!=", ">", "<", ">=", "<="] to express logical relationships, then return result in JSON format with the key "metadata_fields" and value "metadata_field_value" and comparison operator "comparison_operator". + Your task is to ONLY extract the metadatas that exist in the input text from the provided metadata list and Use the following operators ["contains", "not contains", "start with", "end with", "is", "is not", "empty", "not empty", "=", "≠", ">", "<", "≥", "≤", "before", "after"] to express logical relationships, then return result in JSON format with the key "metadata_fields" and value "metadata_field_value" and comparison operator "comparison_operator". ### Format The input text is in the variable input_text. Metadata are specified as a list in the variable metadata_fields. ### Constraint @@ -50,7 +50,7 @@ You are a text metadata extract engine that extract text's metadata based on use # Your task is to ONLY extract the metadatas that exist in the input text from the provided metadata list and Use the following operators ["=", "!=", ">", "<", ">=", "<="] to express logical relationships, then return result in JSON format with the key "metadata_fields" and value "metadata_field_value" and comparison operator "comparison_operator". ### Format The input text is in the variable input_text. Metadata are specified as a list in the variable metadata_fields. -### Constraint +### Constraint DO NOT include anything other than the JSON array in your response. ### Example Here is the chat example between human and assistant, inside XML tags. @@ -59,7 +59,7 @@ User:{{"input_text": ["I want to know which company’s email address test@examp Assistant:{{"metadata_map": [{{"metadata_field_name": "email", "metadata_field_value": "test@example.com", "comparison_operator": "="}}]}} User:{{"input_text": "What are the movies with a score of more than 9 in 2024?", "metadata_fields": ["name", "year", "rating", "country"]}} Assistant:{{"metadata_map": [{{"metadata_field_name": "year", "metadata_field_value": "2024", "comparison_operator": "="}, {{"metadata_field_name": "rating", "metadata_field_value": "9", "comparison_operator": ">"}}]}} - + ### User Input {{"input_text" : "{input_text}", "metadata_fields" : {metadata_fields}}} ### Assistant Output diff --git a/api/core/workflow/nodes/list_operator/node.py b/api/core/workflow/nodes/list_operator/node.py index 04ccfc5405..e698d3f5d8 100644 --- a/api/core/workflow/nodes/list_operator/node.py +++ b/api/core/workflow/nodes/list_operator/node.py @@ -4,9 +4,9 @@ from typing import Any, Literal, Union from core.file import File from core.variables import ArrayFileSegment, ArrayNumberSegment, ArrayStringSegment from core.workflow.entities.node_entities import NodeRunResult +from core.workflow.entities.workflow_node_execution import WorkflowNodeExecutionStatus from core.workflow.nodes.base import BaseNode from core.workflow.nodes.enums import NodeType -from models.workflow import WorkflowNodeExecutionStatus from .entities import ListOperatorNodeData from .exc import InvalidConditionError, InvalidFilterValueError, InvalidKeyError, ListOperatorError diff --git a/api/core/workflow/nodes/llm/entities.py b/api/core/workflow/nodes/llm/entities.py index 486b4b01af..36d0688807 100644 --- a/api/core/workflow/nodes/llm/entities.py +++ b/api/core/workflow/nodes/llm/entities.py @@ -66,7 +66,8 @@ class LLMNodeData(BaseNodeData): context: ContextConfig vision: VisionConfig = Field(default_factory=VisionConfig) structured_output: dict | None = None - structured_output_enabled: bool = False + # We used 'structured_output_enabled' in the past, but it's not a good name. + structured_output_switch_on: bool = Field(False, alias="structured_output_enabled") @field_validator("prompt_config", mode="before") @classmethod @@ -74,3 +75,7 @@ class LLMNodeData(BaseNodeData): if v is None: return PromptConfig() return v + + @property + def structured_output_enabled(self) -> bool: + return self.structured_output_switch_on and self.structured_output is not None diff --git a/api/core/workflow/nodes/llm/exc.py b/api/core/workflow/nodes/llm/exc.py index 6599221691..42b8f4e6ce 100644 --- a/api/core/workflow/nodes/llm/exc.py +++ b/api/core/workflow/nodes/llm/exc.py @@ -38,3 +38,8 @@ class MemoryRolePrefixRequiredError(LLMNodeError): class FileTypeNotSupportError(LLMNodeError): def __init__(self, *, type_name: str): super().__init__(f"{type_name} type is not supported by this model") + + +class UnsupportedPromptContentTypeError(LLMNodeError): + def __init__(self, *, type_name: str) -> None: + super().__init__(f"Prompt content type {type_name} is not supported.") diff --git a/api/core/workflow/nodes/llm/file_saver.py b/api/core/workflow/nodes/llm/file_saver.py new file mode 100644 index 0000000000..c85baade03 --- /dev/null +++ b/api/core/workflow/nodes/llm/file_saver.py @@ -0,0 +1,160 @@ +import mimetypes +import typing as tp + +from sqlalchemy import Engine + +from constants.mimetypes import DEFAULT_EXTENSION, DEFAULT_MIME_TYPE +from core.file import File, FileTransferMethod, FileType +from core.helper import ssrf_proxy +from core.tools.signature import sign_tool_file +from core.tools.tool_file_manager import ToolFileManager +from models import db as global_db + + +class LLMFileSaver(tp.Protocol): + """LLMFileSaver is responsible for save multimodal output returned by + LLM. + """ + + def save_binary_string( + self, + data: bytes, + mime_type: str, + file_type: FileType, + extension_override: str | None = None, + ) -> File: + """save_binary_string saves the inline file data returned by LLM. + + Currently (2025-04-30), only some of Google Gemini models will return + multimodal output as inline data. + + :param data: the contents of the file + :param mime_type: the media type of the file, specified by rfc6838 + (https://datatracker.ietf.org/doc/html/rfc6838) + :param file_type: The file type of the inline file. + :param extension_override: Override the auto-detected file extension while saving this file. + + The default value is `None`, which means do not override the file extension and guessing it + from the `mime_type` attribute while saving the file. + + Setting it to values other than `None` means override the file's extension, and + will bypass the extension guessing saving the file. + + Specially, setting it to empty string (`""`) will leave the file extension empty. + + When it is not `None` or empty string (`""`), it should be a string beginning with a + dot (`.`). For example, `.py` and `.tar.gz` are both valid values, while `py` + and `tar.gz` are not. + """ + pass + + def save_remote_url(self, url: str, file_type: FileType) -> File: + """save_remote_url saves the file from a remote url returned by LLM. + + Currently (2025-04-30), no model returns multimodel output as a url. + + :param url: the url of the file. + :param file_type: the file type of the file, check `FileType` enum for reference. + """ + pass + + +EngineFactory: tp.TypeAlias = tp.Callable[[], Engine] + + +class FileSaverImpl(LLMFileSaver): + _engine_factory: EngineFactory + _tenant_id: str + _user_id: str + + def __init__(self, user_id: str, tenant_id: str, engine_factory: EngineFactory | None = None): + if engine_factory is None: + + def _factory(): + return global_db.engine + + engine_factory = _factory + self._engine_factory = engine_factory + self._user_id = user_id + self._tenant_id = tenant_id + + def _get_tool_file_manager(self): + return ToolFileManager(engine=self._engine_factory()) + + def save_remote_url(self, url: str, file_type: FileType) -> File: + http_response = ssrf_proxy.get(url) + http_response.raise_for_status() + data = http_response.content + mime_type_from_header = http_response.headers.get("Content-Type") + mime_type, extension = _extract_content_type_and_extension(url, mime_type_from_header) + return self.save_binary_string(data, mime_type, file_type, extension_override=extension) + + def save_binary_string( + self, + data: bytes, + mime_type: str, + file_type: FileType, + extension_override: str | None = None, + ) -> File: + tool_file_manager = self._get_tool_file_manager() + tool_file = tool_file_manager.create_file_by_raw( + user_id=self._user_id, + tenant_id=self._tenant_id, + # TODO(QuantumGhost): what is conversation id? + conversation_id=None, + file_binary=data, + mimetype=mime_type, + ) + extension_override = _validate_extension_override(extension_override) + extension = _get_extension(mime_type, extension_override) + url = sign_tool_file(tool_file.id, extension) + + return File( + tenant_id=self._tenant_id, + type=file_type, + transfer_method=FileTransferMethod.TOOL_FILE, + filename=tool_file.name, + extension=extension, + mime_type=mime_type, + size=len(data), + related_id=tool_file.id, + url=url, + # TODO(QuantumGhost): how should I set the following key? + # What's the difference between `remote_url` and `url`? + # What's the purpose of `storage_key` and `dify_model_identity`? + storage_key=tool_file.file_key, + ) + + +def _get_extension(mime_type: str, extension_override: str | None = None) -> str: + """get_extension return the extension of file. + + If the `extension_override` parameter is set, this function should honor it and + return its value. + """ + if extension_override is not None: + return extension_override + return mimetypes.guess_extension(mime_type) or DEFAULT_EXTENSION + + +def _extract_content_type_and_extension(url: str, content_type_header: str | None) -> tuple[str, str]: + """_extract_content_type_and_extension tries to + guess content type of file from url and `Content-Type` header in response. + """ + if content_type_header: + extension = mimetypes.guess_extension(content_type_header) or DEFAULT_EXTENSION + return content_type_header, extension + content_type = mimetypes.guess_type(url)[0] or DEFAULT_MIME_TYPE + extension = mimetypes.guess_extension(content_type) or DEFAULT_EXTENSION + return content_type, extension + + +def _validate_extension_override(extension_override: str | None) -> str | None: + # `extension_override` is allow to be `None or `""`. + if extension_override is None: + return None + if extension_override == "": + return "" + if not extension_override.startswith("."): + raise ValueError("extension_override should start with '.' if not None or empty.", extension_override) + return extension_override diff --git a/api/core/workflow/nodes/llm/llm_utils.py b/api/core/workflow/nodes/llm/llm_utils.py new file mode 100644 index 0000000000..0966c87a1d --- /dev/null +++ b/api/core/workflow/nodes/llm/llm_utils.py @@ -0,0 +1,156 @@ +from collections.abc import Sequence +from datetime import UTC, datetime +from typing import Optional, cast + +from sqlalchemy import select, update +from sqlalchemy.orm import Session + +from configs import dify_config +from core.app.entities.app_invoke_entities import ModelConfigWithCredentialsEntity +from core.entities.provider_entities import QuotaUnit +from core.file.models import File +from core.memory.token_buffer_memory import TokenBufferMemory +from core.model_manager import ModelInstance, ModelManager +from core.model_runtime.entities.llm_entities import LLMUsage +from core.model_runtime.entities.model_entities import ModelType +from core.model_runtime.model_providers.__base.large_language_model import LargeLanguageModel +from core.plugin.entities.plugin import ModelProviderID +from core.prompt.entities.advanced_prompt_entities import MemoryConfig +from core.variables.segments import ArrayAnySegment, ArrayFileSegment, FileSegment, NoneSegment, StringSegment +from core.workflow.entities.variable_pool import VariablePool +from core.workflow.enums import SystemVariableKey +from core.workflow.nodes.llm.entities import ModelConfig +from models import db +from models.model import Conversation +from models.provider import Provider, ProviderType + +from .exc import InvalidVariableTypeError, LLMModeRequiredError, ModelNotExistError + + +def fetch_model_config( + tenant_id: str, node_data_model: ModelConfig +) -> tuple[ModelInstance, ModelConfigWithCredentialsEntity]: + if not node_data_model.mode: + raise LLMModeRequiredError("LLM mode is required.") + + model = ModelManager().get_model_instance( + tenant_id=tenant_id, + model_type=ModelType.LLM, + provider=node_data_model.provider, + model=node_data_model.name, + ) + + model.model_type_instance = cast(LargeLanguageModel, model.model_type_instance) + + # check model + provider_model = model.provider_model_bundle.configuration.get_provider_model( + model=node_data_model.name, model_type=ModelType.LLM + ) + + if provider_model is None: + raise ModelNotExistError(f"Model {node_data_model.name} not exist.") + provider_model.raise_for_status() + + # model config + stop: list[str] = [] + if "stop" in node_data_model.completion_params: + stop = node_data_model.completion_params.pop("stop") + + model_schema = model.model_type_instance.get_model_schema(node_data_model.name, model.credentials) + if not model_schema: + raise ModelNotExistError(f"Model {node_data_model.name} not exist.") + + return model, ModelConfigWithCredentialsEntity( + provider=node_data_model.provider, + model=node_data_model.name, + model_schema=model_schema, + mode=node_data_model.mode, + provider_model_bundle=model.provider_model_bundle, + credentials=model.credentials, + parameters=node_data_model.completion_params, + stop=stop, + ) + + +def fetch_files(variable_pool: VariablePool, selector: Sequence[str]) -> Sequence["File"]: + variable = variable_pool.get(selector) + if variable is None: + return [] + elif isinstance(variable, FileSegment): + return [variable.value] + elif isinstance(variable, ArrayFileSegment): + return variable.value + elif isinstance(variable, NoneSegment | ArrayAnySegment): + return [] + raise InvalidVariableTypeError(f"Invalid variable type: {type(variable)}") + + +def fetch_memory( + variable_pool: VariablePool, app_id: str, node_data_memory: Optional[MemoryConfig], model_instance: ModelInstance +) -> Optional[TokenBufferMemory]: + if not node_data_memory: + return None + + # get conversation id + conversation_id_variable = variable_pool.get(["sys", SystemVariableKey.CONVERSATION_ID.value]) + if not isinstance(conversation_id_variable, StringSegment): + return None + conversation_id = conversation_id_variable.value + + with Session(db.engine, expire_on_commit=False) as session: + stmt = select(Conversation).where(Conversation.app_id == app_id, Conversation.id == conversation_id) + conversation = session.scalar(stmt) + if not conversation: + return None + + memory = TokenBufferMemory(conversation=conversation, model_instance=model_instance) + return memory + + +def deduct_llm_quota(tenant_id: str, model_instance: ModelInstance, usage: LLMUsage) -> None: + provider_model_bundle = model_instance.provider_model_bundle + provider_configuration = provider_model_bundle.configuration + + if provider_configuration.using_provider_type != ProviderType.SYSTEM: + return + + system_configuration = provider_configuration.system_configuration + + quota_unit = None + for quota_configuration in system_configuration.quota_configurations: + if quota_configuration.quota_type == system_configuration.current_quota_type: + quota_unit = quota_configuration.quota_unit + + if quota_configuration.quota_limit == -1: + return + + break + + used_quota = None + if quota_unit: + if quota_unit == QuotaUnit.TOKENS: + used_quota = usage.total_tokens + elif quota_unit == QuotaUnit.CREDITS: + used_quota = dify_config.get_model_credits(model_instance.model) + else: + used_quota = 1 + + if used_quota is not None and system_configuration.current_quota_type is not None: + with Session(db.engine) as session: + stmt = ( + update(Provider) + .where( + Provider.tenant_id == tenant_id, + # TODO: Use provider name with prefix after the data migration. + Provider.provider_name == ModelProviderID(model_instance.provider).provider_name, + Provider.provider_type == ProviderType.SYSTEM.value, + Provider.quota_type == system_configuration.current_quota_type.value, + Provider.quota_limit > Provider.quota_used, + ) + .values( + quota_used=Provider.quota_used + used_quota, + last_used=datetime.now(tz=UTC).replace(tzinfo=None), + ) + ) + session.execute(stmt) + session.commit() diff --git a/api/core/workflow/nodes/llm/node.py b/api/core/workflow/nodes/llm/node.py index 1089e7168e..d27124d62c 100644 --- a/api/core/workflow/nodes/llm/node.py +++ b/api/core/workflow/nodes/llm/node.py @@ -1,16 +1,13 @@ +import base64 +import io import json import logging from collections.abc import Generator, Mapping, Sequence -from datetime import UTC, datetime from typing import TYPE_CHECKING, Any, Optional, cast import json_repair -from configs import dify_config from core.app.entities.app_invoke_entities import ModelConfigWithCredentialsEntity -from core.entities.model_entities import ModelStatus -from core.entities.provider_entities import QuotaUnit -from core.errors.error import ModelCurrentlyNotSupportError, ProviderTokenNotInitError, QuotaExceededError from core.file import FileType, file_manager from core.helper.code_executor import CodeExecutor, CodeLanguage from core.memory.token_buffer_memory import TokenBufferMemory @@ -21,7 +18,7 @@ from core.model_runtime.entities import ( PromptMessageContentType, TextPromptMessageContent, ) -from core.model_runtime.entities.llm_entities import LLMResult, LLMUsage +from core.model_runtime.entities.llm_entities import LLMResult, LLMResultChunk, LLMUsage from core.model_runtime.entities.message_entities import ( AssistantPromptMessage, PromptMessageContentUnionTypes, @@ -38,11 +35,10 @@ from core.model_runtime.entities.model_entities import ( ) from core.model_runtime.model_providers.__base.large_language_model import LargeLanguageModel from core.model_runtime.utils.encoders import jsonable_encoder -from core.plugin.entities.plugin import ModelProviderID from core.prompt.entities.advanced_prompt_entities import CompletionModelPromptTemplate, MemoryConfig from core.prompt.utils.prompt_message_util import PromptMessageUtil +from core.rag.entities.citation_metadata import RetrievalSourceMetadata from core.variables import ( - ArrayAnySegment, ArrayFileSegment, ArraySegment, FileSegment, @@ -51,9 +47,10 @@ from core.variables import ( StringSegment, ) from core.workflow.constants import SYSTEM_VARIABLE_NODE_ID -from core.workflow.entities.node_entities import NodeRunMetadataKey, NodeRunResult +from core.workflow.entities.node_entities import NodeRunResult from core.workflow.entities.variable_entities import VariableSelector from core.workflow.entities.variable_pool import VariablePool +from core.workflow.entities.workflow_node_execution import WorkflowNodeExecutionMetadataKey, WorkflowNodeExecutionStatus from core.workflow.enums import SystemVariableKey from core.workflow.graph_engine.entities.event import InNodeEvent from core.workflow.nodes.base import BaseNode @@ -68,15 +65,11 @@ from core.workflow.nodes.event import ( from core.workflow.utils.structured_output.entities import ( ResponseFormat, SpecialModelType, - SupportStructuredOutputStatus, ) from core.workflow.utils.structured_output.prompt import STRUCTURED_OUTPUT_PROMPT from core.workflow.utils.variable_template_parser import VariableTemplateParser -from extensions.ext_database import db -from models.model import Conversation -from models.provider import Provider, ProviderType -from models.workflow import WorkflowNodeExecutionStatus +from . import llm_utils from .entities import ( LLMNodeChatModelMessage, LLMNodeCompletionModelPromptTemplate, @@ -86,7 +79,6 @@ from .entities import ( from .exc import ( InvalidContextStructureError, InvalidVariableTypeError, - LLMModeRequiredError, LLMNodeError, MemoryRolePrefixRequiredError, ModelNotExistError, @@ -94,9 +86,13 @@ from .exc import ( TemplateTypeNotSupportError, VariableNotFoundError, ) +from .file_saver import FileSaverImpl, LLMFileSaver if TYPE_CHECKING: from core.file.models import File + from core.workflow.graph_engine.entities.graph import Graph + from core.workflow.graph_engine.entities.graph_init_params import GraphInitParams + from core.workflow.graph_engine.entities.graph_runtime_state import GraphRuntimeState logger = logging.getLogger(__name__) @@ -105,8 +101,45 @@ class LLMNode(BaseNode[LLMNodeData]): _node_data_cls = LLMNodeData _node_type = NodeType.LLM + # Instance attributes specific to LLMNode. + # Output variable for file + _file_outputs: list["File"] + + _llm_file_saver: LLMFileSaver + + def __init__( + self, + id: str, + config: Mapping[str, Any], + graph_init_params: "GraphInitParams", + graph: "Graph", + graph_runtime_state: "GraphRuntimeState", + previous_node_id: Optional[str] = None, + thread_pool_id: Optional[str] = None, + *, + llm_file_saver: LLMFileSaver | None = None, + ) -> None: + super().__init__( + id=id, + config=config, + graph_init_params=graph_init_params, + graph=graph, + graph_runtime_state=graph_runtime_state, + previous_node_id=previous_node_id, + thread_pool_id=thread_pool_id, + ) + # LLM file outputs, used for MultiModal outputs. + self._file_outputs: list[File] = [] + + if llm_file_saver is None: + llm_file_saver = FileSaverImpl( + user_id=graph_init_params.user_id, + tenant_id=graph_init_params.tenant_id, + ) + self._llm_file_saver = llm_file_saver + def _run(self) -> Generator[NodeEvent | InNodeEvent, None, None]: - def process_structured_output(text: str) -> Optional[dict[str, Any] | list[Any]]: + def process_structured_output(text: str) -> Optional[dict[str, Any]]: """Process structured output if enabled""" if not self.node_data.structured_output_enabled or not self.node_data.structured_output: return None @@ -117,6 +150,7 @@ class LLMNode(BaseNode[LLMNodeData]): result_text = "" usage = LLMUsage.empty_usage() finish_reason = None + variable_pool = self.graph_runtime_state.variable_pool try: # init messages template @@ -135,7 +169,10 @@ class LLMNode(BaseNode[LLMNodeData]): # fetch files files = ( - self._fetch_files(selector=self.node_data.vision.configs.variable_selector) + llm_utils.fetch_files( + variable_pool=variable_pool, + selector=self.node_data.vision.configs.variable_selector, + ) if self.node_data.vision.enabled else [] ) @@ -157,15 +194,18 @@ class LLMNode(BaseNode[LLMNodeData]): model_instance, model_config = self._fetch_model_config(self.node_data.model) # fetch memory - memory = self._fetch_memory(node_data_memory=self.node_data.memory, model_instance=model_instance) + memory = llm_utils.fetch_memory( + variable_pool=variable_pool, + app_id=self.app_id, + node_data_memory=self.node_data.memory, + model_instance=model_instance, + ) query = None if self.node_data.memory: query = self.node_data.memory.query_prompt_template if not query and ( - query_variable := self.graph_runtime_state.variable_pool.get( - (SYSTEM_VARIABLE_NODE_ID, SystemVariableKey.QUERY) - ) + query_variable := variable_pool.get((SYSTEM_VARIABLE_NODE_ID, SystemVariableKey.QUERY)) ): query = query_variable.text @@ -179,7 +219,7 @@ class LLMNode(BaseNode[LLMNodeData]): memory_config=self.node_data.memory, vision_enabled=self.node_data.vision.enabled, vision_detail=self.node_data.vision.configs.detail, - variable_pool=self.graph_runtime_state.variable_pool, + variable_pool=variable_pool, jinja2_variables=self.node_data.prompt_config.jinja2_variables, ) @@ -208,12 +248,15 @@ class LLMNode(BaseNode[LLMNodeData]): usage = event.usage finish_reason = event.finish_reason # deduct quota - self.deduct_llm_quota(tenant_id=self.tenant_id, model_instance=model_instance, usage=usage) + llm_utils.deduct_llm_quota(tenant_id=self.tenant_id, model_instance=model_instance, usage=usage) break outputs = {"text": result_text, "usage": jsonable_encoder(usage), "finish_reason": finish_reason} structured_output = process_structured_output(result_text) if structured_output: outputs["structured_output"] = structured_output + if self._file_outputs is not None: + outputs["files"] = self._file_outputs + yield RunCompletedEvent( run_result=NodeRunResult( status=WorkflowNodeExecutionStatus.SUCCEEDED, @@ -221,14 +264,14 @@ class LLMNode(BaseNode[LLMNodeData]): process_data=process_data, outputs=outputs, metadata={ - NodeRunMetadataKey.TOTAL_TOKENS: usage.total_tokens, - NodeRunMetadataKey.TOTAL_PRICE: usage.total_price, - NodeRunMetadataKey.CURRENCY: usage.currency, + WorkflowNodeExecutionMetadataKey.TOTAL_TOKENS: usage.total_tokens, + WorkflowNodeExecutionMetadataKey.TOTAL_PRICE: usage.total_price, + WorkflowNodeExecutionMetadataKey.CURRENCY: usage.currency, }, llm_usage=usage, ) ) - except LLMNodeError as e: + except ValueError as e: yield RunCompletedEvent( run_result=NodeRunResult( status=WorkflowNodeExecutionStatus.FAILED, @@ -239,6 +282,7 @@ class LLMNode(BaseNode[LLMNodeData]): ) ) except Exception as e: + logger.exception("error while executing llm node") yield RunCompletedEvent( run_result=NodeRunResult( status=WorkflowNodeExecutionStatus.FAILED, @@ -255,8 +299,6 @@ class LLMNode(BaseNode[LLMNodeData]): prompt_messages: Sequence[PromptMessage], stop: Optional[Sequence[str]] = None, ) -> Generator[NodeEvent, None, None]: - db.session.close() - invoke_result = model_instance.invoke_llm( prompt_messages=list(prompt_messages), model_parameters=node_data_model.completion_params, @@ -267,55 +309,45 @@ class LLMNode(BaseNode[LLMNodeData]): return self._handle_invoke_result(invoke_result=invoke_result) - def _handle_invoke_result(self, invoke_result: LLMResult | Generator) -> Generator[NodeEvent, None, None]: + def _handle_invoke_result( + self, invoke_result: LLMResult | Generator[LLMResultChunk, None, None] + ) -> Generator[NodeEvent, None, None]: + # For blocking mode if isinstance(invoke_result, LLMResult): - content = invoke_result.message.content - if content is None: - message_text = "" - elif isinstance(content, str): - message_text = content - elif isinstance(content, list): - # Assuming the list contains PromptMessageContent objects with a "data" attribute - message_text = "".join( - item.data if hasattr(item, "data") and isinstance(item.data, str) else str(item) for item in content - ) - else: - message_text = str(content) - - yield ModelInvokeCompletedEvent( - text=message_text, - usage=invoke_result.usage, - finish_reason=None, - ) + event = self._handle_blocking_result(invoke_result=invoke_result) + yield event return - model = None + # For streaming mode + model = "" prompt_messages: list[PromptMessage] = [] - full_text = "" - usage = None + + usage = LLMUsage.empty_usage() finish_reason = None + full_text_buffer = io.StringIO() for result in invoke_result: - text = result.delta.message.content - full_text += text - - yield RunStreamChunkEvent(chunk_content=text, from_variable_selector=[self.node_id, "text"]) + contents = result.delta.message.content + for text_part in self._save_multimodal_output_and_convert_result_to_markdown(contents): + full_text_buffer.write(text_part) + yield RunStreamChunkEvent(chunk_content=text_part, from_variable_selector=[self.node_id, "text"]) - if not model: + # Update the whole metadata + if not model and result.model: model = result.model - - if not prompt_messages: - prompt_messages = result.prompt_messages - - if not usage and result.delta.usage: + if len(prompt_messages) == 0: + # TODO(QuantumGhost): it seems that this update has no visable effect. + # What's the purpose of the line below? + prompt_messages = list(result.prompt_messages) + if usage.prompt_tokens == 0 and result.delta.usage: usage = result.delta.usage - - if not finish_reason and result.delta.finish_reason: + if finish_reason is None and result.delta.finish_reason: finish_reason = result.delta.finish_reason - if not usage: - usage = LLMUsage.empty_usage() + yield ModelInvokeCompletedEvent(text=full_text_buffer.getvalue(), usage=usage, finish_reason=finish_reason) - yield ModelInvokeCompletedEvent(text=full_text, usage=usage, finish_reason=finish_reason) + def _image_file_to_markdown(self, file: "File", /): + text_chunk = f"![]({file.generate_url()})" + return text_chunk def _transform_chat_messages( self, messages: Sequence[LLMNodeChatModelMessage] | LLMNodeCompletionModelPromptTemplate, / @@ -412,18 +444,6 @@ class LLMNode(BaseNode[LLMNodeData]): return inputs - def _fetch_files(self, *, selector: Sequence[str]) -> Sequence["File"]: - variable = self.graph_runtime_state.variable_pool.get(selector) - if variable is None: - return [] - elif isinstance(variable, FileSegment): - return [variable.value] - elif isinstance(variable, ArrayFileSegment): - return variable.value - elif isinstance(variable, NoneSegment | ArrayAnySegment): - return [] - raise InvalidVariableTypeError(f"Invalid variable type: {type(variable)}") - def _fetch_context(self, node_data: LLMNodeData): if not node_data.context.enabled: return @@ -437,7 +457,7 @@ class LLMNode(BaseNode[LLMNodeData]): yield RunRetrieverResourceEvent(retriever_resources=[], context=context_value_variable.value) elif isinstance(context_value_variable, ArraySegment): context_str = "" - original_retriever_resource = [] + original_retriever_resource: list[RetrievalSourceMetadata] = [] for item in context_value_variable.value: if isinstance(item, str): context_str += item + "\n" @@ -455,7 +475,7 @@ class LLMNode(BaseNode[LLMNodeData]): retriever_resources=original_retriever_resource, context=context_str.strip() ) - def _convert_to_original_retriever_resource(self, context_dict: dict) -> Optional[dict]: + def _convert_to_original_retriever_resource(self, context_dict: dict): if ( "metadata" in context_dict and "_source" in context_dict["metadata"] @@ -463,24 +483,24 @@ class LLMNode(BaseNode[LLMNodeData]): ): metadata = context_dict.get("metadata", {}) - source = { - "position": metadata.get("position"), - "dataset_id": metadata.get("dataset_id"), - "dataset_name": metadata.get("dataset_name"), - "document_id": metadata.get("document_id"), - "document_name": metadata.get("document_name"), - "data_source_type": metadata.get("document_data_source_type"), - "segment_id": metadata.get("segment_id"), - "retriever_from": metadata.get("retriever_from"), - "score": metadata.get("score"), - "hit_count": metadata.get("segment_hit_count"), - "word_count": metadata.get("segment_word_count"), - "segment_position": metadata.get("segment_position"), - "index_node_hash": metadata.get("segment_index_node_hash"), - "content": context_dict.get("content"), - "page": metadata.get("page"), - "doc_metadata": metadata.get("doc_metadata"), - } + source = RetrievalSourceMetadata( + position=metadata.get("position"), + dataset_id=metadata.get("dataset_id"), + dataset_name=metadata.get("dataset_name"), + document_id=metadata.get("document_id"), + document_name=metadata.get("document_name"), + data_source_type=metadata.get("data_source_type"), + segment_id=metadata.get("segment_id"), + retriever_from=metadata.get("retriever_from"), + score=metadata.get("score"), + hit_count=metadata.get("segment_hit_count"), + word_count=metadata.get("segment_word_count"), + segment_position=metadata.get("segment_position"), + index_node_hash=metadata.get("segment_index_node_hash"), + content=context_dict.get("content"), + page=metadata.get("page"), + doc_metadata=metadata.get("doc_metadata"), + ) return source @@ -489,95 +509,25 @@ class LLMNode(BaseNode[LLMNodeData]): def _fetch_model_config( self, node_data_model: ModelConfig ) -> tuple[ModelInstance, ModelConfigWithCredentialsEntity]: - model_name = node_data_model.name - provider_name = node_data_model.provider - - model_manager = ModelManager() - model_instance = model_manager.get_model_instance( - tenant_id=self.tenant_id, model_type=ModelType.LLM, provider=provider_name, model=model_name + model, model_config_with_cred = llm_utils.fetch_model_config( + tenant_id=self.tenant_id, node_data_model=node_data_model ) + completion_params = model_config_with_cred.parameters - provider_model_bundle = model_instance.provider_model_bundle - model_type_instance = model_instance.model_type_instance - model_type_instance = cast(LargeLanguageModel, model_type_instance) - - model_credentials = model_instance.credentials - - # check model - provider_model = provider_model_bundle.configuration.get_provider_model( - model=model_name, model_type=ModelType.LLM - ) - - if provider_model is None: - raise ModelNotExistError(f"Model {model_name} not exist.") - - if provider_model.status == ModelStatus.NO_CONFIGURE: - raise ProviderTokenNotInitError(f"Model {model_name} credentials is not initialized.") - elif provider_model.status == ModelStatus.NO_PERMISSION: - raise ModelCurrentlyNotSupportError(f"Dify Hosted OpenAI {model_name} currently not support.") - elif provider_model.status == ModelStatus.QUOTA_EXCEEDED: - raise QuotaExceededError(f"Model provider {provider_name} quota exceeded.") - - # model config - completion_params = node_data_model.completion_params - stop = [] - if "stop" in completion_params: - stop = completion_params["stop"] - del completion_params["stop"] - - # get model mode - model_mode = node_data_model.mode - if not model_mode: - raise LLMModeRequiredError("LLM mode is required.") - - model_schema = model_type_instance.get_model_schema(model_name, model_credentials) - + model_schema = model.model_type_instance.get_model_schema(node_data_model.name, model.credentials) if not model_schema: - raise ModelNotExistError(f"Model {model_name} not exist.") - support_structured_output = self._check_model_structured_output_support() - if support_structured_output == SupportStructuredOutputStatus.SUPPORTED: - completion_params = self._handle_native_json_schema(completion_params, model_schema.parameter_rules) - elif support_structured_output == SupportStructuredOutputStatus.UNSUPPORTED: - # Set appropriate response format based on model capabilities - self._set_response_format(completion_params, model_schema.parameter_rules) - return model_instance, ModelConfigWithCredentialsEntity( - provider=provider_name, - model=model_name, - model_schema=model_schema, - mode=model_mode, - provider_model_bundle=provider_model_bundle, - credentials=model_credentials, - parameters=completion_params, - stop=stop, - ) - - def _fetch_memory( - self, node_data_memory: Optional[MemoryConfig], model_instance: ModelInstance - ) -> Optional[TokenBufferMemory]: - if not node_data_memory: - return None + raise ModelNotExistError(f"Model {node_data_model.name} not exist.") - # get conversation id - conversation_id_variable = self.graph_runtime_state.variable_pool.get( - ["sys", SystemVariableKey.CONVERSATION_ID.value] - ) - if not isinstance(conversation_id_variable, StringSegment): - return None - conversation_id = conversation_id_variable.value - - # get conversation - conversation = ( - db.session.query(Conversation) - .filter(Conversation.app_id == self.app_id, Conversation.id == conversation_id) - .first() - ) - - if not conversation: - return None - - memory = TokenBufferMemory(conversation=conversation, model_instance=model_instance) - - return memory + if self.node_data.structured_output_enabled: + if model_schema.support_structure_output: + completion_params = self._handle_native_json_schema(completion_params, model_schema.parameter_rules) + else: + # Set appropriate response format based on model capabilities + self._set_response_format(completion_params, model_schema.parameter_rules) + model_config_with_cred.parameters = completion_params + # NOTE(-LAN-): This line modify the `self.node_data.model`, which is used in `_invoke_llm()`. + node_data_model.completion_params = completion_params + return model, model_config_with_cred def _fetch_prompt_messages( self, @@ -752,74 +702,45 @@ class LLMNode(BaseNode[LLMNodeData]): "No prompt found in the LLM configuration. " "Please ensure a prompt is properly configured before proceeding." ) - support_structured_output = self._check_model_structured_output_support() - if support_structured_output == SupportStructuredOutputStatus.UNSUPPORTED: - filtered_prompt_messages = self._handle_prompt_based_schema( - prompt_messages=filtered_prompt_messages, - ) - stop = model_config.stop - return filtered_prompt_messages, stop - def _parse_structured_output(self, result_text: str) -> dict[str, Any] | list[Any]: - structured_output: dict[str, Any] | list[Any] = {} + model = ModelManager().get_model_instance( + tenant_id=self.tenant_id, + model_type=ModelType.LLM, + provider=model_config.provider, + model=model_config.model, + ) + model_schema = model.model_type_instance.get_model_schema( + model=model_config.model, + credentials=model.credentials, + ) + if not model_schema: + raise ModelNotExistError(f"Model {model_config.model} not exist.") + if self.node_data.structured_output_enabled: + if not model_schema.support_structure_output: + filtered_prompt_messages = self._handle_prompt_based_schema( + prompt_messages=filtered_prompt_messages, + ) + return filtered_prompt_messages, model_config.stop + + def _parse_structured_output(self, result_text: str) -> dict[str, Any]: + structured_output: dict[str, Any] = {} try: parsed = json.loads(result_text) - if not isinstance(parsed, (dict | list)): + if not isinstance(parsed, dict): raise LLMNodeError(f"Failed to parse structured output: {result_text}") structured_output = parsed except json.JSONDecodeError as e: # if the result_text is not a valid json, try to repair it parsed = json_repair.loads(result_text) - if not isinstance(parsed, (dict | list)): - raise LLMNodeError(f"Failed to parse structured output: {result_text}") + if not isinstance(parsed, dict): + # handle reasoning model like deepseek-r1 got '\n\n\n' prefix + if isinstance(parsed, list): + parsed = next((item for item in parsed if isinstance(item, dict)), {}) + else: + raise LLMNodeError(f"Failed to parse structured output: {result_text}") structured_output = parsed return structured_output - @classmethod - def deduct_llm_quota(cls, tenant_id: str, model_instance: ModelInstance, usage: LLMUsage) -> None: - provider_model_bundle = model_instance.provider_model_bundle - provider_configuration = provider_model_bundle.configuration - - if provider_configuration.using_provider_type != ProviderType.SYSTEM: - return - - system_configuration = provider_configuration.system_configuration - - quota_unit = None - for quota_configuration in system_configuration.quota_configurations: - if quota_configuration.quota_type == system_configuration.current_quota_type: - quota_unit = quota_configuration.quota_unit - - if quota_configuration.quota_limit == -1: - return - - break - - used_quota = None - if quota_unit: - if quota_unit == QuotaUnit.TOKENS: - used_quota = usage.total_tokens - elif quota_unit == QuotaUnit.CREDITS: - used_quota = dify_config.get_model_credits(model_instance.model) - else: - used_quota = 1 - - if used_quota is not None and system_configuration.current_quota_type is not None: - db.session.query(Provider).filter( - Provider.tenant_id == tenant_id, - # TODO: Use provider name with prefix after the data migration. - Provider.provider_name == ModelProviderID(model_instance.provider).provider_name, - Provider.provider_type == ProviderType.SYSTEM.value, - Provider.quota_type == system_configuration.current_quota_type.value, - Provider.quota_limit > Provider.quota_used, - ).update( - { - "quota_used": Provider.quota_used + used_quota, - "last_used": datetime.now(tz=UTC).replace(tzinfo=None), - } - ) - db.session.commit() - @classmethod def _extract_variable_selector_to_variable_mapping( cls, @@ -861,7 +782,7 @@ class LLMNode(BaseNode[LLMNodeData]): variable_mapping["#context#"] = node_data.context.variable_selector if node_data.vision.enabled: - variable_mapping["#files#"] = ["sys", SystemVariableKey.FILES.value] + variable_mapping["#files#"] = node_data.vision.configs.variable_selector if node_data.memory: variable_mapping["#sys.query#"] = ["sys", SystemVariableKey.QUERY.value] @@ -973,6 +894,42 @@ class LLMNode(BaseNode[LLMNodeData]): return prompt_messages + def _handle_blocking_result(self, *, invoke_result: LLMResult) -> ModelInvokeCompletedEvent: + buffer = io.StringIO() + for text_part in self._save_multimodal_output_and_convert_result_to_markdown(invoke_result.message.content): + buffer.write(text_part) + + return ModelInvokeCompletedEvent( + text=buffer.getvalue(), + usage=invoke_result.usage, + finish_reason=None, + ) + + def _save_multimodal_image_output(self, content: ImagePromptMessageContent) -> "File": + """_save_multimodal_output saves multi-modal contents generated by LLM plugins. + + There are two kinds of multimodal outputs: + + - Inlined data encoded in base64, which would be saved to storage directly. + - Remote files referenced by an url, which would be downloaded and then saved to storage. + + Currently, only image files are supported. + """ + # Inject the saver somehow... + _saver = self._llm_file_saver + + # If this + if content.url != "": + saved_file = _saver.save_remote_url(content.url, FileType.IMAGE) + else: + saved_file = _saver.save_binary_string( + data=base64.b64decode(content.base64_data), + mime_type=content.mime_type, + file_type=FileType.IMAGE, + ) + self._file_outputs.append(saved_file) + return saved_file + def _handle_native_json_schema(self, model_parameters: dict, rules: list[ParameterRule]) -> dict: """ Handle structured output for models with native JSON schema support. @@ -1107,31 +1064,40 @@ class LLMNode(BaseNode[LLMNodeData]): except json.JSONDecodeError: raise LLMNodeError("structured_output_schema is not valid JSON format") - def _check_model_structured_output_support(self) -> SupportStructuredOutputStatus: - """ - Check if the current model supports structured output. + def _save_multimodal_output_and_convert_result_to_markdown( + self, + contents: str | list[PromptMessageContentUnionTypes] | None, + ) -> Generator[str, None, None]: + """Convert intermediate prompt messages into strings and yield them to the caller. - Returns: - SupportStructuredOutput: The support status of structured output + If the messages contain non-textual content (e.g., multimedia like images or videos), + it will be saved separately, and the corresponding Markdown representation will + be yielded to the caller. """ - # Early return if structured output is disabled - if ( - not isinstance(self.node_data, LLMNodeData) - or not self.node_data.structured_output_enabled - or not self.node_data.structured_output - ): - return SupportStructuredOutputStatus.DISABLED - # Get model schema and check if it exists - model_schema = self._fetch_model_schema(self.node_data.model.provider) - if not model_schema: - return SupportStructuredOutputStatus.DISABLED - # Check if model supports structured output feature - return ( - SupportStructuredOutputStatus.SUPPORTED - if bool(model_schema.features and ModelFeature.STRUCTURED_OUTPUT in model_schema.features) - else SupportStructuredOutputStatus.UNSUPPORTED - ) + # NOTE(QuantumGhost): This function should yield results to the caller immediately + # whenever new content or partial content is available. Avoid any intermediate buffering + # of results. Additionally, do not yield empty strings; instead, yield from an empty list + # if necessary. + if contents is None: + yield from [] + return + if isinstance(contents, str): + yield contents + elif isinstance(contents, list): + for item in contents: + if isinstance(item, TextPromptMessageContent): + yield item.data + elif isinstance(item, ImagePromptMessageContent): + file = self._save_multimodal_image_output(item) + self._file_outputs.append(file) + yield self._image_file_to_markdown(file) + else: + logger.warning("unknown item type encountered, type=%s", type(item)) + yield str(item) + else: + logger.warning("unknown contents type encountered, type=%s", type(contents)) + yield str(contents) def _combine_message_content_with_role( diff --git a/api/core/workflow/nodes/loop/entities.py b/api/core/workflow/nodes/loop/entities.py index 16802311dc..3f4a5edab9 100644 --- a/api/core/workflow/nodes/loop/entities.py +++ b/api/core/workflow/nodes/loop/entities.py @@ -26,7 +26,7 @@ class LoopNodeData(BaseLoopNodeData): loop_count: int # Maximum number of loops break_conditions: list[Condition] # Conditions to break the loop logical_operator: Literal["and", "or"] - loop_variables: Optional[list[LoopVariableData]] = Field(default_factory=list) + loop_variables: Optional[list[LoopVariableData]] = Field(default_factory=list[LoopVariableData]) outputs: Optional[Mapping[str, Any]] = None diff --git a/api/core/workflow/nodes/loop/loop_end_node.py b/api/core/workflow/nodes/loop/loop_end_node.py index 5d4ce0ccbe..327b9e234b 100644 --- a/api/core/workflow/nodes/loop/loop_end_node.py +++ b/api/core/workflow/nodes/loop/loop_end_node.py @@ -1,8 +1,8 @@ from core.workflow.entities.node_entities import NodeRunResult +from core.workflow.entities.workflow_node_execution import WorkflowNodeExecutionStatus from core.workflow.nodes.base import BaseNode from core.workflow.nodes.enums import NodeType from core.workflow.nodes.loop.entities import LoopEndNodeData -from models.workflow import WorkflowNodeExecutionStatus class LoopEndNode(BaseNode[LoopEndNodeData]): diff --git a/api/core/workflow/nodes/loop/loop_node.py b/api/core/workflow/nodes/loop/loop_node.py index eae33c0a92..fafa205386 100644 --- a/api/core/workflow/nodes/loop/loop_node.py +++ b/api/core/workflow/nodes/loop/loop_node.py @@ -15,7 +15,8 @@ from core.variables import ( SegmentType, StringSegment, ) -from core.workflow.entities.node_entities import NodeRunMetadataKey, NodeRunResult +from core.workflow.entities.node_entities import NodeRunResult +from core.workflow.entities.workflow_node_execution import WorkflowNodeExecutionMetadataKey, WorkflowNodeExecutionStatus from core.workflow.graph_engine.entities.event import ( BaseGraphEvent, BaseNodeEvent, @@ -37,7 +38,6 @@ from core.workflow.nodes.enums import NodeType from core.workflow.nodes.event import NodeEvent, RunCompletedEvent from core.workflow.nodes.loop.entities import LoopNodeData from core.workflow.utils.condition.processor import ConditionProcessor -from models.workflow import WorkflowNodeExecutionStatus if TYPE_CHECKING: from core.workflow.entities.variable_pool import VariablePool @@ -187,10 +187,10 @@ class LoopNode(BaseNode[LoopNodeData]): outputs=self.node_data.outputs, steps=loop_count, metadata={ - NodeRunMetadataKey.TOTAL_TOKENS: graph_engine.graph_runtime_state.total_tokens, + WorkflowNodeExecutionMetadataKey.TOTAL_TOKENS: graph_engine.graph_runtime_state.total_tokens, "completed_reason": "loop_break" if check_break_result else "loop_completed", - NodeRunMetadataKey.LOOP_DURATION_MAP: loop_duration_map, - NodeRunMetadataKey.LOOP_VARIABLE_MAP: single_loop_variable_map, + WorkflowNodeExecutionMetadataKey.LOOP_DURATION_MAP: loop_duration_map, + WorkflowNodeExecutionMetadataKey.LOOP_VARIABLE_MAP: single_loop_variable_map, }, ) @@ -198,9 +198,9 @@ class LoopNode(BaseNode[LoopNodeData]): run_result=NodeRunResult( status=WorkflowNodeExecutionStatus.SUCCEEDED, metadata={ - NodeRunMetadataKey.TOTAL_TOKENS: graph_engine.graph_runtime_state.total_tokens, - NodeRunMetadataKey.LOOP_DURATION_MAP: loop_duration_map, - NodeRunMetadataKey.LOOP_VARIABLE_MAP: single_loop_variable_map, + WorkflowNodeExecutionMetadataKey.TOTAL_TOKENS: graph_engine.graph_runtime_state.total_tokens, + WorkflowNodeExecutionMetadataKey.LOOP_DURATION_MAP: loop_duration_map, + WorkflowNodeExecutionMetadataKey.LOOP_VARIABLE_MAP: single_loop_variable_map, }, outputs=self.node_data.outputs, inputs=inputs, @@ -221,8 +221,8 @@ class LoopNode(BaseNode[LoopNodeData]): metadata={ "total_tokens": graph_engine.graph_runtime_state.total_tokens, "completed_reason": "error", - NodeRunMetadataKey.LOOP_DURATION_MAP: loop_duration_map, - NodeRunMetadataKey.LOOP_VARIABLE_MAP: single_loop_variable_map, + WorkflowNodeExecutionMetadataKey.LOOP_DURATION_MAP: loop_duration_map, + WorkflowNodeExecutionMetadataKey.LOOP_VARIABLE_MAP: single_loop_variable_map, }, error=str(e), ) @@ -232,9 +232,9 @@ class LoopNode(BaseNode[LoopNodeData]): status=WorkflowNodeExecutionStatus.FAILED, error=str(e), metadata={ - NodeRunMetadataKey.TOTAL_TOKENS: graph_engine.graph_runtime_state.total_tokens, - NodeRunMetadataKey.LOOP_DURATION_MAP: loop_duration_map, - NodeRunMetadataKey.LOOP_VARIABLE_MAP: single_loop_variable_map, + WorkflowNodeExecutionMetadataKey.TOTAL_TOKENS: graph_engine.graph_runtime_state.total_tokens, + WorkflowNodeExecutionMetadataKey.LOOP_DURATION_MAP: loop_duration_map, + WorkflowNodeExecutionMetadataKey.LOOP_VARIABLE_MAP: single_loop_variable_map, }, ) ) @@ -322,7 +322,9 @@ class LoopNode(BaseNode[LoopNodeData]): inputs=inputs, steps=current_index, metadata={ - NodeRunMetadataKey.TOTAL_TOKENS: graph_engine.graph_runtime_state.total_tokens, + WorkflowNodeExecutionMetadataKey.TOTAL_TOKENS: ( + graph_engine.graph_runtime_state.total_tokens + ), "completed_reason": "error", }, error=event.error, @@ -331,13 +333,17 @@ class LoopNode(BaseNode[LoopNodeData]): run_result=NodeRunResult( status=WorkflowNodeExecutionStatus.FAILED, error=event.error, - metadata={NodeRunMetadataKey.TOTAL_TOKENS: graph_engine.graph_runtime_state.total_tokens}, + metadata={ + WorkflowNodeExecutionMetadataKey.TOTAL_TOKENS: ( + graph_engine.graph_runtime_state.total_tokens + ) + }, ) ) return {"check_break_result": True} elif isinstance(event, NodeRunFailedEvent): # Loop run failed - yield event + yield self._handle_event_metadata(event=event, iter_run_index=current_index) yield LoopRunFailedEvent( loop_id=self.id, loop_node_id=self.node_id, @@ -347,7 +353,7 @@ class LoopNode(BaseNode[LoopNodeData]): inputs=inputs, steps=current_index, metadata={ - NodeRunMetadataKey.TOTAL_TOKENS: graph_engine.graph_runtime_state.total_tokens, + WorkflowNodeExecutionMetadataKey.TOTAL_TOKENS: graph_engine.graph_runtime_state.total_tokens, "completed_reason": "error", }, error=event.error, @@ -356,7 +362,9 @@ class LoopNode(BaseNode[LoopNodeData]): run_result=NodeRunResult( status=WorkflowNodeExecutionStatus.FAILED, error=event.error, - metadata={NodeRunMetadataKey.TOTAL_TOKENS: graph_engine.graph_runtime_state.total_tokens}, + metadata={ + WorkflowNodeExecutionMetadataKey.TOTAL_TOKENS: graph_engine.graph_runtime_state.total_tokens + }, ) ) return {"check_break_result": True} @@ -411,11 +419,11 @@ class LoopNode(BaseNode[LoopNodeData]): metadata = event.route_node_state.node_run_result.metadata if not metadata: metadata = {} - if NodeRunMetadataKey.LOOP_ID not in metadata: + if WorkflowNodeExecutionMetadataKey.LOOP_ID not in metadata: metadata = { **metadata, - NodeRunMetadataKey.LOOP_ID: self.node_id, - NodeRunMetadataKey.LOOP_INDEX: iter_run_index, + WorkflowNodeExecutionMetadataKey.LOOP_ID: self.node_id, + WorkflowNodeExecutionMetadataKey.LOOP_INDEX: iter_run_index, } event.route_node_state.node_run_result.metadata = metadata return event diff --git a/api/core/workflow/nodes/loop/loop_start_node.py b/api/core/workflow/nodes/loop/loop_start_node.py index 7cf145e4e5..5a15f36044 100644 --- a/api/core/workflow/nodes/loop/loop_start_node.py +++ b/api/core/workflow/nodes/loop/loop_start_node.py @@ -1,8 +1,8 @@ from core.workflow.entities.node_entities import NodeRunResult +from core.workflow.entities.workflow_node_execution import WorkflowNodeExecutionStatus from core.workflow.nodes.base import BaseNode from core.workflow.nodes.enums import NodeType from core.workflow.nodes.loop.entities import LoopStartNodeData -from models.workflow import WorkflowNodeExecutionStatus class LoopStartNode(BaseNode[LoopStartNodeData]): diff --git a/api/core/workflow/nodes/parameter_extractor/parameter_extractor_node.py b/api/core/workflow/nodes/parameter_extractor/parameter_extractor_node.py index 8db1e432fc..2552784762 100644 --- a/api/core/workflow/nodes/parameter_extractor/parameter_extractor_node.py +++ b/api/core/workflow/nodes/parameter_extractor/parameter_extractor_node.py @@ -25,13 +25,13 @@ from core.prompt.advanced_prompt_transform import AdvancedPromptTransform from core.prompt.entities.advanced_prompt_entities import ChatModelMessage, CompletionModelPromptTemplate from core.prompt.simple_prompt_transform import ModelMode from core.prompt.utils.prompt_message_util import PromptMessageUtil -from core.workflow.entities.node_entities import NodeRunMetadataKey, NodeRunResult +from core.workflow.entities.node_entities import NodeRunResult from core.workflow.entities.variable_pool import VariablePool +from core.workflow.entities.workflow_node_execution import WorkflowNodeExecutionMetadataKey, WorkflowNodeExecutionStatus +from core.workflow.nodes.base.node import BaseNode from core.workflow.nodes.enums import NodeType -from core.workflow.nodes.llm import LLMNode, ModelConfig +from core.workflow.nodes.llm import ModelConfig, llm_utils from core.workflow.utils import variable_template_parser -from extensions.ext_database import db -from models.workflow import WorkflowNodeExecutionStatus from .entities import ParameterExtractorNodeData from .exc import ( @@ -84,7 +84,7 @@ def extract_json(text): return None -class ParameterExtractorNode(LLMNode): +class ParameterExtractorNode(BaseNode): """ Parameter Extractor Node. """ @@ -117,8 +117,11 @@ class ParameterExtractorNode(LLMNode): variable = self.graph_runtime_state.variable_pool.get(node_data.query) query = variable.text if variable else "" + variable_pool = self.graph_runtime_state.variable_pool + files = ( - self._fetch_files( + llm_utils.fetch_files( + variable_pool=variable_pool, selector=node_data.vision.configs.variable_selector, ) if node_data.vision.enabled @@ -138,7 +141,9 @@ class ParameterExtractorNode(LLMNode): raise ModelSchemaNotFoundError("Model schema not found") # fetch memory - memory = self._fetch_memory( + memory = llm_utils.fetch_memory( + variable_pool=variable_pool, + app_id=self.app_id, node_data_memory=node_data.memory, model_instance=model_instance, ) @@ -244,9 +249,9 @@ class ParameterExtractorNode(LLMNode): process_data=process_data, outputs={"__is_success": 1 if not error else 0, "__reason": error, **result}, metadata={ - NodeRunMetadataKey.TOTAL_TOKENS: usage.total_tokens, - NodeRunMetadataKey.TOTAL_PRICE: usage.total_price, - NodeRunMetadataKey.CURRENCY: usage.currency, + WorkflowNodeExecutionMetadataKey.TOTAL_TOKENS: usage.total_tokens, + WorkflowNodeExecutionMetadataKey.TOTAL_PRICE: usage.total_price, + WorkflowNodeExecutionMetadataKey.CURRENCY: usage.currency, }, llm_usage=usage, ) @@ -259,8 +264,6 @@ class ParameterExtractorNode(LLMNode): tools: list[PromptMessageTool], stop: list[str], ) -> tuple[str, LLMUsage, Optional[AssistantPromptMessage.ToolCall]]: - db.session.close() - invoke_result = model_instance.invoke_llm( prompt_messages=prompt_messages, model_parameters=node_data_model.completion_params, @@ -282,7 +285,7 @@ class ParameterExtractorNode(LLMNode): tool_call = invoke_result.message.tool_calls[0] if invoke_result.message.tool_calls else None # deduct quota - self.deduct_llm_quota(tenant_id=self.tenant_id, model_instance=model_instance, usage=usage) + llm_utils.deduct_llm_quota(tenant_id=self.tenant_id, model_instance=model_instance, usage=usage) if text is None: text = "" @@ -797,7 +800,9 @@ class ParameterExtractorNode(LLMNode): Fetch model config. """ if not self._model_instance or not self._model_config: - self._model_instance, self._model_config = super()._fetch_model_config(node_data_model) + self._model_instance, self._model_config = llm_utils.fetch_model_config( + tenant_id=self.tenant_id, node_data_model=node_data_model + ) return self._model_instance, self._model_config @@ -816,7 +821,6 @@ class ParameterExtractorNode(LLMNode): :param node_data: node data :return: """ - # FIXME: fix the type error later variable_mapping: dict[str, Sequence[str]] = {"query": node_data.query} if node_data.instruction: diff --git a/api/core/workflow/nodes/parameter_extractor/prompts.py b/api/core/workflow/nodes/parameter_extractor/prompts.py index 6c3155ac9a..ab7ddcc32a 100644 --- a/api/core/workflow/nodes/parameter_extractor/prompts.py +++ b/api/core/workflow/nodes/parameter_extractor/prompts.py @@ -17,7 +17,7 @@ Some additional information is provided below. Always adhere to these instructio Steps: 1. Review the chat history provided within the tags. -2. Extract the relevant information based on the criteria given, output multiple values if there is multiple relevant information that match the criteria in the given text. +2. Extract the relevant information based on the criteria given, output multiple values if there is multiple relevant information that match the criteria in the given text. 3. Generate a well-formatted output using the defined functions and arguments. 4. Use the `extract_parameter` function to create structured outputs with appropriate parameters. 5. Do not include any XML tags in your output. @@ -89,13 +89,13 @@ Some extra information are provided below, I should always follow the instructio ### Extract parameter Workflow -I need to extract the following information from the input text. The tag specifies the 'type', 'description' and 'required' of the information to be extracted. +I need to extract the following information from the input text. The tag specifies the 'type', 'description' and 'required' of the information to be extracted. {{ structure }} Step 1: Carefully read the input and understand the structure of the expected output. -Step 2: Extract relevant parameters from the provided text based on the name and description of object. +Step 2: Extract relevant parameters from the provided text based on the name and description of object. Step 3: Structure the extracted parameters to JSON object as specified in . Step 4: Ensure that the JSON object is properly formatted and valid. The output should not contain any XML tags. Only the JSON object should be outputted. @@ -106,10 +106,10 @@ Here are the chat histories between human and assistant, inside ### Structure -Here is the structure of the expected output, I should always follow the output structure. +Here is the structure of the expected output, I should always follow the output structure. {{γγγ - 'properties1': 'relevant text extracted from input', - 'properties2': 'relevant text extracted from input', + 'properties1': 'relevant text extracted from input', + 'properties2': 'relevant text extracted from input', }}γγγ ### Input Text @@ -119,7 +119,7 @@ Inside XML tags, there is a text that I should extract parameters ### Answer -I should always output a valid JSON object. Output nothing other than the JSON object. +I should always output a valid JSON object. Output nothing other than the JSON object. ```JSON """ # noqa: E501 diff --git a/api/core/workflow/nodes/question_classifier/entities.py b/api/core/workflow/nodes/question_classifier/entities.py index 5219f11d26..6248df0edf 100644 --- a/api/core/workflow/nodes/question_classifier/entities.py +++ b/api/core/workflow/nodes/question_classifier/entities.py @@ -19,3 +19,12 @@ class QuestionClassifierNodeData(BaseNodeData): instruction: Optional[str] = None memory: Optional[MemoryConfig] = None vision: VisionConfig = Field(default_factory=VisionConfig) + + @property + def structured_output_enabled(self) -> bool: + # NOTE(QuantumGhost): Temporary workaround for issue #20725 + # (https://github.com/langgenius/dify/issues/20725). + # + # The proper fix would be to make `QuestionClassifierNode` inherit + # from `BaseNode` instead of `LLMNode`. + return False diff --git a/api/core/workflow/nodes/question_classifier/question_classifier_node.py b/api/core/workflow/nodes/question_classifier/question_classifier_node.py index b4f34a3bef..1f50700c7e 100644 --- a/api/core/workflow/nodes/question_classifier/question_classifier_node.py +++ b/api/core/workflow/nodes/question_classifier/question_classifier_node.py @@ -10,17 +10,18 @@ from core.model_runtime.utils.encoders import jsonable_encoder from core.prompt.advanced_prompt_transform import AdvancedPromptTransform from core.prompt.simple_prompt_transform import ModelMode from core.prompt.utils.prompt_message_util import PromptMessageUtil -from core.workflow.entities.node_entities import NodeRunMetadataKey, NodeRunResult +from core.workflow.entities.node_entities import NodeRunResult +from core.workflow.entities.workflow_node_execution import WorkflowNodeExecutionMetadataKey, WorkflowNodeExecutionStatus from core.workflow.nodes.enums import NodeType from core.workflow.nodes.event import ModelInvokeCompletedEvent from core.workflow.nodes.llm import ( LLMNode, LLMNodeChatModelMessage, LLMNodeCompletionModelPromptTemplate, + llm_utils, ) from core.workflow.utils.variable_template_parser import VariableTemplateParser from libs.json_in_md_parser import parse_and_check_json_markdown -from models.workflow import WorkflowNodeExecutionStatus from .entities import QuestionClassifierNodeData from .exc import InvalidModelTypeError @@ -50,7 +51,9 @@ class QuestionClassifierNode(LLMNode): # fetch model config model_instance, model_config = self._fetch_model_config(node_data.model) # fetch memory - memory = self._fetch_memory( + memory = llm_utils.fetch_memory( + variable_pool=variable_pool, + app_id=self.app_id, node_data_memory=node_data.memory, model_instance=model_instance, ) @@ -59,7 +62,8 @@ class QuestionClassifierNode(LLMNode): node_data.instruction = variable_pool.convert_template(node_data.instruction).text files = ( - self._fetch_files( + llm_utils.fetch_files( + variable_pool=variable_pool, selector=node_data.vision.configs.variable_selector, ) if node_data.vision.enabled @@ -79,9 +83,13 @@ class QuestionClassifierNode(LLMNode): memory=memory, max_token_limit=rest_token, ) + # Some models (e.g. Gemma, Mistral) force roles alternation (user/assistant/user/assistant...). + # If both self._get_prompt_template and self._fetch_prompt_messages append a user prompt, + # two consecutive user prompts will be generated, causing model's error. + # To avoid this, set sys_query to an empty string so that only one user prompt is appended at the end. prompt_messages, stop = self._fetch_prompt_messages( prompt_template=prompt_template, - sys_query=query, + sys_query="", memory=memory, model_config=model_config, sys_files=files, @@ -142,9 +150,9 @@ class QuestionClassifierNode(LLMNode): outputs=outputs, edge_source_handle=category_id, metadata={ - NodeRunMetadataKey.TOTAL_TOKENS: usage.total_tokens, - NodeRunMetadataKey.TOTAL_PRICE: usage.total_price, - NodeRunMetadataKey.CURRENCY: usage.currency, + WorkflowNodeExecutionMetadataKey.TOTAL_TOKENS: usage.total_tokens, + WorkflowNodeExecutionMetadataKey.TOTAL_PRICE: usage.total_price, + WorkflowNodeExecutionMetadataKey.CURRENCY: usage.currency, }, llm_usage=usage, ) @@ -154,9 +162,9 @@ class QuestionClassifierNode(LLMNode): inputs=variables, error=str(e), metadata={ - NodeRunMetadataKey.TOTAL_TOKENS: usage.total_tokens, - NodeRunMetadataKey.TOTAL_PRICE: usage.total_price, - NodeRunMetadataKey.CURRENCY: usage.currency, + WorkflowNodeExecutionMetadataKey.TOTAL_TOKENS: usage.total_tokens, + WorkflowNodeExecutionMetadataKey.TOTAL_PRICE: usage.total_price, + WorkflowNodeExecutionMetadataKey.CURRENCY: usage.currency, }, llm_usage=usage, ) diff --git a/api/core/workflow/nodes/question_classifier/template_prompts.py b/api/core/workflow/nodes/question_classifier/template_prompts.py index 70178ed934..a615c32383 100644 --- a/api/core/workflow/nodes/question_classifier/template_prompts.py +++ b/api/core/workflow/nodes/question_classifier/template_prompts.py @@ -55,7 +55,7 @@ You are a text classification engine that analyzes text data and assigns categor Your task is to assign one categories ONLY to the input text and only one category may be assigned returned in the output. Additionally, you need to extract the key words from the text that are related to the classification. ### Format The input text is in the variable input_text. Categories are specified as a category list with two filed category_id and category_name in the variable categories. Classification instructions may be included to improve the classification accuracy. -### Constraint +### Constraint DO NOT include anything other than the JSON array in your response. ### Example Here is the chat example between human and assistant, inside XML tags. @@ -64,7 +64,7 @@ User:{{"input_text": ["I recently had a great experience with your company. The Assistant:{{"keywords": ["recently", "great experience", "company", "service", "prompt", "staff", "friendly"],"category_id": "f5660049-284f-41a7-b301-fd24176a711c","category_name": "Customer Service"}} User:{{"input_text": ["bad service, slow to bring the food"], "categories": [{{"category_id":"80fb86a0-4454-4bf5-924c-f253fdd83c02","category_name":"Food Quality"}},{{"category_id":"f6ff5bc3-aca0-4e4a-8627-e760d0aca78f","category_name":"Experience"}},{{"category_id":"cc771f63-74e7-4c61-882e-3eda9d8ba5d7","category_name":"Price"}}], "classification_instructions": []}} Assistant:{{"keywords": ["bad service", "slow", "food", "tip", "terrible", "waitresses"],"category_id": "f6ff5bc3-aca0-4e4a-8627-e760d0aca78f","category_name": "Experience"}} - + ### Memory Here are the chat histories between human and assistant, inside XML tags. diff --git a/api/core/workflow/nodes/start/start_node.py b/api/core/workflow/nodes/start/start_node.py index 1b47b81517..8839aec9d6 100644 --- a/api/core/workflow/nodes/start/start_node.py +++ b/api/core/workflow/nodes/start/start_node.py @@ -1,9 +1,9 @@ from core.workflow.constants import SYSTEM_VARIABLE_NODE_ID from core.workflow.entities.node_entities import NodeRunResult +from core.workflow.entities.workflow_node_execution import WorkflowNodeExecutionStatus from core.workflow.nodes.base import BaseNode from core.workflow.nodes.enums import NodeType from core.workflow.nodes.start.entities import StartNodeData -from models.workflow import WorkflowNodeExecutionStatus class StartNode(BaseNode[StartNodeData]): diff --git a/api/core/workflow/nodes/template_transform/template_transform_node.py b/api/core/workflow/nodes/template_transform/template_transform_node.py index 22a1b21888..476cf7eee4 100644 --- a/api/core/workflow/nodes/template_transform/template_transform_node.py +++ b/api/core/workflow/nodes/template_transform/template_transform_node.py @@ -4,10 +4,10 @@ from typing import Any, Optional from core.helper.code_executor.code_executor import CodeExecutionError, CodeExecutor, CodeLanguage from core.workflow.entities.node_entities import NodeRunResult +from core.workflow.entities.workflow_node_execution import WorkflowNodeExecutionStatus from core.workflow.nodes.base import BaseNode from core.workflow.nodes.enums import NodeType from core.workflow.nodes.template_transform.entities import TemplateTransformNodeData -from models.workflow import WorkflowNodeExecutionStatus MAX_TEMPLATE_TRANSFORM_OUTPUT_LENGTH = int(os.environ.get("TEMPLATE_TRANSFORM_MAX_LENGTH", "80000")) diff --git a/api/core/workflow/nodes/tool/tool_node.py b/api/core/workflow/nodes/tool/tool_node.py index 6f0cc3f6d2..aaecc7b989 100644 --- a/api/core/workflow/nodes/tool/tool_node.py +++ b/api/core/workflow/nodes/tool/tool_node.py @@ -6,16 +6,17 @@ from sqlalchemy.orm import Session from core.callback_handler.workflow_tool_callback_handler import DifyWorkflowCallbackHandler from core.file import File, FileTransferMethod -from core.plugin.manager.exc import PluginDaemonClientSideError -from core.plugin.manager.plugin import PluginInstallationManager +from core.plugin.impl.exc import PluginDaemonClientSideError +from core.plugin.impl.plugin import PluginInstaller from core.tools.entities.tool_entities import ToolInvokeMessage, ToolParameter from core.tools.errors import ToolInvokeError from core.tools.tool_engine import ToolEngine from core.tools.utils.message_transformer import ToolFileMessageTransformer from core.variables.segments import ArrayAnySegment from core.variables.variables import ArrayAnyVariable -from core.workflow.entities.node_entities import NodeRunMetadataKey, NodeRunResult +from core.workflow.entities.node_entities import NodeRunResult from core.workflow.entities.variable_pool import VariablePool +from core.workflow.entities.workflow_node_execution import WorkflowNodeExecutionMetadataKey, WorkflowNodeExecutionStatus from core.workflow.enums import SystemVariableKey from core.workflow.graph_engine.entities.event import AgentLogEvent from core.workflow.nodes.base import BaseNode @@ -25,7 +26,6 @@ from core.workflow.utils.variable_template_parser import VariableTemplateParser from extensions.ext_database import db from factories import file_factory from models import ToolFile -from models.workflow import WorkflowNodeExecutionStatus from services.tools.builtin_tools_manage_service import BuiltinToolManageService from .entities import ToolNodeData @@ -70,7 +70,7 @@ class ToolNode(BaseNode[ToolNodeData]): run_result=NodeRunResult( status=WorkflowNodeExecutionStatus.FAILED, inputs={}, - metadata={NodeRunMetadataKey.TOOL_INFO: tool_info}, + metadata={WorkflowNodeExecutionMetadataKey.TOOL_INFO: tool_info}, error=f"Failed to get tool runtime: {str(e)}", error_type=type(e).__name__, ) @@ -110,7 +110,7 @@ class ToolNode(BaseNode[ToolNodeData]): run_result=NodeRunResult( status=WorkflowNodeExecutionStatus.FAILED, inputs=parameters_for_log, - metadata={NodeRunMetadataKey.TOOL_INFO: tool_info}, + metadata={WorkflowNodeExecutionMetadataKey.TOOL_INFO: tool_info}, error=f"Failed to invoke tool: {str(e)}", error_type=type(e).__name__, ) @@ -125,7 +125,7 @@ class ToolNode(BaseNode[ToolNodeData]): run_result=NodeRunResult( status=WorkflowNodeExecutionStatus.FAILED, inputs=parameters_for_log, - metadata={NodeRunMetadataKey.TOOL_INFO: tool_info}, + metadata={WorkflowNodeExecutionMetadataKey.TOOL_INFO: tool_info}, error=f"Failed to transform tool message: {str(e)}", error_type=type(e).__name__, ) @@ -201,7 +201,7 @@ class ToolNode(BaseNode[ToolNodeData]): json: list[dict] = [] agent_logs: list[AgentLogEvent] = [] - agent_execution_metadata: Mapping[NodeRunMetadataKey, Any] = {} + agent_execution_metadata: Mapping[WorkflowNodeExecutionMetadataKey, Any] = {} variables: dict[str, Any] = {} @@ -274,7 +274,7 @@ class ToolNode(BaseNode[ToolNodeData]): agent_execution_metadata = { key: value for key, value in msg_metadata.items() - if key in NodeRunMetadataKey.__members__.values() + if key in WorkflowNodeExecutionMetadataKey.__members__.values() } json.append(message.message.json_object) elif message.type == ToolInvokeMessage.MessageType.LINK: @@ -307,7 +307,7 @@ class ToolNode(BaseNode[ToolNodeData]): icon = tool_info.get("icon", "") dict_metadata = dict(message.message.metadata) if dict_metadata.get("provider"): - manager = PluginInstallationManager() + manager = PluginInstaller() plugins = manager.list_plugins(self.tenant_id) try: current_plugin = next( @@ -366,8 +366,8 @@ class ToolNode(BaseNode[ToolNodeData]): outputs={"text": text, "files": files, "json": json, **variables}, metadata={ **agent_execution_metadata, - NodeRunMetadataKey.TOOL_INFO: tool_info, - NodeRunMetadataKey.AGENT_LOG: agent_logs, + WorkflowNodeExecutionMetadataKey.TOOL_INFO: tool_info, + WorkflowNodeExecutionMetadataKey.AGENT_LOG: agent_logs, }, inputs=parameters_for_log, ) diff --git a/api/core/workflow/nodes/variable_aggregator/entities.py b/api/core/workflow/nodes/variable_aggregator/entities.py index 9e58f5e944..f4577d7573 100644 --- a/api/core/workflow/nodes/variable_aggregator/entities.py +++ b/api/core/workflow/nodes/variable_aggregator/entities.py @@ -1,7 +1,8 @@ -from typing import Literal, Optional +from typing import Optional from pydantic import BaseModel +from core.variables.types import SegmentType from core.workflow.nodes.base import BaseNodeData @@ -17,7 +18,7 @@ class AdvancedSettings(BaseModel): Group. """ - output_type: Literal["string", "number", "object", "array[string]", "array[number]", "array[object]"] + output_type: SegmentType variables: list[list[str]] group_name: str diff --git a/api/core/workflow/nodes/variable_aggregator/variable_aggregator_node.py b/api/core/workflow/nodes/variable_aggregator/variable_aggregator_node.py index 372496a8fa..db3e25b015 100644 --- a/api/core/workflow/nodes/variable_aggregator/variable_aggregator_node.py +++ b/api/core/workflow/nodes/variable_aggregator/variable_aggregator_node.py @@ -1,8 +1,8 @@ from core.workflow.entities.node_entities import NodeRunResult +from core.workflow.entities.workflow_node_execution import WorkflowNodeExecutionStatus from core.workflow.nodes.base import BaseNode from core.workflow.nodes.enums import NodeType from core.workflow.nodes.variable_aggregator.entities import VariableAssignerNodeData -from models.workflow import WorkflowNodeExecutionStatus class VariableAggregatorNode(BaseNode[VariableAssignerNodeData]): diff --git a/api/core/workflow/nodes/variable_assigner/v1/node.py b/api/core/workflow/nodes/variable_assigner/v1/node.py index 7c7f14c0b8..835e1d77b5 100644 --- a/api/core/workflow/nodes/variable_assigner/v1/node.py +++ b/api/core/workflow/nodes/variable_assigner/v1/node.py @@ -1,11 +1,11 @@ from core.variables import SegmentType, Variable from core.workflow.entities.node_entities import NodeRunResult +from core.workflow.entities.workflow_node_execution import WorkflowNodeExecutionStatus from core.workflow.nodes.base import BaseNode from core.workflow.nodes.enums import NodeType from core.workflow.nodes.variable_assigner.common import helpers as common_helpers from core.workflow.nodes.variable_assigner.common.exc import VariableOperatorNodeError from factories import variable_factory -from models.workflow import WorkflowNodeExecutionStatus from .node_data import VariableAssignerData, WriteMode diff --git a/api/core/workflow/nodes/variable_assigner/v2/enums.py b/api/core/workflow/nodes/variable_assigner/v2/enums.py index 36cf68aa19..291b1208d4 100644 --- a/api/core/workflow/nodes/variable_assigner/v2/enums.py +++ b/api/core/workflow/nodes/variable_assigner/v2/enums.py @@ -11,6 +11,8 @@ class Operation(StrEnum): SUBTRACT = "-=" MULTIPLY = "*=" DIVIDE = "/=" + REMOVE_FIRST = "remove-first" + REMOVE_LAST = "remove-last" class InputType(StrEnum): diff --git a/api/core/workflow/nodes/variable_assigner/v2/helpers.py b/api/core/workflow/nodes/variable_assigner/v2/helpers.py index a86c7eb94a..8fb2a27388 100644 --- a/api/core/workflow/nodes/variable_assigner/v2/helpers.py +++ b/api/core/workflow/nodes/variable_assigner/v2/helpers.py @@ -23,6 +23,15 @@ def is_operation_supported(*, variable_type: SegmentType, operation: Operation): SegmentType.ARRAY_NUMBER, SegmentType.ARRAY_FILE, } + case Operation.REMOVE_FIRST | Operation.REMOVE_LAST: + # Only array variable can have elements removed + return variable_type in { + SegmentType.ARRAY_ANY, + SegmentType.ARRAY_OBJECT, + SegmentType.ARRAY_STRING, + SegmentType.ARRAY_NUMBER, + SegmentType.ARRAY_FILE, + } case _: return False @@ -51,7 +60,7 @@ def is_constant_input_supported(*, variable_type: SegmentType, operation: Operat def is_input_value_valid(*, variable_type: SegmentType, operation: Operation, value: Any): - if operation == Operation.CLEAR: + if operation in {Operation.CLEAR, Operation.REMOVE_FIRST, Operation.REMOVE_LAST}: return True match variable_type: case SegmentType.STRING: diff --git a/api/core/workflow/nodes/variable_assigner/v2/node.py b/api/core/workflow/nodes/variable_assigner/v2/node.py index 0305eb7f41..8759a55b34 100644 --- a/api/core/workflow/nodes/variable_assigner/v2/node.py +++ b/api/core/workflow/nodes/variable_assigner/v2/node.py @@ -6,11 +6,11 @@ from core.app.entities.app_invoke_entities import InvokeFrom from core.variables import SegmentType, Variable from core.workflow.constants import CONVERSATION_VARIABLE_NODE_ID from core.workflow.entities.node_entities import NodeRunResult +from core.workflow.entities.workflow_node_execution import WorkflowNodeExecutionStatus from core.workflow.nodes.base import BaseNode from core.workflow.nodes.enums import NodeType from core.workflow.nodes.variable_assigner.common import helpers as common_helpers from core.workflow.nodes.variable_assigner.common.exc import VariableOperatorNodeError -from models.workflow import WorkflowNodeExecutionStatus from . import helpers from .constants import EMPTY_VALUE_MAPPING @@ -64,7 +64,7 @@ class VariableAssignerNode(BaseNode[VariableAssignerNodeData]): # Get value from variable pool if ( item.input_type == InputType.VARIABLE - and item.operation != Operation.CLEAR + and item.operation not in {Operation.CLEAR, Operation.REMOVE_FIRST, Operation.REMOVE_LAST} and item.value is not None ): value = self.graph_runtime_state.variable_pool.get(item.value) @@ -165,5 +165,15 @@ class VariableAssignerNode(BaseNode[VariableAssignerNodeData]): return variable.value * value case Operation.DIVIDE: return variable.value / value + case Operation.REMOVE_FIRST: + # If array is empty, do nothing + if not variable.value: + return variable.value + return variable.value[1:] + case Operation.REMOVE_LAST: + # If array is empty, do nothing + if not variable.value: + return variable.value + return variable.value[:-1] case _: raise OperationNotSupportedError(operation=operation, variable_type=variable.value_type) diff --git a/api/core/repository/__init__.py b/api/core/workflow/repositories/__init__.py similarity index 58% rename from api/core/repository/__init__.py rename to api/core/workflow/repositories/__init__.py index 253df1251d..a778151baa 100644 --- a/api/core/repository/__init__.py +++ b/api/core/workflow/repositories/__init__.py @@ -6,10 +6,9 @@ for accessing and manipulating data, regardless of the underlying storage mechanism. """ -from core.repository.repository_factory import RepositoryFactory -from core.repository.workflow_node_execution_repository import WorkflowNodeExecutionRepository +from core.workflow.repositories.workflow_node_execution_repository import OrderConfig, WorkflowNodeExecutionRepository __all__ = [ - "RepositoryFactory", + "OrderConfig", "WorkflowNodeExecutionRepository", ] diff --git a/api/core/workflow/repositories/workflow_execution_repository.py b/api/core/workflow/repositories/workflow_execution_repository.py new file mode 100644 index 0000000000..5917310c8b --- /dev/null +++ b/api/core/workflow/repositories/workflow_execution_repository.py @@ -0,0 +1,42 @@ +from typing import Optional, Protocol + +from core.workflow.entities.workflow_execution import WorkflowExecution + + +class WorkflowExecutionRepository(Protocol): + """ + Repository interface for WorkflowExecution. + + This interface defines the contract for accessing and manipulating + WorkflowExecution data, regardless of the underlying storage mechanism. + + Note: Domain-specific concepts like multi-tenancy (tenant_id), application context (app_id), + and other implementation details should be handled at the implementation level, not in + the core interface. This keeps the core domain model clean and independent of specific + application domains or deployment scenarios. + """ + + def save(self, execution: WorkflowExecution) -> None: + """ + Save or update a WorkflowExecution instance. + + This method handles both creating new records and updating existing ones. + The implementation should determine whether to create or update based on + the execution's ID or other identifying fields. + + Args: + execution: The WorkflowExecution instance to save or update + """ + ... + + def get(self, execution_id: str) -> Optional[WorkflowExecution]: + """ + Retrieve a WorkflowExecution by its ID. + + Args: + execution_id: The workflow execution ID + + Returns: + The WorkflowExecution instance if found, None otherwise + """ + ... diff --git a/api/core/repository/workflow_node_execution_repository.py b/api/core/workflow/repositories/workflow_node_execution_repository.py similarity index 65% rename from api/core/repository/workflow_node_execution_repository.py rename to api/core/workflow/repositories/workflow_node_execution_repository.py index 9bb790cb0f..1908a6b190 100644 --- a/api/core/repository/workflow_node_execution_repository.py +++ b/api/core/workflow/repositories/workflow_node_execution_repository.py @@ -2,12 +2,12 @@ from collections.abc import Sequence from dataclasses import dataclass from typing import Literal, Optional, Protocol -from models.workflow import WorkflowNodeExecution +from core.workflow.entities.workflow_node_execution import WorkflowNodeExecution @dataclass class OrderConfig: - """Configuration for ordering WorkflowNodeExecution instances.""" + """Configuration for ordering NodeExecution instances.""" order_by: list[str] order_direction: Optional[Literal["asc", "desc"]] = None @@ -15,10 +15,10 @@ class OrderConfig: class WorkflowNodeExecutionRepository(Protocol): """ - Repository interface for WorkflowNodeExecution. + Repository interface for NodeExecution. This interface defines the contract for accessing and manipulating - WorkflowNodeExecution data, regardless of the underlying storage mechanism. + NodeExecution data, regardless of the underlying storage mechanism. Note: Domain-specific concepts like multi-tenancy (tenant_id), application context (app_id), and trigger sources (triggered_from) should be handled at the implementation level, not in @@ -28,22 +28,26 @@ class WorkflowNodeExecutionRepository(Protocol): def save(self, execution: WorkflowNodeExecution) -> None: """ - Save a WorkflowNodeExecution instance. + Save or update a NodeExecution instance. + + This method handles both creating new records and updating existing ones. + The implementation should determine whether to create or update based on + the execution's ID or other identifying fields. Args: - execution: The WorkflowNodeExecution instance to save + execution: The NodeExecution instance to save or update """ ... def get_by_node_execution_id(self, node_execution_id: str) -> Optional[WorkflowNodeExecution]: """ - Retrieve a WorkflowNodeExecution by its node_execution_id. + Retrieve a NodeExecution by its node_execution_id. Args: node_execution_id: The node execution ID Returns: - The WorkflowNodeExecution instance if found, None otherwise + The NodeExecution instance if found, None otherwise """ ... @@ -53,7 +57,7 @@ class WorkflowNodeExecutionRepository(Protocol): order_config: Optional[OrderConfig] = None, ) -> Sequence[WorkflowNodeExecution]: """ - Retrieve all WorkflowNodeExecution instances for a specific workflow run. + Retrieve all NodeExecution instances for a specific workflow run. Args: workflow_run_id: The workflow run ID @@ -62,34 +66,25 @@ class WorkflowNodeExecutionRepository(Protocol): order_config.order_direction: Direction to order ("asc" or "desc") Returns: - A list of WorkflowNodeExecution instances + A list of NodeExecution instances """ ... def get_running_executions(self, workflow_run_id: str) -> Sequence[WorkflowNodeExecution]: """ - Retrieve all running WorkflowNodeExecution instances for a specific workflow run. + Retrieve all running NodeExecution instances for a specific workflow run. Args: workflow_run_id: The workflow run ID Returns: - A list of running WorkflowNodeExecution instances - """ - ... - - def update(self, execution: WorkflowNodeExecution) -> None: - """ - Update an existing WorkflowNodeExecution instance. - - Args: - execution: The WorkflowNodeExecution instance to update + A list of running NodeExecution instances """ ... def clear(self) -> None: """ - Clear all WorkflowNodeExecution records based on implementation-specific criteria. + Clear all NodeExecution records based on implementation-specific criteria. This method is intended to be used for bulk deletion operations, such as removing all records associated with a specific app_id and tenant_id in multi-tenant implementations. diff --git a/api/core/workflow/utils/condition/entities.py b/api/core/workflow/utils/condition/entities.py index 799c735f54..56871a15d8 100644 --- a/api/core/workflow/utils/condition/entities.py +++ b/api/core/workflow/utils/condition/entities.py @@ -39,7 +39,7 @@ class SubCondition(BaseModel): class SubVariableCondition(BaseModel): logical_operator: Literal["and", "or"] - conditions: list[SubCondition] = Field(default=list) + conditions: list[SubCondition] = Field(default_factory=list) class Condition(BaseModel): diff --git a/api/core/workflow/utils/structured_output/entities.py b/api/core/workflow/utils/structured_output/entities.py index 7954acbaee..6491042bfe 100644 --- a/api/core/workflow/utils/structured_output/entities.py +++ b/api/core/workflow/utils/structured_output/entities.py @@ -14,11 +14,3 @@ class SpecialModelType(StrEnum): GEMINI = "gemini" OLLAMA = "ollama" - - -class SupportStructuredOutputStatus(StrEnum): - """Constants for structured output support status""" - - SUPPORTED = "supported" - UNSUPPORTED = "unsupported" - DISABLED = "disabled" diff --git a/api/core/workflow/workflow_cycle_manager.py b/api/core/workflow/workflow_cycle_manager.py new file mode 100644 index 0000000000..b88f9edd03 --- /dev/null +++ b/api/core/workflow/workflow_cycle_manager.py @@ -0,0 +1,376 @@ +from collections.abc import Mapping +from dataclasses import dataclass +from datetime import UTC, datetime +from typing import Any, Optional, Union +from uuid import uuid4 + +from core.app.entities.app_invoke_entities import AdvancedChatAppGenerateEntity, WorkflowAppGenerateEntity +from core.app.entities.queue_entities import ( + QueueNodeExceptionEvent, + QueueNodeFailedEvent, + QueueNodeInIterationFailedEvent, + QueueNodeInLoopFailedEvent, + QueueNodeRetryEvent, + QueueNodeStartedEvent, + QueueNodeSucceededEvent, +) +from core.app.task_pipeline.exc import WorkflowRunNotFoundError +from core.ops.entities.trace_entity import TraceTaskName +from core.ops.ops_trace_manager import TraceQueueManager, TraceTask +from core.workflow.entities.workflow_execution import WorkflowExecution, WorkflowExecutionStatus, WorkflowType +from core.workflow.entities.workflow_node_execution import ( + WorkflowNodeExecution, + WorkflowNodeExecutionMetadataKey, + WorkflowNodeExecutionStatus, +) +from core.workflow.enums import SystemVariableKey +from core.workflow.repositories.workflow_execution_repository import WorkflowExecutionRepository +from core.workflow.repositories.workflow_node_execution_repository import WorkflowNodeExecutionRepository +from core.workflow.workflow_entry import WorkflowEntry + + +@dataclass +class CycleManagerWorkflowInfo: + workflow_id: str + workflow_type: WorkflowType + version: str + graph_data: Mapping[str, Any] + + +class WorkflowCycleManager: + def __init__( + self, + *, + application_generate_entity: Union[AdvancedChatAppGenerateEntity, WorkflowAppGenerateEntity], + workflow_system_variables: dict[SystemVariableKey, Any], + workflow_info: CycleManagerWorkflowInfo, + workflow_execution_repository: WorkflowExecutionRepository, + workflow_node_execution_repository: WorkflowNodeExecutionRepository, + ) -> None: + self._application_generate_entity = application_generate_entity + self._workflow_system_variables = workflow_system_variables + self._workflow_info = workflow_info + self._workflow_execution_repository = workflow_execution_repository + self._workflow_node_execution_repository = workflow_node_execution_repository + + def handle_workflow_run_start(self) -> WorkflowExecution: + inputs = {**self._application_generate_entity.inputs} + for key, value in (self._workflow_system_variables or {}).items(): + if key.value == "conversation": + continue + inputs[f"sys.{key.value}"] = value + + # handle special values + inputs = dict(WorkflowEntry.handle_special_values(inputs) or {}) + + # init workflow run + # TODO: This workflow_run_id should always not be None, maybe we can use a more elegant way to handle this + execution_id = str(self._workflow_system_variables.get(SystemVariableKey.WORKFLOW_EXECUTION_ID) or uuid4()) + execution = WorkflowExecution.new( + id_=execution_id, + workflow_id=self._workflow_info.workflow_id, + workflow_type=self._workflow_info.workflow_type, + workflow_version=self._workflow_info.version, + graph=self._workflow_info.graph_data, + inputs=inputs, + started_at=datetime.now(UTC).replace(tzinfo=None), + ) + + self._workflow_execution_repository.save(execution) + + return execution + + def handle_workflow_run_success( + self, + *, + workflow_run_id: str, + total_tokens: int, + total_steps: int, + outputs: Mapping[str, Any] | None = None, + conversation_id: Optional[str] = None, + trace_manager: Optional[TraceQueueManager] = None, + ) -> WorkflowExecution: + workflow_execution = self._get_workflow_execution_or_raise_error(workflow_run_id) + + outputs = WorkflowEntry.handle_special_values(outputs) + + workflow_execution.status = WorkflowExecutionStatus.SUCCEEDED + workflow_execution.outputs = outputs or {} + workflow_execution.total_tokens = total_tokens + workflow_execution.total_steps = total_steps + workflow_execution.finished_at = datetime.now(UTC).replace(tzinfo=None) + + if trace_manager: + trace_manager.add_trace_task( + TraceTask( + TraceTaskName.WORKFLOW_TRACE, + workflow_execution=workflow_execution, + conversation_id=conversation_id, + user_id=trace_manager.user_id, + ) + ) + + self._workflow_execution_repository.save(workflow_execution) + return workflow_execution + + def handle_workflow_run_partial_success( + self, + *, + workflow_run_id: str, + total_tokens: int, + total_steps: int, + outputs: Mapping[str, Any] | None = None, + exceptions_count: int = 0, + conversation_id: Optional[str] = None, + trace_manager: Optional[TraceQueueManager] = None, + ) -> WorkflowExecution: + execution = self._get_workflow_execution_or_raise_error(workflow_run_id) + outputs = WorkflowEntry.handle_special_values(dict(outputs) if outputs else None) + + execution.status = WorkflowExecutionStatus.PARTIAL_SUCCEEDED + execution.outputs = outputs or {} + execution.total_tokens = total_tokens + execution.total_steps = total_steps + execution.finished_at = datetime.now(UTC).replace(tzinfo=None) + execution.exceptions_count = exceptions_count + + if trace_manager: + trace_manager.add_trace_task( + TraceTask( + TraceTaskName.WORKFLOW_TRACE, + workflow_execution=execution, + conversation_id=conversation_id, + user_id=trace_manager.user_id, + ) + ) + + self._workflow_execution_repository.save(execution) + return execution + + def handle_workflow_run_failed( + self, + *, + workflow_run_id: str, + total_tokens: int, + total_steps: int, + status: WorkflowExecutionStatus, + error_message: str, + conversation_id: Optional[str] = None, + trace_manager: Optional[TraceQueueManager] = None, + exceptions_count: int = 0, + ) -> WorkflowExecution: + workflow_execution = self._get_workflow_execution_or_raise_error(workflow_run_id) + + workflow_execution.status = WorkflowExecutionStatus(status.value) + workflow_execution.error_message = error_message + workflow_execution.total_tokens = total_tokens + workflow_execution.total_steps = total_steps + workflow_execution.finished_at = datetime.now(UTC).replace(tzinfo=None) + workflow_execution.exceptions_count = exceptions_count + + # Use the instance repository to find running executions for a workflow run + running_node_executions = self._workflow_node_execution_repository.get_running_executions( + workflow_run_id=workflow_execution.id_ + ) + + # Update the domain models + now = datetime.now(UTC).replace(tzinfo=None) + for node_execution in running_node_executions: + if node_execution.node_execution_id: + # Update the domain model + node_execution.status = WorkflowNodeExecutionStatus.FAILED + node_execution.error = error_message + node_execution.finished_at = now + node_execution.elapsed_time = (now - node_execution.created_at).total_seconds() + + # Update the repository with the domain model + self._workflow_node_execution_repository.save(node_execution) + + if trace_manager: + trace_manager.add_trace_task( + TraceTask( + TraceTaskName.WORKFLOW_TRACE, + workflow_execution=workflow_execution, + conversation_id=conversation_id, + user_id=trace_manager.user_id, + ) + ) + + self._workflow_execution_repository.save(workflow_execution) + return workflow_execution + + def handle_node_execution_start( + self, + *, + workflow_execution_id: str, + event: QueueNodeStartedEvent, + ) -> WorkflowNodeExecution: + workflow_execution = self._get_workflow_execution_or_raise_error(workflow_execution_id) + + # Create a domain model + created_at = datetime.now(UTC).replace(tzinfo=None) + metadata = { + WorkflowNodeExecutionMetadataKey.PARALLEL_MODE_RUN_ID: event.parallel_mode_run_id, + WorkflowNodeExecutionMetadataKey.ITERATION_ID: event.in_iteration_id, + WorkflowNodeExecutionMetadataKey.LOOP_ID: event.in_loop_id, + } + + domain_execution = WorkflowNodeExecution( + id=str(uuid4()), + workflow_id=workflow_execution.workflow_id, + workflow_execution_id=workflow_execution.id_, + predecessor_node_id=event.predecessor_node_id, + index=event.node_run_index, + node_execution_id=event.node_execution_id, + node_id=event.node_id, + node_type=event.node_type, + title=event.node_data.title, + status=WorkflowNodeExecutionStatus.RUNNING, + metadata=metadata, + created_at=created_at, + ) + + # Use the instance repository to save the domain model + self._workflow_node_execution_repository.save(domain_execution) + + return domain_execution + + def handle_workflow_node_execution_success(self, *, event: QueueNodeSucceededEvent) -> WorkflowNodeExecution: + # Get the domain model from repository + domain_execution = self._workflow_node_execution_repository.get_by_node_execution_id(event.node_execution_id) + if not domain_execution: + raise ValueError(f"Domain node execution not found: {event.node_execution_id}") + + # Process data + inputs = WorkflowEntry.handle_special_values(event.inputs) + process_data = WorkflowEntry.handle_special_values(event.process_data) + outputs = WorkflowEntry.handle_special_values(event.outputs) + + # Convert metadata keys to strings + execution_metadata_dict = {} + if event.execution_metadata: + for key, value in event.execution_metadata.items(): + execution_metadata_dict[key] = value + + finished_at = datetime.now(UTC).replace(tzinfo=None) + elapsed_time = (finished_at - event.start_at).total_seconds() + + # Update domain model + domain_execution.status = WorkflowNodeExecutionStatus.SUCCEEDED + domain_execution.update_from_mapping( + inputs=inputs, process_data=process_data, outputs=outputs, metadata=execution_metadata_dict + ) + domain_execution.finished_at = finished_at + domain_execution.elapsed_time = elapsed_time + + # Update the repository with the domain model + self._workflow_node_execution_repository.save(domain_execution) + + return domain_execution + + def handle_workflow_node_execution_failed( + self, + *, + event: QueueNodeFailedEvent + | QueueNodeInIterationFailedEvent + | QueueNodeInLoopFailedEvent + | QueueNodeExceptionEvent, + ) -> WorkflowNodeExecution: + """ + Workflow node execution failed + :param event: queue node failed event + :return: + """ + # Get the domain model from repository + domain_execution = self._workflow_node_execution_repository.get_by_node_execution_id(event.node_execution_id) + if not domain_execution: + raise ValueError(f"Domain node execution not found: {event.node_execution_id}") + + # Process data + inputs = WorkflowEntry.handle_special_values(event.inputs) + process_data = WorkflowEntry.handle_special_values(event.process_data) + outputs = WorkflowEntry.handle_special_values(event.outputs) + + # Convert metadata keys to strings + execution_metadata_dict = {} + if event.execution_metadata: + for key, value in event.execution_metadata.items(): + execution_metadata_dict[key] = value + + finished_at = datetime.now(UTC).replace(tzinfo=None) + elapsed_time = (finished_at - event.start_at).total_seconds() + + # Update domain model + domain_execution.status = ( + WorkflowNodeExecutionStatus.FAILED + if not isinstance(event, QueueNodeExceptionEvent) + else WorkflowNodeExecutionStatus.EXCEPTION + ) + domain_execution.error = event.error + domain_execution.update_from_mapping( + inputs=inputs, process_data=process_data, outputs=outputs, metadata=execution_metadata_dict + ) + domain_execution.finished_at = finished_at + domain_execution.elapsed_time = elapsed_time + + # Update the repository with the domain model + self._workflow_node_execution_repository.save(domain_execution) + + return domain_execution + + def handle_workflow_node_execution_retried( + self, *, workflow_execution_id: str, event: QueueNodeRetryEvent + ) -> WorkflowNodeExecution: + workflow_execution = self._get_workflow_execution_or_raise_error(workflow_execution_id) + created_at = event.start_at + finished_at = datetime.now(UTC).replace(tzinfo=None) + elapsed_time = (finished_at - created_at).total_seconds() + inputs = WorkflowEntry.handle_special_values(event.inputs) + outputs = WorkflowEntry.handle_special_values(event.outputs) + + # Convert metadata keys to strings + origin_metadata = { + WorkflowNodeExecutionMetadataKey.ITERATION_ID: event.in_iteration_id, + WorkflowNodeExecutionMetadataKey.PARALLEL_MODE_RUN_ID: event.parallel_mode_run_id, + WorkflowNodeExecutionMetadataKey.LOOP_ID: event.in_loop_id, + } + + # Convert execution metadata keys to strings + execution_metadata_dict: dict[WorkflowNodeExecutionMetadataKey, str | None] = {} + if event.execution_metadata: + for key, value in event.execution_metadata.items(): + execution_metadata_dict[key] = value + + merged_metadata = {**execution_metadata_dict, **origin_metadata} if execution_metadata_dict else origin_metadata + + # Create a domain model + domain_execution = WorkflowNodeExecution( + id=str(uuid4()), + workflow_id=workflow_execution.workflow_id, + workflow_execution_id=workflow_execution.id_, + predecessor_node_id=event.predecessor_node_id, + node_execution_id=event.node_execution_id, + node_id=event.node_id, + node_type=event.node_type, + title=event.node_data.title, + status=WorkflowNodeExecutionStatus.RETRY, + created_at=created_at, + finished_at=finished_at, + elapsed_time=elapsed_time, + error=event.error, + index=event.node_run_index, + ) + + # Update with mappings + domain_execution.update_from_mapping(inputs=inputs, outputs=outputs, metadata=merged_metadata) + + # Use the instance repository to save the domain model + self._workflow_node_execution_repository.save(domain_execution) + + return domain_execution + + def _get_workflow_execution_or_raise_error(self, id: str, /) -> WorkflowExecution: + execution = self._workflow_execution_repository.get(id) + if not execution: + raise WorkflowRunNotFoundError(id) + return execution diff --git a/api/core/workflow/workflow_entry.py b/api/core/workflow/workflow_entry.py index 50118a401c..7648947fca 100644 --- a/api/core/workflow/workflow_entry.py +++ b/api/core/workflow/workflow_entry.py @@ -9,6 +9,7 @@ from core.app.apps.base_app_queue_manager import GenerateTaskStoppedError from core.app.entities.app_invoke_entities import InvokeFrom from core.file.models import File from core.workflow.callbacks import WorkflowCallback +from core.workflow.constants import ENVIRONMENT_VARIABLE_NODE_ID from core.workflow.entities.variable_pool import VariablePool from core.workflow.errors import WorkflowNodeRunFailedError from core.workflow.graph_engine.entities.event import GraphEngineEvent, GraphRunFailedEvent, InNodeEvent @@ -364,4 +365,5 @@ class WorkflowEntry: input_value = file_factory.build_from_mappings(mappings=input_value, tenant_id=tenant_id) # append variable and value to variable pool - variable_pool.add([variable_node_id] + variable_key_list, input_value) + if variable_node_id != ENVIRONMENT_VARIABLE_NODE_ID: + variable_pool.add([variable_node_id] + variable_key_list, input_value) diff --git a/api/docker/entrypoint.sh b/api/docker/entrypoint.sh index 68f3c65a4b..18d4f4885d 100755 --- a/api/docker/entrypoint.sh +++ b/api/docker/entrypoint.sh @@ -20,7 +20,8 @@ if [[ "${MODE}" == "worker" ]]; then CONCURRENCY_OPTION="-c ${CELERY_WORKER_AMOUNT:-1}" fi - exec celery -A app.celery worker -P ${CELERY_WORKER_CLASS:-gevent} $CONCURRENCY_OPTION --loglevel ${LOG_LEVEL:-INFO} \ + exec celery -A app.celery worker -P ${CELERY_WORKER_CLASS:-gevent} $CONCURRENCY_OPTION \ + --max-tasks-per-child ${MAX_TASK_PRE_CHILD:-50} --loglevel ${LOG_LEVEL:-INFO} \ -Q ${CELERY_QUEUES:-dataset,mail,ops_trace,app_deletion} elif [[ "${MODE}" == "beat" ]]; then diff --git a/api/extensions/ext_celery.py b/api/extensions/ext_celery.py index 26bd6b3577..a837552007 100644 --- a/api/extensions/ext_celery.py +++ b/api/extensions/ext_celery.py @@ -70,6 +70,7 @@ def init_app(app: DifyApp) -> Celery: "schedule.update_tidb_serverless_status_task", "schedule.clean_messages", "schedule.mail_clean_document_notify_task", + "schedule.queue_monitor_task", ] day = dify_config.CELERY_BEAT_SCHEDULER_TIME beat_schedule = { @@ -98,6 +99,12 @@ def init_app(app: DifyApp) -> Celery: "task": "schedule.mail_clean_document_notify_task.mail_clean_document_notify_task", "schedule": crontab(minute="0", hour="10", day_of_week="1"), }, + "datasets-queue-monitor": { + "task": "schedule.queue_monitor_task.queue_monitor_task", + "schedule": timedelta( + minutes=dify_config.QUEUE_MONITOR_INTERVAL if dify_config.QUEUE_MONITOR_INTERVAL else 30 + ), + }, } celery_app.conf.update(beat_schedule=beat_schedule, imports=imports) diff --git a/api/extensions/ext_commands.py b/api/extensions/ext_commands.py index be43f55ea7..ddc2158a02 100644 --- a/api/extensions/ext_commands.py +++ b/api/extensions/ext_commands.py @@ -5,6 +5,7 @@ def init_app(app: DifyApp): from commands import ( add_qdrant_index, clear_free_plan_tenant_expired_logs, + clear_orphaned_file_records, convert_to_agent_apps, create_tenant, extract_plugins, @@ -13,6 +14,7 @@ def init_app(app: DifyApp): install_plugins, migrate_data_for_plugin, old_metadata_migration, + remove_orphaned_files_on_storage, reset_email, reset_encrypt_key_pair, reset_password, @@ -36,6 +38,8 @@ def init_app(app: DifyApp): install_plugins, old_metadata_migration, clear_free_plan_tenant_expired_logs, + clear_orphaned_file_records, + remove_orphaned_files_on_storage, ] for cmd in cmds_to_register: app.cli.add_command(cmd) diff --git a/api/extensions/ext_logging.py b/api/extensions/ext_logging.py index aa55862b7c..79d49aba5e 100644 --- a/api/extensions/ext_logging.py +++ b/api/extensions/ext_logging.py @@ -39,6 +39,10 @@ def init_app(app: DifyApp): handlers=log_handlers, force=True, ) + + # Apply RequestIdFormatter to all handlers + apply_request_id_formatter() + # Disable propagation for noisy loggers to avoid duplicate logs logging.getLogger("sqlalchemy.engine").propagate = False log_tz = dify_config.LOG_TZ @@ -74,3 +78,16 @@ class RequestIdFilter(logging.Filter): def filter(self, record): record.req_id = get_request_id() if flask.has_request_context() else "" return True + + +class RequestIdFormatter(logging.Formatter): + def format(self, record): + if not hasattr(record, "req_id"): + record.req_id = "" + return super().format(record) + + +def apply_request_id_formatter(): + for handler in logging.root.handlers: + if handler.formatter: + handler.formatter = RequestIdFormatter(dify_config.LOG_FORMAT, dify_config.LOG_DATEFORMAT) diff --git a/api/extensions/ext_login.py b/api/extensions/ext_login.py index 10fb89eb73..3b4d787d01 100644 --- a/api/extensions/ext_login.py +++ b/api/extensions/ext_login.py @@ -3,11 +3,14 @@ import json import flask_login # type: ignore from flask import Response, request from flask_login import user_loaded_from_request, user_logged_in -from werkzeug.exceptions import Unauthorized +from werkzeug.exceptions import NotFound, Unauthorized -import contexts +from configs import dify_config from dify_app import DifyApp +from extensions.ext_database import db from libs.passport import PassportService +from models.account import Account, Tenant, TenantAccountJoin +from models.model import EndUser from services.account_service import AccountService login_manager = flask_login.LoginManager() @@ -17,35 +20,72 @@ login_manager = flask_login.LoginManager() @login_manager.request_loader def load_user_from_request(request_from_flask_login): """Load user based on the request.""" - if request.blueprint not in {"console", "inner_api"}: - return None - # Check if the user_id contains a dot, indicating the old format auth_header = request.headers.get("Authorization", "") - if not auth_header: - auth_token = request.args.get("_token") - if not auth_token: - raise Unauthorized("Invalid Authorization token.") - else: + auth_token: str | None = None + if auth_header: if " " not in auth_header: raise Unauthorized("Invalid Authorization header format. Expected 'Bearer ' format.") - auth_scheme, auth_token = auth_header.split(None, 1) + auth_scheme, auth_token = auth_header.split(maxsplit=1) auth_scheme = auth_scheme.lower() if auth_scheme != "bearer": raise Unauthorized("Invalid Authorization header format. Expected 'Bearer ' format.") + else: + auth_token = request.args.get("_token") - decoded = PassportService().verify(auth_token) - user_id = decoded.get("user_id") + # Check for admin API key authentication first + if dify_config.ADMIN_API_KEY_ENABLE and auth_header: + admin_api_key = dify_config.ADMIN_API_KEY + if admin_api_key and admin_api_key == auth_token: + workspace_id = request.headers.get("X-WORKSPACE-ID") + if workspace_id: + tenant_account_join = ( + db.session.query(Tenant, TenantAccountJoin) + .filter(Tenant.id == workspace_id) + .filter(TenantAccountJoin.tenant_id == Tenant.id) + .filter(TenantAccountJoin.role == "owner") + .one_or_none() + ) + if tenant_account_join: + tenant, ta = tenant_account_join + account = db.session.query(Account).filter_by(id=ta.account_id).first() + if account: + account.current_tenant = tenant + return account - logged_in_account = AccountService.load_logged_in_account(account_id=user_id) - return logged_in_account + if request.blueprint in {"console", "inner_api"}: + if not auth_token: + raise Unauthorized("Invalid Authorization token.") + decoded = PassportService().verify(auth_token) + user_id = decoded.get("user_id") + source = decoded.get("token_source") + if source: + raise Unauthorized("Invalid Authorization token.") + if not user_id: + raise Unauthorized("Invalid Authorization token.") + + logged_in_account = AccountService.load_logged_in_account(account_id=user_id) + return logged_in_account + elif request.blueprint == "web": + decoded = PassportService().verify(auth_token) + end_user_id = decoded.get("end_user_id") + if not end_user_id: + raise Unauthorized("Invalid Authorization token.") + end_user = db.session.query(EndUser).filter(EndUser.id == decoded["end_user_id"]).first() + if not end_user: + raise NotFound("End user not found.") + return end_user @user_logged_in.connect @user_loaded_from_request.connect def on_user_logged_in(_sender, user): - """Called when a user logged in.""" - if user: - contexts.tenant_id.set(user.current_tenant_id) + """Called when a user logged in. + + Note: AccountService.load_logged_in_account will populate user.current_tenant_id + through the load_user method, which calls account.set_tenant_id(). + """ + # tenant_id context variable removed - using current_user.current_tenant_id directly + pass @login_manager.unauthorized_handler diff --git a/api/extensions/ext_mail.py b/api/extensions/ext_mail.py index 9240ebe7fc..84bc12eca0 100644 --- a/api/extensions/ext_mail.py +++ b/api/extensions/ext_mail.py @@ -26,7 +26,7 @@ class Mail: match mail_type: case "resend": - import resend # type: ignore + import resend api_key = dify_config.RESEND_API_KEY if not api_key: diff --git a/api/extensions/ext_otel.py b/api/extensions/ext_otel.py index a2edd832ec..6dcfa7bec6 100644 --- a/api/extensions/ext_otel.py +++ b/api/extensions/ext_otel.py @@ -6,163 +6,243 @@ import socket import sys from typing import Union +import flask from celery.signals import worker_init # type: ignore from flask_login import user_loaded_from_request, user_logged_in # type: ignore -from opentelemetry import trace -from opentelemetry.exporter.otlp.proto.http.metric_exporter import OTLPMetricExporter -from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter -from opentelemetry.instrumentation.celery import CeleryInstrumentor -from opentelemetry.instrumentation.flask import FlaskInstrumentor -from opentelemetry.instrumentation.sqlalchemy import SQLAlchemyInstrumentor -from opentelemetry.metrics import get_meter, get_meter_provider, set_meter_provider -from opentelemetry.propagate import set_global_textmap -from opentelemetry.propagators.b3 import B3Format -from opentelemetry.propagators.composite import CompositePropagator -from opentelemetry.sdk.metrics import MeterProvider -from opentelemetry.sdk.metrics.export import ConsoleMetricExporter, PeriodicExportingMetricReader -from opentelemetry.sdk.resources import Resource -from opentelemetry.sdk.trace import TracerProvider -from opentelemetry.sdk.trace.export import ( - BatchSpanProcessor, - ConsoleSpanExporter, -) -from opentelemetry.sdk.trace.sampling import ParentBasedTraceIdRatio -from opentelemetry.semconv.resource import ResourceAttributes -from opentelemetry.trace import Span, get_current_span, get_tracer_provider, set_tracer_provider -from opentelemetry.trace.propagation.tracecontext import TraceContextTextMapPropagator -from opentelemetry.trace.status import StatusCode from configs import dify_config from dify_app import DifyApp +from models import Account, EndUser @user_logged_in.connect @user_loaded_from_request.connect -def on_user_loaded(_sender, user): - if user: - current_span = get_current_span() - if current_span: - current_span.set_attribute("service.tenant.id", user.current_tenant_id) - current_span.set_attribute("service.user.id", user.id) +def on_user_loaded(_sender, user: Union["Account", "EndUser"]): + if dify_config.ENABLE_OTEL: + from opentelemetry.trace import get_current_span + + if user: + try: + current_span = get_current_span() + if isinstance(user, Account) and user.current_tenant_id: + tenant_id = user.current_tenant_id + elif isinstance(user, EndUser): + tenant_id = user.tenant_id + else: + return + if current_span: + current_span.set_attribute("service.tenant.id", tenant_id) + current_span.set_attribute("service.user.id", user.id) + except Exception: + logging.exception("Error setting tenant and user attributes") + pass def init_app(app: DifyApp): - if dify_config.ENABLE_OTEL: - setup_context_propagation() - # Initialize OpenTelemetry - # Follow Semantic Convertions 1.32.0 to define resource attributes - resource = Resource( - attributes={ - ResourceAttributes.SERVICE_NAME: dify_config.APPLICATION_NAME, - ResourceAttributes.SERVICE_VERSION: f"dify-{dify_config.CURRENT_VERSION}-{dify_config.COMMIT_SHA}", - ResourceAttributes.PROCESS_PID: os.getpid(), - ResourceAttributes.DEPLOYMENT_ENVIRONMENT: f"{dify_config.DEPLOY_ENV}-{dify_config.EDITION}", - ResourceAttributes.HOST_NAME: socket.gethostname(), - ResourceAttributes.HOST_ARCH: platform.machine(), - "custom.deployment.git_commit": dify_config.COMMIT_SHA, - ResourceAttributes.HOST_ID: platform.node(), - ResourceAttributes.OS_TYPE: platform.system().lower(), - ResourceAttributes.OS_DESCRIPTION: platform.platform(), - ResourceAttributes.OS_VERSION: platform.version(), - } + from opentelemetry.semconv.trace import SpanAttributes + + def is_celery_worker(): + return "celery" in sys.argv[0].lower() + + def instrument_exception_logging(): + exception_handler = ExceptionLoggingHandler() + logging.getLogger().addHandler(exception_handler) + + def init_flask_instrumentor(app: DifyApp): + meter = get_meter("http_metrics", version=dify_config.CURRENT_VERSION) + _http_response_counter = meter.create_counter( + "http.server.response.count", + description="Total number of HTTP responses by status code, method and target", + unit="{response}", + ) + + def response_hook(span: Span, status: str, response_headers: list): + if span and span.is_recording(): + try: + if status.startswith("2"): + span.set_status(StatusCode.OK) + else: + span.set_status(StatusCode.ERROR, status) + + status = status.split(" ")[0] + status_code = int(status) + status_class = f"{status_code // 100}xx" + attributes: dict[str, str | int] = {"status_code": status_code, "status_class": status_class} + request = flask.request + if request and request.url_rule: + attributes[SpanAttributes.HTTP_TARGET] = str(request.url_rule.rule) + if request and request.method: + attributes[SpanAttributes.HTTP_METHOD] = str(request.method) + _http_response_counter.add(1, attributes) + except Exception: + logging.exception("Error setting status and attributes") + pass + + instrumentor = FlaskInstrumentor() + if dify_config.DEBUG: + logging.info("Initializing Flask instrumentor") + instrumentor.instrument_app(app, response_hook=response_hook) + + def init_sqlalchemy_instrumentor(app: DifyApp): + with app.app_context(): + engines = list(app.extensions["sqlalchemy"].engines.values()) + SQLAlchemyInstrumentor().instrument(enable_commenter=True, engines=engines) + + def setup_context_propagation(): + # Configure propagators + set_global_textmap( + CompositePropagator( + [ + TraceContextTextMapPropagator(), # W3C trace context + B3Format(), # B3 propagation (used by many systems) + ] + ) ) - sampler = ParentBasedTraceIdRatio(dify_config.OTEL_SAMPLING_RATE) - provider = TracerProvider(resource=resource, sampler=sampler) - set_tracer_provider(provider) - exporter: Union[OTLPSpanExporter, ConsoleSpanExporter] - metric_exporter: Union[OTLPMetricExporter, ConsoleMetricExporter] - if dify_config.OTEL_EXPORTER_TYPE == "otlp": - exporter = OTLPSpanExporter( + + def shutdown_tracer(): + provider = trace.get_tracer_provider() + if hasattr(provider, "force_flush"): + provider.force_flush() + + class ExceptionLoggingHandler(logging.Handler): + """Custom logging handler that creates spans for logging.exception() calls""" + + def emit(self, record: logging.LogRecord): + try: + if record.exc_info: + tracer = get_tracer_provider().get_tracer("dify.exception.logging") + with tracer.start_as_current_span( + "log.exception", + attributes={ + "log.level": record.levelname, + "log.message": record.getMessage(), + "log.logger": record.name, + "log.file.path": record.pathname, + "log.file.line": record.lineno, + }, + ) as span: + span.set_status(StatusCode.ERROR) + if record.exc_info[1]: + span.record_exception(record.exc_info[1]) + span.set_attribute("exception.message", str(record.exc_info[1])) + if record.exc_info[0]: + span.set_attribute("exception.type", record.exc_info[0].__name__) + + except Exception: + pass + + from opentelemetry import trace + from opentelemetry.exporter.otlp.proto.grpc.metric_exporter import OTLPMetricExporter as GRPCMetricExporter + from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter as GRPCSpanExporter + from opentelemetry.exporter.otlp.proto.http.metric_exporter import OTLPMetricExporter as HTTPMetricExporter + from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter as HTTPSpanExporter + from opentelemetry.instrumentation.celery import CeleryInstrumentor + from opentelemetry.instrumentation.flask import FlaskInstrumentor + from opentelemetry.instrumentation.sqlalchemy import SQLAlchemyInstrumentor + from opentelemetry.metrics import get_meter, get_meter_provider, set_meter_provider + from opentelemetry.propagate import set_global_textmap + from opentelemetry.propagators.b3 import B3Format + from opentelemetry.propagators.composite import CompositePropagator + from opentelemetry.sdk.metrics import MeterProvider + from opentelemetry.sdk.metrics.export import ConsoleMetricExporter, PeriodicExportingMetricReader + from opentelemetry.sdk.resources import Resource + from opentelemetry.sdk.trace import TracerProvider + from opentelemetry.sdk.trace.export import ( + BatchSpanProcessor, + ConsoleSpanExporter, + ) + from opentelemetry.sdk.trace.sampling import ParentBasedTraceIdRatio + from opentelemetry.semconv.resource import ResourceAttributes + from opentelemetry.trace import Span, get_tracer_provider, set_tracer_provider + from opentelemetry.trace.propagation.tracecontext import TraceContextTextMapPropagator + from opentelemetry.trace.status import StatusCode + + setup_context_propagation() + # Initialize OpenTelemetry + # Follow Semantic Convertions 1.32.0 to define resource attributes + resource = Resource( + attributes={ + ResourceAttributes.SERVICE_NAME: dify_config.APPLICATION_NAME, + ResourceAttributes.SERVICE_VERSION: f"dify-{dify_config.CURRENT_VERSION}-{dify_config.COMMIT_SHA}", + ResourceAttributes.PROCESS_PID: os.getpid(), + ResourceAttributes.DEPLOYMENT_ENVIRONMENT: f"{dify_config.DEPLOY_ENV}-{dify_config.EDITION}", + ResourceAttributes.HOST_NAME: socket.gethostname(), + ResourceAttributes.HOST_ARCH: platform.machine(), + "custom.deployment.git_commit": dify_config.COMMIT_SHA, + ResourceAttributes.HOST_ID: platform.node(), + ResourceAttributes.OS_TYPE: platform.system().lower(), + ResourceAttributes.OS_DESCRIPTION: platform.platform(), + ResourceAttributes.OS_VERSION: platform.version(), + } + ) + sampler = ParentBasedTraceIdRatio(dify_config.OTEL_SAMPLING_RATE) + provider = TracerProvider(resource=resource, sampler=sampler) + set_tracer_provider(provider) + exporter: Union[GRPCSpanExporter, HTTPSpanExporter, ConsoleSpanExporter] + metric_exporter: Union[GRPCMetricExporter, HTTPMetricExporter, ConsoleMetricExporter] + protocol = (dify_config.OTEL_EXPORTER_OTLP_PROTOCOL or "").lower() + if dify_config.OTEL_EXPORTER_TYPE == "otlp": + if protocol == "grpc": + exporter = GRPCSpanExporter( + endpoint=dify_config.OTLP_BASE_ENDPOINT, + # Header field names must consist of lowercase letters, check RFC7540 + headers=(("authorization", f"Bearer {dify_config.OTLP_API_KEY}"),), + insecure=True, + ) + metric_exporter = GRPCMetricExporter( + endpoint=dify_config.OTLP_BASE_ENDPOINT, + headers=(("authorization", f"Bearer {dify_config.OTLP_API_KEY}"),), + insecure=True, + ) + else: + exporter = HTTPSpanExporter( endpoint=dify_config.OTLP_BASE_ENDPOINT + "/v1/traces", headers={"Authorization": f"Bearer {dify_config.OTLP_API_KEY}"}, ) - metric_exporter = OTLPMetricExporter( + metric_exporter = HTTPMetricExporter( endpoint=dify_config.OTLP_BASE_ENDPOINT + "/v1/metrics", headers={"Authorization": f"Bearer {dify_config.OTLP_API_KEY}"}, ) - else: - # Fallback to console exporter - exporter = ConsoleSpanExporter() - metric_exporter = ConsoleMetricExporter() - - provider.add_span_processor( - BatchSpanProcessor( - exporter, - max_queue_size=dify_config.OTEL_MAX_QUEUE_SIZE, - schedule_delay_millis=dify_config.OTEL_BATCH_EXPORT_SCHEDULE_DELAY, - max_export_batch_size=dify_config.OTEL_MAX_EXPORT_BATCH_SIZE, - export_timeout_millis=dify_config.OTEL_BATCH_EXPORT_TIMEOUT, - ) + else: + exporter = ConsoleSpanExporter() + metric_exporter = ConsoleMetricExporter() + + provider.add_span_processor( + BatchSpanProcessor( + exporter, + max_queue_size=dify_config.OTEL_MAX_QUEUE_SIZE, + schedule_delay_millis=dify_config.OTEL_BATCH_EXPORT_SCHEDULE_DELAY, + max_export_batch_size=dify_config.OTEL_MAX_EXPORT_BATCH_SIZE, + export_timeout_millis=dify_config.OTEL_BATCH_EXPORT_TIMEOUT, ) - reader = PeriodicExportingMetricReader( - metric_exporter, - export_interval_millis=dify_config.OTEL_METRIC_EXPORT_INTERVAL, - export_timeout_millis=dify_config.OTEL_METRIC_EXPORT_TIMEOUT, - ) - set_meter_provider(MeterProvider(resource=resource, metric_readers=[reader])) - if not is_celery_worker(): - init_flask_instrumentor(app) - CeleryInstrumentor(tracer_provider=get_tracer_provider(), meter_provider=get_meter_provider()).instrument() - init_sqlalchemy_instrumentor(app) - atexit.register(shutdown_tracer) - - -def is_celery_worker(): - return "celery" in sys.argv[0].lower() - - -def init_flask_instrumentor(app: DifyApp): - meter = get_meter("http_metrics", version=dify_config.CURRENT_VERSION) - _http_response_counter = meter.create_counter( - "http.server.response.count", description="Total number of HTTP responses by status code", unit="{response}" ) - - def response_hook(span: Span, status: str, response_headers: list): - if span and span.is_recording(): - if status.startswith("2"): - span.set_status(StatusCode.OK) - else: - span.set_status(StatusCode.ERROR, status) - - status = status.split(" ")[0] - status_code = int(status) - status_class = f"{status_code // 100}xx" - _http_response_counter.add(1, {"status_code": status_code, "status_class": status_class}) - - instrumentor = FlaskInstrumentor() - if dify_config.DEBUG: - logging.info("Initializing Flask instrumentor") - instrumentor.instrument_app(app, response_hook=response_hook) - - -def init_sqlalchemy_instrumentor(app: DifyApp): - with app.app_context(): - engines = list(app.extensions["sqlalchemy"].engines.values()) - SQLAlchemyInstrumentor().instrument(enable_commenter=True, engines=engines) - - -def setup_context_propagation(): - # Configure propagators - set_global_textmap( - CompositePropagator( - [ - TraceContextTextMapPropagator(), # W3C trace context - B3Format(), # B3 propagation (used by many systems) - ] - ) + reader = PeriodicExportingMetricReader( + metric_exporter, + export_interval_millis=dify_config.OTEL_METRIC_EXPORT_INTERVAL, + export_timeout_millis=dify_config.OTEL_METRIC_EXPORT_TIMEOUT, ) + set_meter_provider(MeterProvider(resource=resource, metric_readers=[reader])) + if not is_celery_worker(): + init_flask_instrumentor(app) + CeleryInstrumentor(tracer_provider=get_tracer_provider(), meter_provider=get_meter_provider()).instrument() + instrument_exception_logging() + init_sqlalchemy_instrumentor(app) + atexit.register(shutdown_tracer) -@worker_init.connect(weak=False) -def init_celery_worker(*args, **kwargs): - tracer_provider = get_tracer_provider() - metric_provider = get_meter_provider() - if dify_config.DEBUG: - logging.info("Initializing OpenTelemetry for Celery worker") - CeleryInstrumentor(tracer_provider=tracer_provider, meter_provider=metric_provider).instrument() +def is_enabled(): + return dify_config.ENABLE_OTEL -def shutdown_tracer(): - provider = trace.get_tracer_provider() - if hasattr(provider, "force_flush"): - provider.force_flush() +@worker_init.connect(weak=False) +def init_celery_worker(*args, **kwargs): + if dify_config.ENABLE_OTEL: + from opentelemetry.instrumentation.celery import CeleryInstrumentor + from opentelemetry.metrics import get_meter_provider + from opentelemetry.trace import get_tracer_provider + + tracer_provider = get_tracer_provider() + metric_provider = get_meter_provider() + if dify_config.DEBUG: + logging.info("Initializing OpenTelemetry for Celery worker") + CeleryInstrumentor(tracer_provider=tracer_provider, meter_provider=metric_provider).instrument() diff --git a/api/extensions/ext_otel_patch.py b/api/extensions/ext_otel_patch.py deleted file mode 100644 index 58309fe4d1..0000000000 --- a/api/extensions/ext_otel_patch.py +++ /dev/null @@ -1,63 +0,0 @@ -""" -Patch for OpenTelemetry context detach method to handle None tokens gracefully. - -This patch addresses the issue where OpenTelemetry's context.detach() method raises a TypeError -when called with a None token. The error occurs in the contextvars_context.py file where it tries -to call reset() on a None token. - -Related GitHub issue: https://github.com/langgenius/dify/issues/18496 - -Error being fixed: -``` -Traceback (most recent call last): - File "opentelemetry/context/__init__.py", line 154, in detach - _RUNTIME_CONTEXT.detach(token) - File "opentelemetry/context/contextvars_context.py", line 50, in detach - self._current_context.reset(token) # type: ignore - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -TypeError: expected an instance of Token, got None -``` - -Instead of modifying the third-party package directly, this patch monkey-patches the -context.detach method to gracefully handle None tokens. -""" - -import logging -from functools import wraps - -from opentelemetry import context - -logger = logging.getLogger(__name__) - -# Store the original detach method -original_detach = context.detach - - -# Create a patched version that handles None tokens -@wraps(original_detach) -def patched_detach(token): - """ - A patched version of context.detach that handles None tokens gracefully. - """ - if token is None: - logger.debug("Attempted to detach a None token, skipping") - return - - return original_detach(token) - - -def is_enabled(): - """ - Check if the extension is enabled. - Always enable this patch to prevent errors even when OpenTelemetry is disabled. - """ - return True - - -def init_app(app): - """ - Initialize the OpenTelemetry context patch. - """ - # Replace the original detach method with our patched version - context.detach = patched_detach - logger.info("OpenTelemetry context.detach patched to handle None tokens") diff --git a/api/extensions/ext_redis.py b/api/extensions/ext_redis.py index f8679f7e4b..c283b1b7ca 100644 --- a/api/extensions/ext_redis.py +++ b/api/extensions/ext_redis.py @@ -1,6 +1,7 @@ from typing import Any, Union import redis +from redis.cache import CacheConfig from redis.cluster import ClusterNode, RedisCluster from redis.connection import Connection, SSLConnection from redis.sentinel import Sentinel @@ -51,6 +52,14 @@ def init_app(app: DifyApp): connection_class: type[Union[Connection, SSLConnection]] = Connection if dify_config.REDIS_USE_SSL: connection_class = SSLConnection + resp_protocol = dify_config.REDIS_SERIALIZATION_PROTOCOL + if dify_config.REDIS_ENABLE_CLIENT_SIDE_CACHE: + if resp_protocol >= 3: + clientside_cache_config = CacheConfig() + else: + raise ValueError("Client side cache is only supported in RESP3") + else: + clientside_cache_config = None redis_params: dict[str, Any] = { "username": dify_config.REDIS_USERNAME, @@ -59,6 +68,8 @@ def init_app(app: DifyApp): "encoding": "utf-8", "encoding_errors": "strict", "decode_responses": False, + "protocol": resp_protocol, + "cache_config": clientside_cache_config, } if dify_config.REDIS_USE_SENTINEL: @@ -82,14 +93,22 @@ def init_app(app: DifyApp): ClusterNode(host=node.split(":")[0], port=int(node.split(":")[1])) for node in dify_config.REDIS_CLUSTERS.split(",") ] - # FIXME: mypy error here, try to figure out how to fix it - redis_client.initialize(RedisCluster(startup_nodes=nodes, password=dify_config.REDIS_CLUSTERS_PASSWORD)) # type: ignore + redis_client.initialize( + RedisCluster( + startup_nodes=nodes, + password=dify_config.REDIS_CLUSTERS_PASSWORD, + protocol=resp_protocol, + cache_config=clientside_cache_config, + ) + ) else: redis_params.update( { "host": dify_config.REDIS_HOST, "port": dify_config.REDIS_PORT, "connection_class": connection_class, + "protocol": resp_protocol, + "cache_config": clientside_cache_config, } ) pool = redis.ConnectionPool(**redis_params) diff --git a/api/extensions/ext_repositories.py b/api/extensions/ext_repositories.py deleted file mode 100644 index 27d8408ec1..0000000000 --- a/api/extensions/ext_repositories.py +++ /dev/null @@ -1,18 +0,0 @@ -""" -Extension for initializing repositories. - -This extension registers repository implementations with the RepositoryFactory. -""" - -from dify_app import DifyApp -from repositories.repository_registry import register_repositories - - -def init_app(_app: DifyApp) -> None: - """ - Initialize repository implementations. - - Args: - _app: The Flask application instance (unused) - """ - register_repositories() diff --git a/api/extensions/ext_request_logging.py b/api/extensions/ext_request_logging.py new file mode 100644 index 0000000000..7c69483e0f --- /dev/null +++ b/api/extensions/ext_request_logging.py @@ -0,0 +1,73 @@ +import json +import logging + +import flask +import werkzeug.http +from flask import Flask +from flask.signals import request_finished, request_started + +from configs import dify_config + +_logger = logging.getLogger(__name__) + + +def _is_content_type_json(content_type: str) -> bool: + if not content_type: + return False + content_type_no_option, _ = werkzeug.http.parse_options_header(content_type) + return content_type_no_option.lower() == "application/json" + + +def _log_request_started(_sender, **_extra): + """Log the start of a request.""" + if not _logger.isEnabledFor(logging.DEBUG): + return + + request = flask.request + if not (_is_content_type_json(request.content_type) and request.data): + _logger.debug("Received Request %s -> %s", request.method, request.path) + return + try: + json_data = json.loads(request.data) + except (TypeError, ValueError): + _logger.exception("Failed to parse JSON request") + return + formatted_json = json.dumps(json_data, ensure_ascii=False, indent=2) + _logger.debug( + "Received Request %s -> %s, Request Body:\n%s", + request.method, + request.path, + formatted_json, + ) + + +def _log_request_finished(_sender, response, **_extra): + """Log the end of a request.""" + if not _logger.isEnabledFor(logging.DEBUG) or response is None: + return + + if not _is_content_type_json(response.content_type): + _logger.debug("Response %s %s", response.status, response.content_type) + return + + response_data = response.get_data(as_text=True) + try: + json_data = json.loads(response_data) + except (TypeError, ValueError): + _logger.exception("Failed to parse JSON response") + return + formatted_json = json.dumps(json_data, ensure_ascii=False, indent=2) + _logger.debug( + "Response %s %s, Response Body:\n%s", + response.status, + response.content_type, + formatted_json, + ) + + +def init_app(app: Flask): + """Initialize the request logging extension.""" + if not dify_config.ENABLE_REQUEST_LOGGING: + return + request_started.connect(_log_request_started, app) + request_finished.connect(_log_request_finished, app) diff --git a/api/extensions/ext_storage.py b/api/extensions/ext_storage.py index 4c811c66ba..bd35278544 100644 --- a/api/extensions/ext_storage.py +++ b/api/extensions/ext_storage.py @@ -102,6 +102,9 @@ class Storage: def delete(self, filename): return self.storage_runner.delete(filename) + def scan(self, path: str, files: bool = True, directories: bool = False) -> list[str]: + return self.storage_runner.scan(path, files=files, directories=directories) + storage = Storage() diff --git a/api/extensions/storage/base_storage.py b/api/extensions/storage/base_storage.py index 0dedd7ff8c..0393206e54 100644 --- a/api/extensions/storage/base_storage.py +++ b/api/extensions/storage/base_storage.py @@ -30,3 +30,11 @@ class BaseStorage(ABC): @abstractmethod def delete(self, filename): raise NotImplementedError + + def scan(self, path, files=True, directories=False) -> list[str]: + """ + Scan files and directories in the given path. + This method is implemented only in some storage backends. + If a storage backend doesn't support scanning, it will raise NotImplementedError. + """ + raise NotImplementedError("This storage backend doesn't support scanning") diff --git a/api/extensions/storage/opendal_storage.py b/api/extensions/storage/opendal_storage.py index ee8cfa9179..12e2738e9d 100644 --- a/api/extensions/storage/opendal_storage.py +++ b/api/extensions/storage/opendal_storage.py @@ -80,3 +80,20 @@ class OpenDALStorage(BaseStorage): logger.debug(f"file {filename} deleted") return logger.debug(f"file {filename} not found, skip delete") + + def scan(self, path: str, files: bool = True, directories: bool = False) -> list[str]: + if not self.exists(path): + raise FileNotFoundError("Path not found") + + all_files = self.op.scan(path=path) + if files and directories: + logger.debug(f"files and directories on {path} scanned") + return [f.path for f in all_files] + if files: + logger.debug(f"files on {path} scanned") + return [f.path for f in all_files if not f.path.endswith("/")] + elif directories: + logger.debug(f"directories on {path} scanned") + return [f.path for f in all_files if f.path.endswith("/")] + else: + raise ValueError("At least one of files or directories must be True") diff --git a/api/factories/agent_factory.py b/api/factories/agent_factory.py index 4b2d2cc769..4b12afb528 100644 --- a/api/factories/agent_factory.py +++ b/api/factories/agent_factory.py @@ -1,12 +1,12 @@ from core.agent.strategy.plugin import PluginAgentStrategy -from core.plugin.manager.agent import PluginAgentManager +from core.plugin.impl.agent import PluginAgentClient def get_plugin_agent_strategy( tenant_id: str, agent_strategy_provider_name: str, agent_strategy_name: str ) -> PluginAgentStrategy: # TODO: use contexts to cache the agent provider - manager = PluginAgentManager() + manager = PluginAgentClient() agent_provider = manager.fetch_agent_strategy_provider(tenant_id, agent_strategy_provider_name) for agent_strategy in agent_provider.declaration.strategies: if agent_strategy.identity.name == agent_strategy_name: diff --git a/api/factories/variable_factory.py b/api/factories/variable_factory.py index bbca8448ec..a41ef4ae4e 100644 --- a/api/factories/variable_factory.py +++ b/api/factories/variable_factory.py @@ -39,10 +39,6 @@ from core.variables.variables import ( from core.workflow.constants import CONVERSATION_VARIABLE_NODE_ID, ENVIRONMENT_VARIABLE_NODE_ID -class InvalidSelectorError(ValueError): - pass - - class UnsupportedSegmentTypeError(Exception): pass @@ -84,8 +80,8 @@ def _build_variable_from_mapping(*, mapping: Mapping[str, Any], selector: Sequen raise VariableError("missing value type") if (value := mapping.get("value")) is None: raise VariableError("missing value") - # FIXME: using Any here, fix it later - result: Any + + result: Variable match value_type: case SegmentType.STRING: result = StringVariable.model_validate(mapping) diff --git a/api/fields/annotation_fields.py b/api/fields/annotation_fields.py index 1c58b3a257..379dcc6d16 100644 --- a/api/fields/annotation_fields.py +++ b/api/fields/annotation_fields.py @@ -1,4 +1,4 @@ -from flask_restful import fields # type: ignore +from flask_restful import fields from libs.helper import TimestampField diff --git a/api/fields/api_based_extension_fields.py b/api/fields/api_based_extension_fields.py index d40407bfcc..a85d4a34db 100644 --- a/api/fields/api_based_extension_fields.py +++ b/api/fields/api_based_extension_fields.py @@ -1,4 +1,4 @@ -from flask_restful import fields # type: ignore +from flask_restful import fields from libs.helper import TimestampField diff --git a/api/fields/app_fields.py b/api/fields/app_fields.py index f42364f110..500ca47c7e 100644 --- a/api/fields/app_fields.py +++ b/api/fields/app_fields.py @@ -1,4 +1,4 @@ -from flask_restful import fields # type: ignore +from flask_restful import fields from fields.workflow_fields import workflow_partial_fields from libs.helper import AppIconUrlField, TimestampField @@ -63,6 +63,7 @@ app_detail_fields = { "created_at": TimestampField, "updated_by": fields.String, "updated_at": TimestampField, + "access_mode": fields.String, } prompt_config_fields = { @@ -98,6 +99,9 @@ app_partial_fields = { "updated_by": fields.String, "updated_at": TimestampField, "tags": fields.List(fields.Nested(tag_fields)), + "access_mode": fields.String, + "create_user_name": fields.String, + "author_name": fields.String, } @@ -176,6 +180,7 @@ app_detail_fields_with_site = { "updated_by": fields.String, "updated_at": TimestampField, "deleted_tools": fields.List(fields.Nested(deleted_tool_fields)), + "access_mode": fields.String, } diff --git a/api/fields/conversation_fields.py b/api/fields/conversation_fields.py index 78e0794833..370e8a5a58 100644 --- a/api/fields/conversation_fields.py +++ b/api/fields/conversation_fields.py @@ -1,4 +1,4 @@ -from flask_restful import fields # type: ignore +from flask_restful import fields from fields.member_fields import simple_account_fields from libs.helper import TimestampField diff --git a/api/fields/conversation_variable_fields.py b/api/fields/conversation_variable_fields.py index c6385efb5a..71785e7d67 100644 --- a/api/fields/conversation_variable_fields.py +++ b/api/fields/conversation_variable_fields.py @@ -1,4 +1,4 @@ -from flask_restful import fields # type: ignore +from flask_restful import fields from libs.helper import TimestampField @@ -19,3 +19,9 @@ paginated_conversation_variable_fields = { "has_more": fields.Boolean, "data": fields.List(fields.Nested(conversation_variable_fields), attribute="data"), } + +conversation_variable_infinite_scroll_pagination_fields = { + "limit": fields.Integer, + "has_more": fields.Boolean, + "data": fields.List(fields.Nested(conversation_variable_fields)), +} diff --git a/api/fields/data_source_fields.py b/api/fields/data_source_fields.py index 608672121e..071071376f 100644 --- a/api/fields/data_source_fields.py +++ b/api/fields/data_source_fields.py @@ -1,4 +1,4 @@ -from flask_restful import fields # type: ignore +from flask_restful import fields from libs.helper import TimestampField diff --git a/api/fields/dataset_fields.py b/api/fields/dataset_fields.py index 67d183c70d..32a88cc5db 100644 --- a/api/fields/dataset_fields.py +++ b/api/fields/dataset_fields.py @@ -1,4 +1,4 @@ -from flask_restful import fields # type: ignore +from flask_restful import fields from libs.helper import TimestampField diff --git a/api/fields/document_fields.py b/api/fields/document_fields.py index 6d59ee9baa..7fd43e8dbe 100644 --- a/api/fields/document_fields.py +++ b/api/fields/document_fields.py @@ -1,4 +1,4 @@ -from flask_restful import fields # type: ignore +from flask_restful import fields from fields.dataset_fields import dataset_fields from libs.helper import TimestampField diff --git a/api/fields/end_user_fields.py b/api/fields/end_user_fields.py index aefa0b2758..99e529f9d1 100644 --- a/api/fields/end_user_fields.py +++ b/api/fields/end_user_fields.py @@ -1,4 +1,4 @@ -from flask_restful import fields # type: ignore +from flask_restful import fields simple_end_user_fields = { "id": fields.String, diff --git a/api/fields/file_fields.py b/api/fields/file_fields.py index dfc1b623d5..8b4839ef97 100644 --- a/api/fields/file_fields.py +++ b/api/fields/file_fields.py @@ -1,4 +1,4 @@ -from flask_restful import fields # type: ignore +from flask_restful import fields from libs.helper import TimestampField diff --git a/api/fields/hit_testing_fields.py b/api/fields/hit_testing_fields.py index 4514c1b8ca..9d67999ea4 100644 --- a/api/fields/hit_testing_fields.py +++ b/api/fields/hit_testing_fields.py @@ -1,4 +1,4 @@ -from flask_restful import fields # type: ignore +from flask_restful import fields from libs.helper import TimestampField diff --git a/api/fields/installed_app_fields.py b/api/fields/installed_app_fields.py index 16f265b9bb..e0b3e340f6 100644 --- a/api/fields/installed_app_fields.py +++ b/api/fields/installed_app_fields.py @@ -1,4 +1,4 @@ -from flask_restful import fields # type: ignore +from flask_restful import fields from libs.helper import AppIconUrlField, TimestampField diff --git a/api/fields/member_fields.py b/api/fields/member_fields.py index 0900bffb8a..8007b7e052 100644 --- a/api/fields/member_fields.py +++ b/api/fields/member_fields.py @@ -1,4 +1,4 @@ -from flask_restful import fields # type: ignore +from flask_restful import fields from libs.helper import AvatarUrlField, TimestampField diff --git a/api/fields/message_fields.py b/api/fields/message_fields.py index 76e61f0707..e6aebd810f 100644 --- a/api/fields/message_fields.py +++ b/api/fields/message_fields.py @@ -1,4 +1,4 @@ -from flask_restful import fields # type: ignore +from flask_restful import fields from fields.conversation_fields import message_file_fields from libs.helper import TimestampField diff --git a/api/fields/raws.py b/api/fields/raws.py index 493d4b6cce..15ec16ab13 100644 --- a/api/fields/raws.py +++ b/api/fields/raws.py @@ -1,4 +1,4 @@ -from flask_restful import fields # type: ignore +from flask_restful import fields from core.file import File diff --git a/api/fields/segment_fields.py b/api/fields/segment_fields.py index 82311e5bb9..4126c24598 100644 --- a/api/fields/segment_fields.py +++ b/api/fields/segment_fields.py @@ -1,4 +1,4 @@ -from flask_restful import fields # type: ignore +from flask_restful import fields from libs.helper import TimestampField diff --git a/api/fields/tag_fields.py b/api/fields/tag_fields.py index 986cd725f7..9af4fc57dd 100644 --- a/api/fields/tag_fields.py +++ b/api/fields/tag_fields.py @@ -1,3 +1,3 @@ -from flask_restful import fields # type: ignore +from flask_restful import fields tag_fields = {"id": fields.String, "name": fields.String, "type": fields.String, "binding_count": fields.String} diff --git a/api/fields/workflow_app_log_fields.py b/api/fields/workflow_app_log_fields.py index e8f8684ae0..823c99ec6b 100644 --- a/api/fields/workflow_app_log_fields.py +++ b/api/fields/workflow_app_log_fields.py @@ -1,4 +1,4 @@ -from flask_restful import fields # type: ignore +from flask_restful import fields from fields.end_user_fields import simple_end_user_fields from fields.member_fields import simple_account_fields diff --git a/api/fields/workflow_fields.py b/api/fields/workflow_fields.py index 971e99c259..9f1bef3b36 100644 --- a/api/fields/workflow_fields.py +++ b/api/fields/workflow_fields.py @@ -1,4 +1,4 @@ -from flask_restful import fields # type: ignore +from flask_restful import fields from core.helper import encrypter from core.variables import SecretVariable, SegmentType, Variable diff --git a/api/fields/workflow_run_fields.py b/api/fields/workflow_run_fields.py index ef59c57ec3..74fdf8bd97 100644 --- a/api/fields/workflow_run_fields.py +++ b/api/fields/workflow_run_fields.py @@ -1,4 +1,4 @@ -from flask_restful import fields # type: ignore +from flask_restful import fields from fields.end_user_fields import simple_end_user_fields from fields.member_fields import simple_account_fields diff --git a/api/libs/external_api.py b/api/libs/external_api.py index 922d2d9cd3..2070df3e55 100644 --- a/api/libs/external_api.py +++ b/api/libs/external_api.py @@ -3,7 +3,7 @@ import sys from typing import Any from flask import current_app, got_request_exception -from flask_restful import Api, http_status_message # type: ignore +from flask_restful import Api, http_status_message from werkzeug.datastructures import Headers from werkzeug.exceptions import HTTPException diff --git a/api/libs/helper.py b/api/libs/helper.py index f0325734d8..3f2a630956 100644 --- a/api/libs/helper.py +++ b/api/libs/helper.py @@ -1,8 +1,9 @@ import json import logging -import random import re +import secrets import string +import struct import subprocess import time import uuid @@ -13,11 +14,13 @@ from typing import TYPE_CHECKING, Any, Optional, Union, cast from zoneinfo import available_timezones from flask import Response, stream_with_context -from flask_restful import fields # type: ignore +from flask_restful import fields +from pydantic import BaseModel from configs import dify_config from core.app.features.rate_limiting.rate_limit import RateLimitGenerator from core.file import helpers as file_helpers +from core.model_runtime.utils.encoders import jsonable_encoder from extensions.ext_redis import redis_client if TYPE_CHECKING: @@ -175,14 +178,14 @@ def generate_string(n): letters_digits = string.ascii_letters + string.digits result = "" for i in range(n): - result += random.choice(letters_digits) + result += secrets.choice(letters_digits) return result def extract_remote_ip(request) -> str: if request.headers.get("CF-Connecting-IP"): - return cast(str, request.headers.get("Cf-Connecting-Ip")) + return cast(str, request.headers.get("CF-Connecting-IP")) elif request.headers.getlist("X-Forwarded-For"): return cast(str, request.headers.getlist("X-Forwarded-For")[0]) else: @@ -196,7 +199,7 @@ def generate_text_hash(text: str) -> str: def compact_generate_response(response: Union[Mapping, Generator, RateLimitGenerator]) -> Response: if isinstance(response, dict): - return Response(response=json.dumps(response), status=200, mimetype="application/json") + return Response(response=json.dumps(jsonable_encoder(response)), status=200, mimetype="application/json") else: def generate() -> Generator: @@ -205,6 +208,60 @@ def compact_generate_response(response: Union[Mapping, Generator, RateLimitGener return Response(stream_with_context(generate()), status=200, mimetype="text/event-stream") +def length_prefixed_response(magic_number: int, response: Union[Mapping, Generator, RateLimitGenerator]) -> Response: + """ + This function is used to return a response with a length prefix. + Magic number is a one byte number that indicates the type of the response. + + For a compatibility with latest plugin daemon https://github.com/langgenius/dify-plugin-daemon/pull/341 + Avoid using line-based response, it leads a memory issue. + + We uses following format: + | Field | Size | Description | + |---------------|----------|---------------------------------| + | Magic Number | 1 byte | Magic number identifier | + | Reserved | 1 byte | Reserved field | + | Header Length | 2 bytes | Header length (usually 0xa) | + | Data Length | 4 bytes | Length of the data | + | Reserved | 6 bytes | Reserved fields | + | Data | Variable | Actual data content | + + | Reserved Fields | Header | Data | + |-----------------|----------|----------| + | 4 bytes total | Variable | Variable | + + all data is in little endian + """ + + def pack_response_with_length_prefix(response: bytes) -> bytes: + header_length = 0xA + data_length = len(response) + # | Magic Number 1byte | Reserved 1byte | Header Length 2bytes | Data Length 4bytes | Reserved 6bytes | Data + return struct.pack(" Generator: + for chunk in response: + if isinstance(chunk, str): + yield pack_response_with_length_prefix(chunk.encode("utf-8")) + else: + yield pack_response_with_length_prefix(chunk) + + return Response(stream_with_context(generate()), status=200, mimetype="text/event-stream") + + class TokenManager: @classmethod def generate_token( diff --git a/api/libs/login.py b/api/libs/login.py index be9478e850..e3a7fe2948 100644 --- a/api/libs/login.py +++ b/api/libs/login.py @@ -2,14 +2,11 @@ from functools import wraps from typing import Any from flask import current_app, g, has_request_context, request -from flask_login import user_logged_in # type: ignore from flask_login.config import EXEMPT_METHODS # type: ignore -from werkzeug.exceptions import Unauthorized from werkzeug.local import LocalProxy from configs import dify_config -from extensions.ext_database import db -from models.account import Account, Tenant, TenantAccountJoin +from models.account import Account from models.model import EndUser #: A proxy for the current user. If no user is logged in, this will be an @@ -53,36 +50,6 @@ def login_required(func): @wraps(func) def decorated_view(*args, **kwargs): - auth_header = request.headers.get("Authorization") - if dify_config.ADMIN_API_KEY_ENABLE: - if auth_header: - if " " not in auth_header: - raise Unauthorized("Invalid Authorization header format. Expected 'Bearer ' format.") - auth_scheme, auth_token = auth_header.split(None, 1) - auth_scheme = auth_scheme.lower() - if auth_scheme != "bearer": - raise Unauthorized("Invalid Authorization header format. Expected 'Bearer ' format.") - - admin_api_key = dify_config.ADMIN_API_KEY - if admin_api_key: - if admin_api_key == auth_token: - workspace_id = request.headers.get("X-WORKSPACE-ID") - if workspace_id: - tenant_account_join = ( - db.session.query(Tenant, TenantAccountJoin) - .filter(Tenant.id == workspace_id) - .filter(TenantAccountJoin.tenant_id == Tenant.id) - .filter(TenantAccountJoin.role == "owner") - .one_or_none() - ) - if tenant_account_join: - tenant, ta = tenant_account_join - account = db.session.query(Account).filter_by(id=ta.account_id).first() - # Login admin - if account: - account.current_tenant = tenant - current_app.login_manager._update_request_context_with_user(account) # type: ignore - user_logged_in.send(current_app._get_current_object(), user=_get_user()) # type: ignore if request.method in EXEMPT_METHODS or dify_config.LOGIN_DISABLED: pass elif not current_user.is_authenticated: diff --git a/api/libs/oauth_data_source.py b/api/libs/oauth_data_source.py index a5ba08d351..218109522d 100644 --- a/api/libs/oauth_data_source.py +++ b/api/libs/oauth_data_source.py @@ -3,7 +3,7 @@ import urllib.parse from typing import Any import requests -from flask_login import current_user # type: ignore +from flask_login import current_user from extensions.ext_database import db from models.source import DataSourceOauthBinding @@ -61,13 +61,17 @@ class NotionOAuth(OAuthDataSource): "total": len(pages), } # save data source binding - data_source_binding = DataSourceOauthBinding.query.filter( - db.and_( - DataSourceOauthBinding.tenant_id == current_user.current_tenant_id, - DataSourceOauthBinding.provider == "notion", - DataSourceOauthBinding.access_token == access_token, + data_source_binding = ( + db.session.query(DataSourceOauthBinding) + .filter( + db.and_( + DataSourceOauthBinding.tenant_id == current_user.current_tenant_id, + DataSourceOauthBinding.provider == "notion", + DataSourceOauthBinding.access_token == access_token, + ) ) - ).first() + .first() + ) if data_source_binding: data_source_binding.source_info = source_info data_source_binding.disabled = False @@ -97,13 +101,17 @@ class NotionOAuth(OAuthDataSource): "total": len(pages), } # save data source binding - data_source_binding = DataSourceOauthBinding.query.filter( - db.and_( - DataSourceOauthBinding.tenant_id == current_user.current_tenant_id, - DataSourceOauthBinding.provider == "notion", - DataSourceOauthBinding.access_token == access_token, + data_source_binding = ( + db.session.query(DataSourceOauthBinding) + .filter( + db.and_( + DataSourceOauthBinding.tenant_id == current_user.current_tenant_id, + DataSourceOauthBinding.provider == "notion", + DataSourceOauthBinding.access_token == access_token, + ) ) - ).first() + .first() + ) if data_source_binding: data_source_binding.source_info = source_info data_source_binding.disabled = False @@ -121,14 +129,18 @@ class NotionOAuth(OAuthDataSource): def sync_data_source(self, binding_id: str): # save data source binding - data_source_binding = DataSourceOauthBinding.query.filter( - db.and_( - DataSourceOauthBinding.tenant_id == current_user.current_tenant_id, - DataSourceOauthBinding.provider == "notion", - DataSourceOauthBinding.id == binding_id, - DataSourceOauthBinding.disabled == False, + data_source_binding = ( + db.session.query(DataSourceOauthBinding) + .filter( + db.and_( + DataSourceOauthBinding.tenant_id == current_user.current_tenant_id, + DataSourceOauthBinding.provider == "notion", + DataSourceOauthBinding.id == binding_id, + DataSourceOauthBinding.disabled == False, + ) ) - ).first() + .first() + ) if data_source_binding: # get all authorized pages pages = self.get_authorized_pages(data_source_binding.access_token) diff --git a/api/libs/smtp.py b/api/libs/smtp.py index 2325d69a41..35561f071c 100644 --- a/api/libs/smtp.py +++ b/api/libs/smtp.py @@ -28,7 +28,8 @@ class SMTPClient: else: smtp = smtplib.SMTP(self.server, self.port, timeout=10) - if self.username and self.password: + # Only authenticate if both username and password are non-empty + if self.username and self.password and self.username.strip() and self.password.strip(): smtp.login(self.username, self.password) msg = MIMEMultipart() diff --git a/api/migrations/versions/2024_10_10_0516-bbadea11becb_add_name_and_size_to_tool_files.py b/api/migrations/versions/2024_10_10_0516-bbadea11becb_add_name_and_size_to_tool_files.py index c17d1db77a..00f2b15802 100644 --- a/api/migrations/versions/2024_10_10_0516-bbadea11becb_add_name_and_size_to_tool_files.py +++ b/api/migrations/versions/2024_10_10_0516-bbadea11becb_add_name_and_size_to_tool_files.py @@ -5,45 +5,61 @@ Revises: 33f5fac87f29 Create Date: 2024-10-10 05:16:14.764268 """ -from alembic import op -import models as models + import sqlalchemy as sa -from sqlalchemy.dialects import postgresql +from alembic import op, context # revision identifiers, used by Alembic. -revision = 'bbadea11becb' -down_revision = 'd8e744d88ed6' +revision = "bbadea11becb" +down_revision = "d8e744d88ed6" branch_labels = None depends_on = None def upgrade(): + def _has_name_or_size_column() -> bool: + # We cannot access the database in offline mode, so assume + # the "name" and "size" columns do not exist. + if context.is_offline_mode(): + # Log a warning message to inform the user that the database schema cannot be inspected + # in offline mode, and the generated SQL may not accurately reflect the actual execution. + op.execute( + "-- Executing in offline mode, assuming the name and size columns do not exist.\n" + "-- The generated SQL may differ from what will actually be executed.\n" + "-- Please review the migration script carefully!" + ) + + return False + # Use SQLAlchemy inspector to get the columns of the 'tool_files' table + inspector = sa.inspect(conn) + columns = [col["name"] for col in inspector.get_columns("tool_files")] + + # If 'name' or 'size' columns already exist, exit the upgrade function + if "name" in columns or "size" in columns: + return True + return False + # ### commands auto generated by Alembic - please adjust! ### # Get the database connection conn = op.get_bind() - - # Use SQLAlchemy inspector to get the columns of the 'tool_files' table - inspector = sa.inspect(conn) - columns = [col['name'] for col in inspector.get_columns('tool_files')] - - # If 'name' or 'size' columns already exist, exit the upgrade function - if 'name' in columns or 'size' in columns: - return - - with op.batch_alter_table('tool_files', schema=None) as batch_op: - batch_op.add_column(sa.Column('name', sa.String(), nullable=True)) - batch_op.add_column(sa.Column('size', sa.Integer(), nullable=True)) + + if _has_name_or_size_column(): + return + + with op.batch_alter_table("tool_files", schema=None) as batch_op: + batch_op.add_column(sa.Column("name", sa.String(), nullable=True)) + batch_op.add_column(sa.Column("size", sa.Integer(), nullable=True)) op.execute("UPDATE tool_files SET name = '' WHERE name IS NULL") op.execute("UPDATE tool_files SET size = -1 WHERE size IS NULL") - with op.batch_alter_table('tool_files', schema=None) as batch_op: - batch_op.alter_column('name', existing_type=sa.String(), nullable=False) - batch_op.alter_column('size', existing_type=sa.Integer(), nullable=False) + with op.batch_alter_table("tool_files", schema=None) as batch_op: + batch_op.alter_column("name", existing_type=sa.String(), nullable=False) + batch_op.alter_column("size", existing_type=sa.Integer(), nullable=False) # ### end Alembic commands ### def downgrade(): # ### commands auto generated by Alembic - please adjust! ### - with op.batch_alter_table('tool_files', schema=None) as batch_op: - batch_op.drop_column('size') - batch_op.drop_column('name') + with op.batch_alter_table("tool_files", schema=None) as batch_op: + batch_op.drop_column("size") + batch_op.drop_column("name") # ### end Alembic commands ### diff --git a/api/migrations/versions/2024_12_20_0628-e1944c35e15e_add_retry_index_field_to_node_execution_.py b/api/migrations/versions/2024_12_20_0628-e1944c35e15e_add_retry_index_field_to_node_execution_.py index 0facd0ecc0..ae9f2de9b1 100644 --- a/api/migrations/versions/2024_12_20_0628-e1944c35e15e_add_retry_index_field_to_node_execution_.py +++ b/api/migrations/versions/2024_12_20_0628-e1944c35e15e_add_retry_index_field_to_node_execution_.py @@ -35,4 +35,4 @@ def downgrade(): # batch_op.drop_column('retry_index') pass - # ### end Alembic commands ### \ No newline at end of file + # ### end Alembic commands ### diff --git a/api/migrations/versions/2024_12_23_1154-d7999dfa4aae_remove_workflow_node_executions_retry_.py b/api/migrations/versions/2024_12_23_1154-d7999dfa4aae_remove_workflow_node_executions_retry_.py index ea129d15f7..adf6421e57 100644 --- a/api/migrations/versions/2024_12_23_1154-d7999dfa4aae_remove_workflow_node_executions_retry_.py +++ b/api/migrations/versions/2024_12_23_1154-d7999dfa4aae_remove_workflow_node_executions_retry_.py @@ -5,28 +5,38 @@ Revises: e1944c35e15e Create Date: 2024-12-23 11:54:15.344543 """ -from alembic import op -import models as models -import sqlalchemy as sa + +from alembic import op, context from sqlalchemy import inspect # revision identifiers, used by Alembic. -revision = 'd7999dfa4aae' -down_revision = 'e1944c35e15e' +revision = "d7999dfa4aae" +down_revision = "e1944c35e15e" branch_labels = None depends_on = None def upgrade(): - # Check if column exists before attempting to remove it - conn = op.get_bind() - inspector = inspect(conn) - has_column = 'retry_index' in [col['name'] for col in inspector.get_columns('workflow_node_executions')] - + def _has_retry_index_column() -> bool: + if context.is_offline_mode(): + # Log a warning message to inform the user that the database schema cannot be inspected + # in offline mode, and the generated SQL may not accurately reflect the actual execution. + op.execute( + '-- Executing in offline mode: assuming the "retry_index" column does not exist.\n' + "-- The generated SQL may differ from what will actually be executed.\n" + "-- Please review the migration script carefully!" + ) + return False + conn = op.get_bind() + inspector = inspect(conn) + return "retry_index" in [col["name"] for col in inspector.get_columns("workflow_node_executions")] + + has_column = _has_retry_index_column() + if has_column: - with op.batch_alter_table('workflow_node_executions', schema=None) as batch_op: - batch_op.drop_column('retry_index') + with op.batch_alter_table("workflow_node_executions", schema=None) as batch_op: + batch_op.drop_column("retry_index") def downgrade(): diff --git a/api/migrations/versions/2025_05_14_1403-d28f2004b072_add_index_for_workflow_conversation_.py b/api/migrations/versions/2025_05_14_1403-d28f2004b072_add_index_for_workflow_conversation_.py new file mode 100644 index 0000000000..19f6c01655 --- /dev/null +++ b/api/migrations/versions/2025_05_14_1403-d28f2004b072_add_index_for_workflow_conversation_.py @@ -0,0 +1,33 @@ +"""add index for workflow_conversation_variables.conversation_id + +Revision ID: d28f2004b072 +Revises: 6a9f914f656c +Create Date: 2025-05-14 14:03:36.713828 + +""" +from alembic import op +import models as models +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = 'd28f2004b072' +down_revision = '6a9f914f656c' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + with op.batch_alter_table('workflow_conversation_variables', schema=None) as batch_op: + batch_op.create_index(batch_op.f('workflow_conversation_variables_conversation_id_idx'), ['conversation_id'], unique=False) + + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + with op.batch_alter_table('workflow_conversation_variables', schema=None) as batch_op: + batch_op.drop_index(batch_op.f('workflow_conversation_variables_conversation_id_idx')) + + # ### end Alembic commands ### diff --git a/api/migrations/versions/2025_05_15_1531-2adcbe1f5dfb_add_workflowdraftvariable_model.py b/api/migrations/versions/2025_05_15_1531-2adcbe1f5dfb_add_workflowdraftvariable_model.py new file mode 100644 index 0000000000..5bf394b21c --- /dev/null +++ b/api/migrations/versions/2025_05_15_1531-2adcbe1f5dfb_add_workflowdraftvariable_model.py @@ -0,0 +1,51 @@ +"""add WorkflowDraftVariable model + +Revision ID: 2adcbe1f5dfb +Revises: d28f2004b072 +Create Date: 2025-05-15 15:31:03.128680 + +""" + +import sqlalchemy as sa +from alembic import op + +import models as models + +# revision identifiers, used by Alembic. +revision = "2adcbe1f5dfb" +down_revision = "d28f2004b072" +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.create_table( + "workflow_draft_variables", + sa.Column("id", models.types.StringUUID(), server_default=sa.text("uuid_generate_v4()"), nullable=False), + sa.Column("created_at", sa.DateTime(), server_default=sa.text("CURRENT_TIMESTAMP"), nullable=False), + sa.Column("updated_at", sa.DateTime(), server_default=sa.text("CURRENT_TIMESTAMP"), nullable=False), + sa.Column("app_id", models.types.StringUUID(), nullable=False), + sa.Column("last_edited_at", sa.DateTime(), nullable=True), + sa.Column("node_id", sa.String(length=255), nullable=False), + sa.Column("name", sa.String(length=255), nullable=False), + sa.Column("description", sa.String(length=255), nullable=False), + sa.Column("selector", sa.String(length=255), nullable=False), + sa.Column("value_type", sa.String(length=20), nullable=False), + sa.Column("value", sa.Text(), nullable=False), + sa.Column("visible", sa.Boolean(), nullable=False), + sa.Column("editable", sa.Boolean(), nullable=False), + sa.PrimaryKeyConstraint("id", name=op.f("workflow_draft_variables_pkey")), + sa.UniqueConstraint("app_id", "node_id", "name", name=op.f("workflow_draft_variables_app_id_key")), + ) + + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + + # Dropping `workflow_draft_variables` also drops any index associated with it. + op.drop_table("workflow_draft_variables") + + # ### end Alembic commands ### diff --git a/api/migrations/versions/2025_06_06_1424-4474872b0ee6_workflow_draft_varaibles_add_node_execution_id.py b/api/migrations/versions/2025_06_06_1424-4474872b0ee6_workflow_draft_varaibles_add_node_execution_id.py new file mode 100644 index 0000000000..d7a5d116c9 --- /dev/null +++ b/api/migrations/versions/2025_06_06_1424-4474872b0ee6_workflow_draft_varaibles_add_node_execution_id.py @@ -0,0 +1,60 @@ +"""`workflow_draft_varaibles` add `node_execution_id` column, add an index for `workflow_node_executions`. + +Revision ID: 4474872b0ee6 +Revises: 2adcbe1f5dfb +Create Date: 2025-06-06 14:24:44.213018 + +""" +from alembic import op +import models as models +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '4474872b0ee6' +down_revision = '2adcbe1f5dfb' +branch_labels = None +depends_on = None + + +def upgrade(): + # `CREATE INDEX CONCURRENTLY` cannot run within a transaction, so use the `autocommit_block` + # context manager to wrap the index creation statement. + # Reference: + # + # - https://www.postgresql.org/docs/current/sql-createindex.html#:~:text=Another%20difference%20is,CREATE%20INDEX%20CONCURRENTLY%20cannot. + # - https://alembic.sqlalchemy.org/en/latest/api/runtime.html#alembic.runtime.migration.MigrationContext.autocommit_block + with op.get_context().autocommit_block(): + op.create_index( + op.f('workflow_node_executions_tenant_id_idx'), + "workflow_node_executions", + ['tenant_id', 'workflow_id', 'node_id', sa.literal_column('created_at DESC')], + unique=False, + postgresql_concurrently=True, + ) + + with op.batch_alter_table('workflow_draft_variables', schema=None) as batch_op: + batch_op.add_column(sa.Column('node_execution_id', models.types.StringUUID(), nullable=True)) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + + # `DROP INDEX CONCURRENTLY` cannot run within a transaction, so use the `autocommit_block` + # context manager to wrap the index creation statement. + # Reference: + # + # - https://www.postgresql.org/docs/current/sql-createindex.html#:~:text=Another%20difference%20is,CREATE%20INDEX%20CONCURRENTLY%20cannot. + # - https://alembic.sqlalchemy.org/en/latest/api/runtime.html#alembic.runtime.migration.MigrationContext.autocommit_block + # `DROP INDEX CONCURRENTLY` cannot run within a transaction, so commit existing transactions first. + # Reference: + # + # https://www.postgresql.org/docs/current/sql-createindex.html#:~:text=Another%20difference%20is,CREATE%20INDEX%20CONCURRENTLY%20cannot. + with op.get_context().autocommit_block(): + op.drop_index(op.f('workflow_node_executions_tenant_id_idx'), postgresql_concurrently=True) + + with op.batch_alter_table('workflow_draft_variables', schema=None) as batch_op: + batch_op.drop_column('node_execution_id') + + # ### end Alembic commands ### diff --git a/api/migrations/versions/64b051264f32_init.py b/api/migrations/versions/64b051264f32_init.py index 8c45ae898d..b0fb3deac6 100644 --- a/api/migrations/versions/64b051264f32_init.py +++ b/api/migrations/versions/64b051264f32_init.py @@ -1,7 +1,7 @@ """init Revision ID: 64b051264f32 -Revises: +Revises: Create Date: 2023-05-13 14:26:59.085018 """ diff --git a/api/migrations/versions/de95f5c77138_migration_serpapi_api_key.py b/api/migrations/versions/de95f5c77138_migration_serpapi_api_key.py index fcca705d21..c18126286c 100644 --- a/api/migrations/versions/de95f5c77138_migration_serpapi_api_key.py +++ b/api/migrations/versions/de95f5c77138_migration_serpapi_api_key.py @@ -99,12 +99,12 @@ def upgrade(): id=id, tenant_id=tenant_id, user_id=user_id, - provider='google', + provider='google', encrypted_credentials=encrypted_credentials, created_at=created_at, updated_at=updated_at ) - + # ### end Alembic commands ### diff --git a/api/models/__init__.py b/api/models/__init__.py index 2066481a61..83b50eb099 100644 --- a/api/models/__init__.py +++ b/api/models/__init__.py @@ -27,7 +27,7 @@ from .dataset import ( Whitelist, ) from .engine import db -from .enums import CreatedByRole, UserFrom, WorkflowRunTriggeredFrom +from .enums import CreatorUserRole, UserFrom, WorkflowRunTriggeredFrom from .model import ( ApiRequest, ApiToken, @@ -84,11 +84,9 @@ from .workflow import ( Workflow, WorkflowAppLog, WorkflowAppLogCreatedFrom, - WorkflowNodeExecution, - WorkflowNodeExecutionStatus, + WorkflowNodeExecutionModel, WorkflowNodeExecutionTriggeredFrom, WorkflowRun, - WorkflowRunStatus, WorkflowType, ) @@ -100,19 +98,19 @@ __all__ = [ "AccountStatus", "ApiRequest", "ApiToken", - "ApiToolProvider", # Added + "ApiToolProvider", "App", "AppAnnotationHitHistory", "AppAnnotationSetting", "AppDatasetJoin", "AppMode", "AppModelConfig", - "BuiltinToolProvider", # Added + "BuiltinToolProvider", "CeleryTask", "CeleryTaskSet", "Conversation", "ConversationVariable", - "CreatedByRole", + "CreatorUserRole", "DataSourceApiKeyAuthBinding", "DataSourceOauthBinding", "Dataset", @@ -171,11 +169,9 @@ __all__ = [ "Workflow", "WorkflowAppLog", "WorkflowAppLogCreatedFrom", - "WorkflowNodeExecution", - "WorkflowNodeExecutionStatus", + "WorkflowNodeExecutionModel", "WorkflowNodeExecutionTriggeredFrom", "WorkflowRun", - "WorkflowRunStatus", "WorkflowRunTriggeredFrom", "WorkflowToolProvider", "WorkflowType", diff --git a/api/models/account.py b/api/models/account.py index a0b8957fe1..7ffeefa980 100644 --- a/api/models/account.py +++ b/api/models/account.py @@ -1,9 +1,10 @@ import enum import json +from typing import Optional, cast from flask_login import UserMixin # type: ignore from sqlalchemy import func -from sqlalchemy.orm import Mapped, mapped_column +from sqlalchemy.orm import Mapped, mapped_column, reconstructor from models.base import Base @@ -11,6 +12,66 @@ from .engine import db from .types import StringUUID +class TenantAccountRole(enum.StrEnum): + OWNER = "owner" + ADMIN = "admin" + EDITOR = "editor" + NORMAL = "normal" + DATASET_OPERATOR = "dataset_operator" + + @staticmethod + def is_valid_role(role: str) -> bool: + if not role: + return False + return role in { + TenantAccountRole.OWNER, + TenantAccountRole.ADMIN, + TenantAccountRole.EDITOR, + TenantAccountRole.NORMAL, + TenantAccountRole.DATASET_OPERATOR, + } + + @staticmethod + def is_privileged_role(role: Optional["TenantAccountRole"]) -> bool: + if not role: + return False + return role in {TenantAccountRole.OWNER, TenantAccountRole.ADMIN} + + @staticmethod + def is_admin_role(role: Optional["TenantAccountRole"]) -> bool: + if not role: + return False + return role == TenantAccountRole.ADMIN + + @staticmethod + def is_non_owner_role(role: Optional["TenantAccountRole"]) -> bool: + if not role: + return False + return role in { + TenantAccountRole.ADMIN, + TenantAccountRole.EDITOR, + TenantAccountRole.NORMAL, + TenantAccountRole.DATASET_OPERATOR, + } + + @staticmethod + def is_editing_role(role: Optional["TenantAccountRole"]) -> bool: + if not role: + return False + return role in {TenantAccountRole.OWNER, TenantAccountRole.ADMIN, TenantAccountRole.EDITOR} + + @staticmethod + def is_dataset_edit_role(role: Optional["TenantAccountRole"]) -> bool: + if not role: + return False + return role in { + TenantAccountRole.OWNER, + TenantAccountRole.ADMIN, + TenantAccountRole.EDITOR, + TenantAccountRole.DATASET_OPERATOR, + } + + class AccountStatus(enum.StrEnum): PENDING = "pending" UNINITIALIZED = "uninitialized" @@ -40,54 +101,54 @@ class Account(UserMixin, Base): created_at = db.Column(db.DateTime, nullable=False, server_default=func.current_timestamp()) updated_at = db.Column(db.DateTime, nullable=False, server_default=func.current_timestamp()) + @reconstructor + def init_on_load(self): + self.role: Optional[TenantAccountRole] = None + self._current_tenant: Optional[Tenant] = None + @property def is_password_set(self): return self.password is not None @property def current_tenant(self): - # FIXME: fix the type error later, because the type is important maybe cause some bugs - return self._current_tenant # type: ignore + return self._current_tenant @current_tenant.setter - def current_tenant(self, value: "Tenant"): - tenant = value - ta = TenantAccountJoin.query.filter_by(tenant_id=tenant.id, account_id=self.id).first() + def current_tenant(self, tenant: "Tenant"): + ta = db.session.query(TenantAccountJoin).filter_by(tenant_id=tenant.id, account_id=self.id).first() if ta: - tenant.current_role = ta.role - else: - tenant = None # type: ignore - - self._current_tenant = tenant + self.role = TenantAccountRole(ta.role) + self._current_tenant = tenant + return + self._current_tenant = None @property def current_tenant_id(self) -> str | None: return self._current_tenant.id if self._current_tenant else None - @current_tenant_id.setter - def current_tenant_id(self, value: str): - try: - tenant_account_join = ( + def set_tenant_id(self, tenant_id: str): + tenant_account_join = cast( + tuple[Tenant, TenantAccountJoin], + ( db.session.query(Tenant, TenantAccountJoin) - .filter(Tenant.id == value) + .filter(Tenant.id == tenant_id) .filter(TenantAccountJoin.tenant_id == Tenant.id) .filter(TenantAccountJoin.account_id == self.id) .one_or_none() - ) + ), + ) - if tenant_account_join: - tenant, ta = tenant_account_join - tenant.current_role = ta.role - else: - tenant = None - except Exception: - tenant = None + if not tenant_account_join: + return + tenant, join = tenant_account_join + self.role = join.role self._current_tenant = tenant @property def current_role(self): - return self._current_tenant.current_role + return self.role def get_status(self) -> AccountStatus: status_str = self.status @@ -107,23 +168,23 @@ class Account(UserMixin, Base): # check current_user.current_tenant.current_role in ['admin', 'owner'] @property def is_admin_or_owner(self): - return TenantAccountRole.is_privileged_role(self._current_tenant.current_role) + return TenantAccountRole.is_privileged_role(self.role) @property def is_admin(self): - return TenantAccountRole.is_admin_role(self._current_tenant.current_role) + return TenantAccountRole.is_admin_role(self.role) @property def is_editor(self): - return TenantAccountRole.is_editing_role(self._current_tenant.current_role) + return TenantAccountRole.is_editing_role(self.role) @property def is_dataset_editor(self): - return TenantAccountRole.is_dataset_edit_role(self._current_tenant.current_role) + return TenantAccountRole.is_dataset_edit_role(self.role) @property def is_dataset_operator(self): - return self._current_tenant.current_role == TenantAccountRole.DATASET_OPERATOR + return self.role == TenantAccountRole.DATASET_OPERATOR class TenantStatus(enum.StrEnum): @@ -131,67 +192,7 @@ class TenantStatus(enum.StrEnum): ARCHIVE = "archive" -class TenantAccountRole(enum.StrEnum): - OWNER = "owner" - ADMIN = "admin" - EDITOR = "editor" - NORMAL = "normal" - DATASET_OPERATOR = "dataset_operator" - - @staticmethod - def is_valid_role(role: str) -> bool: - if not role: - return False - return role in { - TenantAccountRole.OWNER, - TenantAccountRole.ADMIN, - TenantAccountRole.EDITOR, - TenantAccountRole.NORMAL, - TenantAccountRole.DATASET_OPERATOR, - } - - @staticmethod - def is_privileged_role(role: str) -> bool: - if not role: - return False - return role in {TenantAccountRole.OWNER, TenantAccountRole.ADMIN} - - @staticmethod - def is_admin_role(role: str) -> bool: - if not role: - return False - return role == TenantAccountRole.ADMIN - - @staticmethod - def is_non_owner_role(role: str) -> bool: - if not role: - return False - return role in { - TenantAccountRole.ADMIN, - TenantAccountRole.EDITOR, - TenantAccountRole.NORMAL, - TenantAccountRole.DATASET_OPERATOR, - } - - @staticmethod - def is_editing_role(role: str) -> bool: - if not role: - return False - return role in {TenantAccountRole.OWNER, TenantAccountRole.ADMIN, TenantAccountRole.EDITOR} - - @staticmethod - def is_dataset_edit_role(role: str) -> bool: - if not role: - return False - return role in { - TenantAccountRole.OWNER, - TenantAccountRole.ADMIN, - TenantAccountRole.EDITOR, - TenantAccountRole.DATASET_OPERATOR, - } - - -class Tenant(db.Model): # type: ignore[name-defined] +class Tenant(Base): __tablename__ = "tenants" __table_args__ = (db.PrimaryKeyConstraint("id", name="tenant_pkey"),) @@ -220,7 +221,7 @@ class Tenant(db.Model): # type: ignore[name-defined] self.custom_config = json.dumps(value) -class TenantAccountJoin(db.Model): # type: ignore[name-defined] +class TenantAccountJoin(Base): __tablename__ = "tenant_account_joins" __table_args__ = ( db.PrimaryKeyConstraint("id", name="tenant_account_join_pkey"), @@ -239,7 +240,7 @@ class TenantAccountJoin(db.Model): # type: ignore[name-defined] updated_at = db.Column(db.DateTime, nullable=False, server_default=func.current_timestamp()) -class AccountIntegrate(db.Model): # type: ignore[name-defined] +class AccountIntegrate(Base): __tablename__ = "account_integrates" __table_args__ = ( db.PrimaryKeyConstraint("id", name="account_integrate_pkey"), @@ -256,7 +257,7 @@ class AccountIntegrate(db.Model): # type: ignore[name-defined] updated_at = db.Column(db.DateTime, nullable=False, server_default=func.current_timestamp()) -class InvitationCode(db.Model): # type: ignore[name-defined] +class InvitationCode(Base): __tablename__ = "invitation_codes" __table_args__ = ( db.PrimaryKeyConstraint("id", name="invitation_code_pkey"), diff --git a/api/models/api_based_extension.py b/api/models/api_based_extension.py index 6b6d808710..5a70e18622 100644 --- a/api/models/api_based_extension.py +++ b/api/models/api_based_extension.py @@ -2,6 +2,7 @@ import enum from sqlalchemy import func +from .base import Base from .engine import db from .types import StringUUID @@ -13,7 +14,7 @@ class APIBasedExtensionPoint(enum.Enum): APP_MODERATION_OUTPUT = "app.moderation.output" -class APIBasedExtension(db.Model): # type: ignore[name-defined] +class APIBasedExtension(Base): __tablename__ = "api_based_extensions" __table_args__ = ( db.PrimaryKeyConstraint("id", name="api_based_extension_pkey"), diff --git a/api/models/base.py b/api/models/base.py index da9509301a..bd120f5487 100644 --- a/api/models/base.py +++ b/api/models/base.py @@ -1,5 +1,7 @@ -from sqlalchemy.orm import declarative_base +from sqlalchemy.orm import DeclarativeBase from models.engine import metadata -Base = declarative_base(metadata=metadata) + +class Base(DeclarativeBase): + metadata = metadata diff --git a/api/models/dataset.py b/api/models/dataset.py index d6708ac88b..ad43d6f371 100644 --- a/api/models/dataset.py +++ b/api/models/dataset.py @@ -22,6 +22,7 @@ from extensions.ext_storage import storage from services.entities.knowledge_entities.knowledge_entities import ParentMode, Rule from .account import Account +from .base import Base from .engine import db from .model import App, Tag, TagBinding, UploadFile from .types import StringUUID @@ -33,7 +34,7 @@ class DatasetPermissionEnum(enum.StrEnum): PARTIAL_TEAM = "partial_members" -class Dataset(db.Model): # type: ignore[name-defined] +class Dataset(Base): __tablename__ = "datasets" __table_args__ = ( db.PrimaryKeyConstraint("id", name="dataset_pkey"), @@ -92,7 +93,8 @@ class Dataset(db.Model): # type: ignore[name-defined] @property def latest_process_rule(self): return ( - DatasetProcessRule.query.filter(DatasetProcessRule.dataset_id == self.id) + db.session.query(DatasetProcessRule) + .filter(DatasetProcessRule.dataset_id == self.id) .order_by(DatasetProcessRule.created_at.desc()) .first() ) @@ -137,7 +139,8 @@ class Dataset(db.Model): # type: ignore[name-defined] @property def word_count(self): return ( - Document.query.with_entities(func.coalesce(func.sum(Document.word_count))) + db.session.query(Document) + .with_entities(func.coalesce(func.sum(Document.word_count))) .filter(Document.dataset_id == self.id) .scalar() ) @@ -255,7 +258,7 @@ class Dataset(db.Model): # type: ignore[name-defined] return f"Vector_index_{normalized_dataset_id}_Node" -class DatasetProcessRule(db.Model): # type: ignore[name-defined] +class DatasetProcessRule(Base): __tablename__ = "dataset_process_rules" __table_args__ = ( db.PrimaryKeyConstraint("id", name="dataset_process_rule_pkey"), @@ -295,7 +298,7 @@ class DatasetProcessRule(db.Model): # type: ignore[name-defined] return None -class Document(db.Model): # type: ignore[name-defined] +class Document(Base): __tablename__ = "documents" __table_args__ = ( db.PrimaryKeyConstraint("id", name="document_pkey"), @@ -439,12 +442,13 @@ class Document(db.Model): # type: ignore[name-defined] @property def segment_count(self): - return DocumentSegment.query.filter(DocumentSegment.document_id == self.id).count() + return db.session.query(DocumentSegment).filter(DocumentSegment.document_id == self.id).count() @property def hit_count(self): return ( - DocumentSegment.query.with_entities(func.coalesce(func.sum(DocumentSegment.hit_count))) + db.session.query(DocumentSegment) + .with_entities(func.coalesce(func.sum(DocumentSegment.hit_count))) .filter(DocumentSegment.document_id == self.id) .scalar() ) @@ -635,7 +639,7 @@ class Document(db.Model): # type: ignore[name-defined] ) -class DocumentSegment(db.Model): # type: ignore[name-defined] +class DocumentSegment(Base): __tablename__ = "document_segments" __table_args__ = ( db.PrimaryKeyConstraint("id", name="document_segment_pkey"), @@ -786,7 +790,7 @@ class DocumentSegment(db.Model): # type: ignore[name-defined] return text -class ChildChunk(db.Model): # type: ignore[name-defined] +class ChildChunk(Base): __tablename__ = "child_chunks" __table_args__ = ( db.PrimaryKeyConstraint("id", name="child_chunk_pkey"), @@ -829,7 +833,7 @@ class ChildChunk(db.Model): # type: ignore[name-defined] return db.session.query(DocumentSegment).filter(DocumentSegment.id == self.segment_id).first() -class AppDatasetJoin(db.Model): # type: ignore[name-defined] +class AppDatasetJoin(Base): __tablename__ = "app_dataset_joins" __table_args__ = ( db.PrimaryKeyConstraint("id", name="app_dataset_join_pkey"), @@ -846,7 +850,7 @@ class AppDatasetJoin(db.Model): # type: ignore[name-defined] return db.session.get(App, self.app_id) -class DatasetQuery(db.Model): # type: ignore[name-defined] +class DatasetQuery(Base): __tablename__ = "dataset_queries" __table_args__ = ( db.PrimaryKeyConstraint("id", name="dataset_query_pkey"), @@ -863,7 +867,7 @@ class DatasetQuery(db.Model): # type: ignore[name-defined] created_at = db.Column(db.DateTime, nullable=False, server_default=db.func.current_timestamp()) -class DatasetKeywordTable(db.Model): # type: ignore[name-defined] +class DatasetKeywordTable(Base): __tablename__ = "dataset_keyword_tables" __table_args__ = ( db.PrimaryKeyConstraint("id", name="dataset_keyword_table_pkey"), @@ -891,7 +895,7 @@ class DatasetKeywordTable(db.Model): # type: ignore[name-defined] return dct # get dataset - dataset = Dataset.query.filter_by(id=self.dataset_id).first() + dataset = db.session.query(Dataset).filter_by(id=self.dataset_id).first() if not dataset: return None if self.data_source_type == "database": @@ -908,7 +912,7 @@ class DatasetKeywordTable(db.Model): # type: ignore[name-defined] return None -class Embedding(db.Model): # type: ignore[name-defined] +class Embedding(Base): __tablename__ = "embeddings" __table_args__ = ( db.PrimaryKeyConstraint("id", name="embedding_pkey"), @@ -932,7 +936,7 @@ class Embedding(db.Model): # type: ignore[name-defined] return cast(list[float], pickle.loads(self.embedding)) # noqa: S301 -class DatasetCollectionBinding(db.Model): # type: ignore[name-defined] +class DatasetCollectionBinding(Base): __tablename__ = "dataset_collection_bindings" __table_args__ = ( db.PrimaryKeyConstraint("id", name="dataset_collection_bindings_pkey"), @@ -947,7 +951,7 @@ class DatasetCollectionBinding(db.Model): # type: ignore[name-defined] created_at = db.Column(db.DateTime, nullable=False, server_default=func.current_timestamp()) -class TidbAuthBinding(db.Model): # type: ignore[name-defined] +class TidbAuthBinding(Base): __tablename__ = "tidb_auth_bindings" __table_args__ = ( db.PrimaryKeyConstraint("id", name="tidb_auth_bindings_pkey"), @@ -967,7 +971,7 @@ class TidbAuthBinding(db.Model): # type: ignore[name-defined] created_at = db.Column(db.DateTime, nullable=False, server_default=func.current_timestamp()) -class Whitelist(db.Model): # type: ignore[name-defined] +class Whitelist(Base): __tablename__ = "whitelists" __table_args__ = ( db.PrimaryKeyConstraint("id", name="whitelists_pkey"), @@ -979,7 +983,7 @@ class Whitelist(db.Model): # type: ignore[name-defined] created_at = db.Column(db.DateTime, nullable=False, server_default=func.current_timestamp()) -class DatasetPermission(db.Model): # type: ignore[name-defined] +class DatasetPermission(Base): __tablename__ = "dataset_permissions" __table_args__ = ( db.PrimaryKeyConstraint("id", name="dataset_permission_pkey"), @@ -996,7 +1000,7 @@ class DatasetPermission(db.Model): # type: ignore[name-defined] created_at = db.Column(db.DateTime, nullable=False, server_default=func.current_timestamp()) -class ExternalKnowledgeApis(db.Model): # type: ignore[name-defined] +class ExternalKnowledgeApis(Base): __tablename__ = "external_knowledge_apis" __table_args__ = ( db.PrimaryKeyConstraint("id", name="external_knowledge_apis_pkey"), @@ -1049,7 +1053,7 @@ class ExternalKnowledgeApis(db.Model): # type: ignore[name-defined] return dataset_bindings -class ExternalKnowledgeBindings(db.Model): # type: ignore[name-defined] +class ExternalKnowledgeBindings(Base): __tablename__ = "external_knowledge_bindings" __table_args__ = ( db.PrimaryKeyConstraint("id", name="external_knowledge_bindings_pkey"), @@ -1070,7 +1074,7 @@ class ExternalKnowledgeBindings(db.Model): # type: ignore[name-defined] updated_at = db.Column(db.DateTime, nullable=False, server_default=func.current_timestamp()) -class DatasetAutoDisableLog(db.Model): # type: ignore[name-defined] +class DatasetAutoDisableLog(Base): __tablename__ = "dataset_auto_disable_logs" __table_args__ = ( db.PrimaryKeyConstraint("id", name="dataset_auto_disable_log_pkey"), @@ -1087,7 +1091,7 @@ class DatasetAutoDisableLog(db.Model): # type: ignore[name-defined] created_at = db.Column(db.DateTime, nullable=False, server_default=db.text("CURRENT_TIMESTAMP(0)")) -class RateLimitLog(db.Model): # type: ignore[name-defined] +class RateLimitLog(Base): __tablename__ = "rate_limit_logs" __table_args__ = ( db.PrimaryKeyConstraint("id", name="rate_limit_log_pkey"), @@ -1102,7 +1106,7 @@ class RateLimitLog(db.Model): # type: ignore[name-defined] created_at = db.Column(db.DateTime, nullable=False, server_default=db.text("CURRENT_TIMESTAMP(0)")) -class DatasetMetadata(db.Model): # type: ignore[name-defined] +class DatasetMetadata(Base): __tablename__ = "dataset_metadatas" __table_args__ = ( db.PrimaryKeyConstraint("id", name="dataset_metadata_pkey"), @@ -1121,7 +1125,7 @@ class DatasetMetadata(db.Model): # type: ignore[name-defined] updated_by = db.Column(StringUUID, nullable=True) -class DatasetMetadataBinding(db.Model): # type: ignore[name-defined] +class DatasetMetadataBinding(Base): __tablename__ = "dataset_metadata_bindings" __table_args__ = ( db.PrimaryKeyConstraint("id", name="dataset_metadata_binding_pkey"), diff --git a/api/models/engine.py b/api/models/engine.py index dda93bc941..05c1cacdcb 100644 --- a/api/models/engine.py +++ b/api/models/engine.py @@ -10,4 +10,16 @@ POSTGRES_INDEXES_NAMING_CONVENTION = { } metadata = MetaData(naming_convention=POSTGRES_INDEXES_NAMING_CONVENTION) + +# ****** IMPORTANT NOTICE ****** +# +# NOTE(QuantumGhost): Avoid directly importing and using `db` in modules outside of the +# `controllers` package. +# +# Instead, import `db` within the `controllers` package and pass it as an argument to +# functions or class constructors. +# +# Directly importing `db` in other modules can make the code more difficult to read, test, and maintain. +# +# Whenever possible, avoid this pattern in new code. db = SQLAlchemy(metadata=metadata) diff --git a/api/models/enums.py b/api/models/enums.py index 7b9500ebe4..4434c3fec8 100644 --- a/api/models/enums.py +++ b/api/models/enums.py @@ -1,7 +1,7 @@ from enum import StrEnum -class CreatedByRole(StrEnum): +class CreatorUserRole(StrEnum): ACCOUNT = "account" END_USER = "end_user" @@ -14,3 +14,10 @@ class UserFrom(StrEnum): class WorkflowRunTriggeredFrom(StrEnum): DEBUGGING = "debugging" APP_RUN = "app-run" + + +class DraftVariableType(StrEnum): + # node means that the correspond variable + NODE = "node" + SYS = "sys" + CONVERSATION = "conversation" diff --git a/api/models/model.py b/api/models/model.py index d1490d75c8..229e77134e 100644 --- a/api/models/model.py +++ b/api/models/model.py @@ -8,6 +8,8 @@ from typing import TYPE_CHECKING, Any, Literal, Optional, cast from core.plugin.entities.plugin import GenericProviderID from core.tools.entities.tool_entities import ToolProviderType +from core.tools.signature import sign_tool_file +from core.workflow.entities.workflow_execution import WorkflowExecutionStatus from services.plugin.plugin_service import PluginService if TYPE_CHECKING: @@ -15,7 +17,7 @@ if TYPE_CHECKING: import sqlalchemy as sa from flask import request -from flask_login import UserMixin # type: ignore +from flask_login import UserMixin from sqlalchemy import Float, Index, PrimaryKeyConstraint, func, text from sqlalchemy.orm import Mapped, Session, mapped_column @@ -23,14 +25,12 @@ from configs import dify_config from constants import DEFAULT_FILE_NUMBER_LIMITS from core.file import FILE_MODEL_IDENTITY, File, FileTransferMethod, FileType from core.file import helpers as file_helpers -from core.file.tool_file_parser import ToolFileParser from libs.helper import generate_string -from models.base import Base -from models.enums import CreatedByRole -from models.workflow import WorkflowRunStatus from .account import Account, Tenant +from .base import Base from .engine import db +from .enums import CreatorUserRole from .types import StringUUID if TYPE_CHECKING: @@ -294,6 +294,15 @@ class App(Base): return tags or [] + @property + def author_name(self): + if self.created_by: + account = db.session.query(Account).filter(Account.id == self.created_by).first() + if account: + return account.name + + return None + class AppModelConfig(Base): __tablename__ = "app_model_configs" @@ -602,7 +611,7 @@ class InstalledApp(Base): return tenant -class Conversation(db.Model): # type: ignore[name-defined] +class Conversation(Base): __tablename__ = "conversations" __table_args__ = ( db.PrimaryKeyConstraint("id", name="conversation_pkey"), @@ -785,22 +794,22 @@ class Conversation(db.Model): # type: ignore[name-defined] def status_count(self): messages = db.session.query(Message).filter(Message.conversation_id == self.id).all() status_counts = { - WorkflowRunStatus.RUNNING: 0, - WorkflowRunStatus.SUCCEEDED: 0, - WorkflowRunStatus.FAILED: 0, - WorkflowRunStatus.STOPPED: 0, - WorkflowRunStatus.PARTIAL_SUCCEEDED: 0, + WorkflowExecutionStatus.RUNNING: 0, + WorkflowExecutionStatus.SUCCEEDED: 0, + WorkflowExecutionStatus.FAILED: 0, + WorkflowExecutionStatus.STOPPED: 0, + WorkflowExecutionStatus.PARTIAL_SUCCEEDED: 0, } for message in messages: if message.workflow_run: - status_counts[message.workflow_run.status] += 1 + status_counts[WorkflowExecutionStatus(message.workflow_run.status)] += 1 return ( { - "success": status_counts[WorkflowRunStatus.SUCCEEDED], - "failed": status_counts[WorkflowRunStatus.FAILED], - "partial_success": status_counts[WorkflowRunStatus.PARTIAL_SUCCEEDED], + "success": status_counts[WorkflowExecutionStatus.SUCCEEDED], + "failed": status_counts[WorkflowExecutionStatus.FAILED], + "partial_success": status_counts[WorkflowExecutionStatus.PARTIAL_SUCCEEDED], } if messages else None @@ -864,7 +873,7 @@ class Conversation(db.Model): # type: ignore[name-defined] } -class Message(db.Model): # type: ignore[name-defined] +class Message(Base): __tablename__ = "messages" __table_args__ = ( PrimaryKeyConstraint("id", name="message_pkey"), @@ -986,9 +995,7 @@ class Message(db.Model): # type: ignore[name-defined] if not tool_file_id: continue - sign_url = ToolFileParser.get_tool_file_manager().sign_file( - tool_file_id=tool_file_id, extension=extension - ) + sign_url = sign_tool_file(tool_file_id=tool_file_id, extension=extension) elif "file-preview" in url: # get upload file id upload_file_id_pattern = r"\/files\/([\w-]+)\/file-preview?\?timestamp=" @@ -1012,7 +1019,9 @@ class Message(db.Model): # type: ignore[name-defined] sign_url = file_helpers.get_signed_file_url(upload_file_id) else: continue - + # if as_attachment is in the url, add it to the sign_url. + if "as_attachment" in url: + sign_url += "&as_attachment=true" re_sign_file_url_answer = re_sign_file_url_answer.replace(url, sign_url) return re_sign_file_url_answer @@ -1211,7 +1220,7 @@ class Message(db.Model): # type: ignore[name-defined] ) -class MessageFeedback(db.Model): # type: ignore[name-defined] +class MessageFeedback(Base): __tablename__ = "message_feedbacks" __table_args__ = ( db.PrimaryKeyConstraint("id", name="message_feedback_pkey"), @@ -1237,8 +1246,23 @@ class MessageFeedback(db.Model): # type: ignore[name-defined] account = db.session.query(Account).filter(Account.id == self.from_account_id).first() return account + def to_dict(self): + return { + "id": str(self.id), + "app_id": str(self.app_id), + "conversation_id": str(self.conversation_id), + "message_id": str(self.message_id), + "rating": self.rating, + "content": self.content, + "from_source": self.from_source, + "from_end_user_id": str(self.from_end_user_id) if self.from_end_user_id else None, + "from_account_id": str(self.from_account_id) if self.from_account_id else None, + "created_at": self.created_at.isoformat(), + "updated_at": self.updated_at.isoformat(), + } -class MessageFile(db.Model): # type: ignore[name-defined] + +class MessageFile(Base): __tablename__ = "message_files" __table_args__ = ( db.PrimaryKeyConstraint("id", name="message_file_pkey"), @@ -1255,7 +1279,7 @@ class MessageFile(db.Model): # type: ignore[name-defined] url: str | None = None, belongs_to: Literal["user", "assistant"] | None = None, upload_file_id: str | None = None, - created_by_role: CreatedByRole, + created_by_role: CreatorUserRole, created_by: str, ): self.message_id = message_id @@ -1279,7 +1303,7 @@ class MessageFile(db.Model): # type: ignore[name-defined] created_at: Mapped[datetime] = db.Column(db.DateTime, nullable=False, server_default=func.current_timestamp()) -class MessageAnnotation(db.Model): # type: ignore[name-defined] +class MessageAnnotation(Base): __tablename__ = "message_annotations" __table_args__ = ( db.PrimaryKeyConstraint("id", name="message_annotation_pkey"), @@ -1310,7 +1334,7 @@ class MessageAnnotation(db.Model): # type: ignore[name-defined] return account -class AppAnnotationHitHistory(db.Model): # type: ignore[name-defined] +class AppAnnotationHitHistory(Base): __tablename__ = "app_annotation_hit_histories" __table_args__ = ( db.PrimaryKeyConstraint("id", name="app_annotation_hit_histories_pkey"), @@ -1322,7 +1346,7 @@ class AppAnnotationHitHistory(db.Model): # type: ignore[name-defined] id = db.Column(StringUUID, server_default=db.text("uuid_generate_v4()")) app_id = db.Column(StringUUID, nullable=False) - annotation_id = db.Column(StringUUID, nullable=False) + annotation_id: Mapped[str] = db.Column(StringUUID, nullable=False) source = db.Column(db.Text, nullable=False) question = db.Column(db.Text, nullable=False) account_id = db.Column(StringUUID, nullable=False) @@ -1348,7 +1372,7 @@ class AppAnnotationHitHistory(db.Model): # type: ignore[name-defined] return account -class AppAnnotationSetting(db.Model): # type: ignore[name-defined] +class AppAnnotationSetting(Base): __tablename__ = "app_annotation_settings" __table_args__ = ( db.PrimaryKeyConstraint("id", name="app_annotation_settings_pkey"), @@ -1364,26 +1388,6 @@ class AppAnnotationSetting(db.Model): # type: ignore[name-defined] updated_user_id = db.Column(StringUUID, nullable=False) updated_at = db.Column(db.DateTime, nullable=False, server_default=func.current_timestamp()) - @property - def created_account(self): - account = ( - db.session.query(Account) - .join(AppAnnotationSetting, AppAnnotationSetting.created_user_id == Account.id) - .filter(AppAnnotationSetting.id == self.annotation_id) - .first() - ) - return account - - @property - def updated_account(self): - account = ( - db.session.query(Account) - .join(AppAnnotationSetting, AppAnnotationSetting.updated_user_id == Account.id) - .filter(AppAnnotationSetting.id == self.annotation_id) - .first() - ) - return account - @property def collection_binding_detail(self): from .dataset import DatasetCollectionBinding @@ -1422,7 +1426,7 @@ class EndUser(Base, UserMixin): ) id = db.Column(StringUUID, server_default=db.text("uuid_generate_v4()")) - tenant_id = db.Column(StringUUID, nullable=False) + tenant_id: Mapped[str] = db.Column(StringUUID, nullable=False) app_id = db.Column(StringUUID, nullable=True) type = db.Column(db.String(255), nullable=False) external_user_id = db.Column(db.String(255), nullable=True) @@ -1552,7 +1556,7 @@ class UploadFile(Base): size: int, extension: str, mime_type: str, - created_by_role: CreatedByRole, + created_by_role: CreatorUserRole, created_by: str, created_at: datetime, used: bool, diff --git a/api/models/provider.py b/api/models/provider.py index 567400702d..1e25f0c90f 100644 --- a/api/models/provider.py +++ b/api/models/provider.py @@ -1,9 +1,11 @@ +from datetime import datetime from enum import Enum +from typing import Optional -from sqlalchemy import func - -from models.base import Base +from sqlalchemy import func, text +from sqlalchemy.orm import Mapped, mapped_column +from .base import Base from .engine import db from .types import StringUUID @@ -52,20 +54,24 @@ class Provider(Base): ), ) - id = db.Column(StringUUID, server_default=db.text("uuid_generate_v4()")) - tenant_id = db.Column(StringUUID, nullable=False) - provider_name = db.Column(db.String(255), nullable=False) - provider_type = db.Column(db.String(40), nullable=False, server_default=db.text("'custom'::character varying")) - encrypted_config = db.Column(db.Text, nullable=True) - is_valid = db.Column(db.Boolean, nullable=False, server_default=db.text("false")) - last_used = db.Column(db.DateTime, nullable=True) + id: Mapped[str] = mapped_column(StringUUID, server_default=text("uuid_generate_v4()")) + tenant_id: Mapped[str] = mapped_column(StringUUID, nullable=False) + provider_name: Mapped[str] = mapped_column(db.String(255), nullable=False) + provider_type: Mapped[str] = mapped_column( + db.String(40), nullable=False, server_default=text("'custom'::character varying") + ) + encrypted_config: Mapped[Optional[str]] = mapped_column(db.Text, nullable=True) + is_valid: Mapped[bool] = mapped_column(db.Boolean, nullable=False, server_default=text("false")) + last_used: Mapped[Optional[datetime]] = mapped_column(db.DateTime, nullable=True) - quota_type = db.Column(db.String(40), nullable=True, server_default=db.text("''::character varying")) - quota_limit = db.Column(db.BigInteger, nullable=True) - quota_used = db.Column(db.BigInteger, default=0) + quota_type: Mapped[Optional[str]] = mapped_column( + db.String(40), nullable=True, server_default=text("''::character varying") + ) + quota_limit: Mapped[Optional[int]] = mapped_column(db.BigInteger, nullable=True) + quota_used: Mapped[Optional[int]] = mapped_column(db.BigInteger, default=0) - created_at = db.Column(db.DateTime, nullable=False, server_default=func.current_timestamp()) - updated_at = db.Column(db.DateTime, nullable=False, server_default=func.current_timestamp()) + created_at: Mapped[datetime] = mapped_column(db.DateTime, nullable=False, server_default=func.current_timestamp()) + updated_at: Mapped[datetime] = mapped_column(db.DateTime, nullable=False, server_default=func.current_timestamp()) def __repr__(self): return ( @@ -105,15 +111,15 @@ class ProviderModel(Base): ), ) - id = db.Column(StringUUID, server_default=db.text("uuid_generate_v4()")) - tenant_id = db.Column(StringUUID, nullable=False) - provider_name = db.Column(db.String(255), nullable=False) - model_name = db.Column(db.String(255), nullable=False) - model_type = db.Column(db.String(40), nullable=False) - encrypted_config = db.Column(db.Text, nullable=True) - is_valid = db.Column(db.Boolean, nullable=False, server_default=db.text("false")) - created_at = db.Column(db.DateTime, nullable=False, server_default=func.current_timestamp()) - updated_at = db.Column(db.DateTime, nullable=False, server_default=func.current_timestamp()) + id: Mapped[str] = mapped_column(StringUUID, server_default=text("uuid_generate_v4()")) + tenant_id: Mapped[str] = mapped_column(StringUUID, nullable=False) + provider_name: Mapped[str] = mapped_column(db.String(255), nullable=False) + model_name: Mapped[str] = mapped_column(db.String(255), nullable=False) + model_type: Mapped[str] = mapped_column(db.String(40), nullable=False) + encrypted_config: Mapped[Optional[str]] = mapped_column(db.Text, nullable=True) + is_valid: Mapped[bool] = mapped_column(db.Boolean, nullable=False, server_default=text("false")) + created_at: Mapped[datetime] = mapped_column(db.DateTime, nullable=False, server_default=func.current_timestamp()) + updated_at: Mapped[datetime] = mapped_column(db.DateTime, nullable=False, server_default=func.current_timestamp()) class TenantDefaultModel(Base): @@ -123,13 +129,13 @@ class TenantDefaultModel(Base): db.Index("tenant_default_model_tenant_id_provider_type_idx", "tenant_id", "provider_name", "model_type"), ) - id = db.Column(StringUUID, server_default=db.text("uuid_generate_v4()")) - tenant_id = db.Column(StringUUID, nullable=False) - provider_name = db.Column(db.String(255), nullable=False) - model_name = db.Column(db.String(255), nullable=False) - model_type = db.Column(db.String(40), nullable=False) - created_at = db.Column(db.DateTime, nullable=False, server_default=func.current_timestamp()) - updated_at = db.Column(db.DateTime, nullable=False, server_default=func.current_timestamp()) + id: Mapped[str] = mapped_column(StringUUID, server_default=text("uuid_generate_v4()")) + tenant_id: Mapped[str] = mapped_column(StringUUID, nullable=False) + provider_name: Mapped[str] = mapped_column(db.String(255), nullable=False) + model_name: Mapped[str] = mapped_column(db.String(255), nullable=False) + model_type: Mapped[str] = mapped_column(db.String(40), nullable=False) + created_at: Mapped[datetime] = mapped_column(db.DateTime, nullable=False, server_default=func.current_timestamp()) + updated_at: Mapped[datetime] = mapped_column(db.DateTime, nullable=False, server_default=func.current_timestamp()) class TenantPreferredModelProvider(Base): @@ -139,12 +145,12 @@ class TenantPreferredModelProvider(Base): db.Index("tenant_preferred_model_provider_tenant_provider_idx", "tenant_id", "provider_name"), ) - id = db.Column(StringUUID, server_default=db.text("uuid_generate_v4()")) - tenant_id = db.Column(StringUUID, nullable=False) - provider_name = db.Column(db.String(255), nullable=False) - preferred_provider_type = db.Column(db.String(40), nullable=False) - created_at = db.Column(db.DateTime, nullable=False, server_default=func.current_timestamp()) - updated_at = db.Column(db.DateTime, nullable=False, server_default=func.current_timestamp()) + id: Mapped[str] = mapped_column(StringUUID, server_default=text("uuid_generate_v4()")) + tenant_id: Mapped[str] = mapped_column(StringUUID, nullable=False) + provider_name: Mapped[str] = mapped_column(db.String(255), nullable=False) + preferred_provider_type: Mapped[str] = mapped_column(db.String(40), nullable=False) + created_at: Mapped[datetime] = mapped_column(db.DateTime, nullable=False, server_default=func.current_timestamp()) + updated_at: Mapped[datetime] = mapped_column(db.DateTime, nullable=False, server_default=func.current_timestamp()) class ProviderOrder(Base): @@ -154,22 +160,24 @@ class ProviderOrder(Base): db.Index("provider_order_tenant_provider_idx", "tenant_id", "provider_name"), ) - id = db.Column(StringUUID, server_default=db.text("uuid_generate_v4()")) - tenant_id = db.Column(StringUUID, nullable=False) - provider_name = db.Column(db.String(255), nullable=False) - account_id = db.Column(StringUUID, nullable=False) - payment_product_id = db.Column(db.String(191), nullable=False) - payment_id = db.Column(db.String(191)) - transaction_id = db.Column(db.String(191)) - quantity = db.Column(db.Integer, nullable=False, server_default=db.text("1")) - currency = db.Column(db.String(40)) - total_amount = db.Column(db.Integer) - payment_status = db.Column(db.String(40), nullable=False, server_default=db.text("'wait_pay'::character varying")) - paid_at = db.Column(db.DateTime) - pay_failed_at = db.Column(db.DateTime) - refunded_at = db.Column(db.DateTime) - created_at = db.Column(db.DateTime, nullable=False, server_default=func.current_timestamp()) - updated_at = db.Column(db.DateTime, nullable=False, server_default=func.current_timestamp()) + id: Mapped[str] = mapped_column(StringUUID, server_default=text("uuid_generate_v4()")) + tenant_id: Mapped[str] = mapped_column(StringUUID, nullable=False) + provider_name: Mapped[str] = mapped_column(db.String(255), nullable=False) + account_id: Mapped[str] = mapped_column(StringUUID, nullable=False) + payment_product_id: Mapped[str] = mapped_column(db.String(191), nullable=False) + payment_id: Mapped[Optional[str]] = mapped_column(db.String(191)) + transaction_id: Mapped[Optional[str]] = mapped_column(db.String(191)) + quantity: Mapped[int] = mapped_column(db.Integer, nullable=False, server_default=text("1")) + currency: Mapped[Optional[str]] = mapped_column(db.String(40)) + total_amount: Mapped[Optional[int]] = mapped_column(db.Integer) + payment_status: Mapped[str] = mapped_column( + db.String(40), nullable=False, server_default=text("'wait_pay'::character varying") + ) + paid_at: Mapped[Optional[datetime]] = mapped_column(db.DateTime) + pay_failed_at: Mapped[Optional[datetime]] = mapped_column(db.DateTime) + refunded_at: Mapped[Optional[datetime]] = mapped_column(db.DateTime) + created_at: Mapped[datetime] = mapped_column(db.DateTime, nullable=False, server_default=func.current_timestamp()) + updated_at: Mapped[datetime] = mapped_column(db.DateTime, nullable=False, server_default=func.current_timestamp()) class ProviderModelSetting(Base): @@ -183,15 +191,15 @@ class ProviderModelSetting(Base): db.Index("provider_model_setting_tenant_provider_model_idx", "tenant_id", "provider_name", "model_type"), ) - id = db.Column(StringUUID, server_default=db.text("uuid_generate_v4()")) - tenant_id = db.Column(StringUUID, nullable=False) - provider_name = db.Column(db.String(255), nullable=False) - model_name = db.Column(db.String(255), nullable=False) - model_type = db.Column(db.String(40), nullable=False) - enabled = db.Column(db.Boolean, nullable=False, server_default=db.text("true")) - load_balancing_enabled = db.Column(db.Boolean, nullable=False, server_default=db.text("false")) - created_at = db.Column(db.DateTime, nullable=False, server_default=func.current_timestamp()) - updated_at = db.Column(db.DateTime, nullable=False, server_default=func.current_timestamp()) + id: Mapped[str] = mapped_column(StringUUID, server_default=text("uuid_generate_v4()")) + tenant_id: Mapped[str] = mapped_column(StringUUID, nullable=False) + provider_name: Mapped[str] = mapped_column(db.String(255), nullable=False) + model_name: Mapped[str] = mapped_column(db.String(255), nullable=False) + model_type: Mapped[str] = mapped_column(db.String(40), nullable=False) + enabled: Mapped[bool] = mapped_column(db.Boolean, nullable=False, server_default=text("true")) + load_balancing_enabled: Mapped[bool] = mapped_column(db.Boolean, nullable=False, server_default=text("false")) + created_at: Mapped[datetime] = mapped_column(db.DateTime, nullable=False, server_default=func.current_timestamp()) + updated_at: Mapped[datetime] = mapped_column(db.DateTime, nullable=False, server_default=func.current_timestamp()) class LoadBalancingModelConfig(Base): @@ -205,13 +213,13 @@ class LoadBalancingModelConfig(Base): db.Index("load_balancing_model_config_tenant_provider_model_idx", "tenant_id", "provider_name", "model_type"), ) - id = db.Column(StringUUID, server_default=db.text("uuid_generate_v4()")) - tenant_id = db.Column(StringUUID, nullable=False) - provider_name = db.Column(db.String(255), nullable=False) - model_name = db.Column(db.String(255), nullable=False) - model_type = db.Column(db.String(40), nullable=False) - name = db.Column(db.String(255), nullable=False) - encrypted_config = db.Column(db.Text, nullable=True) - enabled = db.Column(db.Boolean, nullable=False, server_default=db.text("true")) - created_at = db.Column(db.DateTime, nullable=False, server_default=func.current_timestamp()) - updated_at = db.Column(db.DateTime, nullable=False, server_default=func.current_timestamp()) + id: Mapped[str] = mapped_column(StringUUID, server_default=text("uuid_generate_v4()")) + tenant_id: Mapped[str] = mapped_column(StringUUID, nullable=False) + provider_name: Mapped[str] = mapped_column(db.String(255), nullable=False) + model_name: Mapped[str] = mapped_column(db.String(255), nullable=False) + model_type: Mapped[str] = mapped_column(db.String(40), nullable=False) + name: Mapped[str] = mapped_column(db.String(255), nullable=False) + encrypted_config: Mapped[Optional[str]] = mapped_column(db.Text, nullable=True) + enabled: Mapped[bool] = mapped_column(db.Boolean, nullable=False, server_default=text("true")) + created_at: Mapped[datetime] = mapped_column(db.DateTime, nullable=False, server_default=func.current_timestamp()) + updated_at: Mapped[datetime] = mapped_column(db.DateTime, nullable=False, server_default=func.current_timestamp()) diff --git a/api/models/source.py b/api/models/source.py index b9d7d91346..f6e0900ae6 100644 --- a/api/models/source.py +++ b/api/models/source.py @@ -9,7 +9,7 @@ from .engine import db from .types import StringUUID -class DataSourceOauthBinding(db.Model): # type: ignore[name-defined] +class DataSourceOauthBinding(Base): __tablename__ = "data_source_oauth_bindings" __table_args__ = ( db.PrimaryKeyConstraint("id", name="source_binding_pkey"), diff --git a/api/models/tools.py b/api/models/tools.py index aef1490729..03fbc3acb1 100644 --- a/api/models/tools.py +++ b/api/models/tools.py @@ -1,6 +1,6 @@ import json from datetime import datetime -from typing import Any, Optional, cast +from typing import Any, cast import sqlalchemy as sa from deprecated import deprecated @@ -172,10 +172,6 @@ class WorkflowToolProvider(Base): db.DateTime, nullable=False, server_default=db.text("CURRENT_TIMESTAMP(0)") ) - @property - def schema_type(self) -> ApiProviderSchemaType: - return ApiProviderSchemaType.value_of(self.schema_type_str) - @property def user(self) -> Account | None: return db.session.query(Account).filter(Account.id == self.user_id).first() @@ -263,8 +259,8 @@ class ToolConversationVariables(Base): class ToolFile(Base): - """ - store the file created by agent + """This table stores file metadata generated in workflows, + not only files created by agent. """ __tablename__ = "tool_files" @@ -304,8 +300,11 @@ class DeprecatedPublishedAppTool(Base): db.UniqueConstraint("app_id", "user_id", name="unique_published_app_tool"), ) + id = db.Column(StringUUID, server_default=db.text("uuid_generate_v4()")) # id of the app app_id = db.Column(StringUUID, ForeignKey("apps.id"), nullable=False) + + user_id: Mapped[str] = db.Column(StringUUID, nullable=False) # who published this tool description = db.Column(db.Text, nullable=False) # llm_description of the tool, for LLM @@ -325,34 +324,3 @@ class DeprecatedPublishedAppTool(Base): @property def description_i18n(self) -> I18nObject: return I18nObject(**json.loads(self.description)) - - id = db.Column(StringUUID, server_default=db.text("uuid_generate_v4()")) - user_id: Mapped[str] = db.Column(StringUUID, nullable=False) - tenant_id: Mapped[str] = db.Column(StringUUID, nullable=False) - conversation_id: Mapped[Optional[str]] = db.Column(StringUUID, nullable=True) - file_key: Mapped[str] = db.Column(db.String(255), nullable=False) - mimetype: Mapped[str] = db.Column(db.String(255), nullable=False) - original_url: Mapped[Optional[str]] = db.Column(db.String(2048), nullable=True) - name: Mapped[str] = mapped_column(default="") - size: Mapped[int] = mapped_column(default=-1) - - def __init__( - self, - *, - user_id: str, - tenant_id: str, - conversation_id: Optional[str] = None, - file_key: str, - mimetype: str, - original_url: Optional[str] = None, - name: str, - size: int, - ): - self.user_id = user_id - self.tenant_id = tenant_id - self.conversation_id = conversation_id - self.file_key = file_key - self.mimetype = mimetype - self.original_url = original_url - self.name = name - self.size = size diff --git a/api/models/types.py b/api/models/types.py index cb6773e70c..e5581c3ab0 100644 --- a/api/models/types.py +++ b/api/models/types.py @@ -1,4 +1,7 @@ -from sqlalchemy import CHAR, TypeDecorator +import enum +from typing import Generic, TypeVar + +from sqlalchemy import CHAR, VARCHAR, TypeDecorator from sqlalchemy.dialects.postgresql import UUID @@ -24,3 +27,51 @@ class StringUUID(TypeDecorator): if value is None: return value return str(value) + + +_E = TypeVar("_E", bound=enum.StrEnum) + + +class EnumText(TypeDecorator, Generic[_E]): + impl = VARCHAR + cache_ok = True + + _length: int + _enum_class: type[_E] + + def __init__(self, enum_class: type[_E], length: int | None = None): + self._enum_class = enum_class + max_enum_value_len = max(len(e.value) for e in enum_class) + if length is not None: + if length < max_enum_value_len: + raise ValueError("length should be greater than enum value length.") + self._length = length + else: + # leave some rooms for future longer enum values. + self._length = max(max_enum_value_len, 20) + + def process_bind_param(self, value: _E | str | None, dialect): + if value is None: + return value + if isinstance(value, self._enum_class): + return value.value + elif isinstance(value, str): + self._enum_class(value) + return value + else: + raise TypeError(f"expected str or {self._enum_class}, got {type(value)}") + + def load_dialect_impl(self, dialect): + return dialect.type_descriptor(VARCHAR(self._length)) + + def process_result_value(self, value, dialect) -> _E | None: + if value is None: + return value + if not isinstance(value, str): + raise TypeError(f"expected str, got {type(value)}") + return self._enum_class(value) + + def compare_values(self, x, y): + if x is None or y is None: + return x is y + return x == y diff --git a/api/models/workflow.py b/api/models/workflow.py index da60617de5..2fff045543 100644 --- a/api/models/workflow.py +++ b/api/models/workflow.py @@ -1,29 +1,37 @@ import json +import logging from collections.abc import Mapping, Sequence from datetime import UTC, datetime from enum import Enum, StrEnum -from typing import TYPE_CHECKING, Any, Optional, Self, Union +from typing import TYPE_CHECKING, Any, Optional, Union from uuid import uuid4 +from flask_login import current_user + +from core.variables import utils as variable_utils +from core.workflow.constants import CONVERSATION_VARIABLE_NODE_ID, SYSTEM_VARIABLE_NODE_ID +from factories.variable_factory import build_segment + if TYPE_CHECKING: from models.model import AppMode import sqlalchemy as sa -from sqlalchemy import Index, PrimaryKeyConstraint, func -from sqlalchemy.orm import Mapped, mapped_column +from sqlalchemy import Index, PrimaryKeyConstraint, UniqueConstraint, func +from sqlalchemy.orm import Mapped, declared_attr, mapped_column -import contexts from constants import DEFAULT_FILE_NUMBER_LIMITS, HIDDEN_VALUE from core.helper import encrypter -from core.variables import SecretVariable, Variable +from core.variables import SecretVariable, Segment, SegmentType, Variable from factories import variable_factory from libs import helper -from models.base import Base -from models.enums import CreatedByRole from .account import Account +from .base import Base from .engine import db -from .types import StringUUID +from .enums import CreatorUserRole, DraftVariableType +from .types import EnumText, StringUUID + +_logger = logging.getLogger(__name__) if TYPE_CHECKING: from models.model import AppMode @@ -143,7 +151,7 @@ class Workflow(Base): conversation_variables: Sequence[Variable], marked_name: str = "", marked_comment: str = "", - ) -> Self: + ) -> "Workflow": workflow = Workflow() workflow.id = str(uuid4()) workflow.tenant_id = tenant_id @@ -192,7 +200,9 @@ class Workflow(Base): features["file_upload"]["number_limits"] = image_number_limits features["file_upload"]["allowed_file_upload_methods"] = image_transfer_methods features["file_upload"]["allowed_file_types"] = features["file_upload"].get("allowed_file_types", ["image"]) - features["file_upload"]["allowed_file_extensions"] = [] + features["file_upload"]["allowed_file_extensions"] = features["file_upload"].get( + "allowed_file_extensions", [] + ) del features["file_upload"]["image"] self._features = json.dumps(features) return self._features @@ -265,7 +275,16 @@ class Workflow(Base): if self._environment_variables is None: self._environment_variables = "{}" - tenant_id = contexts.tenant_id.get() + # Get tenant_id from current_user (Account or EndUser) + if isinstance(current_user, Account): + # Account user + tenant_id = current_user.current_tenant_id + else: + # EndUser + tenant_id = current_user.tenant_id + + if not tenant_id: + return [] environment_variables_dict: dict[str, Any] = json.loads(self._environment_variables) results = [ @@ -288,7 +307,17 @@ class Workflow(Base): self._environment_variables = "{}" return - tenant_id = contexts.tenant_id.get() + # Get tenant_id from current_user (Account or EndUser) + if isinstance(current_user, Account): + # Account user + tenant_id = current_user.current_tenant_id + else: + # EndUser + tenant_id = current_user.tenant_id + + if not tenant_id: + self._environment_variables = "{}" + return value = list(value) if any(var for var in value if not var.id): @@ -348,18 +377,6 @@ class Workflow(Base): ) -class WorkflowRunStatus(StrEnum): - """ - Workflow Run Status Enum - """ - - RUNNING = "running" - SUCCEEDED = "succeeded" - FAILED = "failed" - STOPPED = "stopped" - PARTIAL_SUCCEEDED = "partial-succeeded" - - class WorkflowRun(Base): """ Workflow Run @@ -418,29 +435,29 @@ class WorkflowRun(Base): status: Mapped[str] = mapped_column(db.String(255)) # running, succeeded, failed, stopped, partial-succeeded outputs: Mapped[Optional[str]] = mapped_column(sa.Text, default="{}") error: Mapped[Optional[str]] = mapped_column(db.Text) - elapsed_time = db.Column(db.Float, nullable=False, server_default=sa.text("0")) + elapsed_time: Mapped[float] = mapped_column(db.Float, nullable=False, server_default=sa.text("0")) total_tokens: Mapped[int] = mapped_column(sa.BigInteger, server_default=sa.text("0")) - total_steps = db.Column(db.Integer, server_default=db.text("0")) + total_steps: Mapped[int] = mapped_column(db.Integer, server_default=db.text("0"), nullable=True) created_by_role: Mapped[str] = mapped_column(db.String(255)) # account, end_user - created_by = db.Column(StringUUID, nullable=False) - created_at = db.Column(db.DateTime, nullable=False, server_default=func.current_timestamp()) - finished_at = db.Column(db.DateTime) - exceptions_count = db.Column(db.Integer, server_default=db.text("0")) + created_by: Mapped[str] = mapped_column(StringUUID, nullable=False) + created_at: Mapped[datetime] = mapped_column(db.DateTime, nullable=False, server_default=func.current_timestamp()) + finished_at: Mapped[Optional[datetime]] = mapped_column(db.DateTime) + exceptions_count: Mapped[int] = mapped_column(db.Integer, server_default=db.text("0"), nullable=True) @property def created_by_account(self): - created_by_role = CreatedByRole(self.created_by_role) - return db.session.get(Account, self.created_by) if created_by_role == CreatedByRole.ACCOUNT else None + created_by_role = CreatorUserRole(self.created_by_role) + return db.session.get(Account, self.created_by) if created_by_role == CreatorUserRole.ACCOUNT else None @property def created_by_end_user(self): from models.model import EndUser - created_by_role = CreatedByRole(self.created_by_role) - return db.session.get(EndUser, self.created_by) if created_by_role == CreatedByRole.END_USER else None + created_by_role = CreatorUserRole(self.created_by_role) + return db.session.get(EndUser, self.created_by) if created_by_role == CreatorUserRole.END_USER else None @property - def graph_dict(self): + def graph_dict(self) -> Mapping[str, Any]: return json.loads(self.graph) if self.graph else {} @property @@ -524,19 +541,7 @@ class WorkflowNodeExecutionTriggeredFrom(StrEnum): WORKFLOW_RUN = "workflow-run" -class WorkflowNodeExecutionStatus(StrEnum): - """ - Workflow Node Execution Status Enum - """ - - RUNNING = "running" - SUCCEEDED = "succeeded" - FAILED = "failed" - EXCEPTION = "exception" - RETRY = "retry" - - -class WorkflowNodeExecution(Base): +class WorkflowNodeExecutionModel(Base): """ Workflow Node Execution @@ -585,28 +590,48 @@ class WorkflowNodeExecution(Base): """ __tablename__ = "workflow_node_executions" - __table_args__ = ( - db.PrimaryKeyConstraint("id", name="workflow_node_execution_pkey"), - db.Index( - "workflow_node_execution_workflow_run_idx", - "tenant_id", - "app_id", - "workflow_id", - "triggered_from", - "workflow_run_id", - ), - db.Index( - "workflow_node_execution_node_run_idx", "tenant_id", "app_id", "workflow_id", "triggered_from", "node_id" - ), - db.Index( - "workflow_node_execution_id_idx", - "tenant_id", - "app_id", - "workflow_id", - "triggered_from", - "node_execution_id", - ), - ) + + @declared_attr + def __table_args__(cls): # noqa + return ( + PrimaryKeyConstraint("id", name="workflow_node_execution_pkey"), + Index( + "workflow_node_execution_workflow_run_idx", + "tenant_id", + "app_id", + "workflow_id", + "triggered_from", + "workflow_run_id", + ), + Index( + "workflow_node_execution_node_run_idx", + "tenant_id", + "app_id", + "workflow_id", + "triggered_from", + "node_id", + ), + Index( + "workflow_node_execution_id_idx", + "tenant_id", + "app_id", + "workflow_id", + "triggered_from", + "node_execution_id", + ), + Index( + # The first argument is the index name, + # which we leave as `None`` to allow auto-generation by the ORM. + None, + cls.tenant_id, + cls.workflow_id, + cls.node_id, + # MyPy may flag the following line because it doesn't recognize that + # the `declared_attr` decorator passes the receiving class as the first + # argument to this method, allowing us to reference class attributes. + cls.created_at.desc(), # type: ignore + ), + ) id: Mapped[str] = mapped_column(StringUUID, server_default=db.text("uuid_generate_v4()")) tenant_id: Mapped[str] = mapped_column(StringUUID) @@ -634,24 +659,24 @@ class WorkflowNodeExecution(Base): @property def created_by_account(self): - created_by_role = CreatedByRole(self.created_by_role) + created_by_role = CreatorUserRole(self.created_by_role) # TODO(-LAN-): Avoid using db.session.get() here. - return db.session.get(Account, self.created_by) if created_by_role == CreatedByRole.ACCOUNT else None + return db.session.get(Account, self.created_by) if created_by_role == CreatorUserRole.ACCOUNT else None @property def created_by_end_user(self): from models.model import EndUser - created_by_role = CreatedByRole(self.created_by_role) + created_by_role = CreatorUserRole(self.created_by_role) # TODO(-LAN-): Avoid using db.session.get() here. - return db.session.get(EndUser, self.created_by) if created_by_role == CreatedByRole.END_USER else None + return db.session.get(EndUser, self.created_by) if created_by_role == CreatorUserRole.END_USER else None @property def inputs_dict(self): return json.loads(self.inputs) if self.inputs else None @property - def outputs_dict(self): + def outputs_dict(self) -> dict[str, Any] | None: return json.loads(self.outputs) if self.outputs else None @property @@ -659,8 +684,11 @@ class WorkflowNodeExecution(Base): return json.loads(self.process_data) if self.process_data else None @property - def execution_metadata_dict(self): - return json.loads(self.execution_metadata) if self.execution_metadata else None + def execution_metadata_dict(self) -> dict[str, Any]: + # When the metadata is unset, we return an empty dictionary instead of `None`. + # This approach streamlines the logic for the caller, making it easier to handle + # cases where metadata is absent. + return json.loads(self.execution_metadata) if self.execution_metadata else {} @property def extras(self): @@ -736,19 +764,18 @@ class WorkflowAppLog(Base): __tablename__ = "workflow_app_logs" __table_args__ = ( db.PrimaryKeyConstraint("id", name="workflow_app_log_pkey"), - db.Index("workflow_app_log_app_idx", "tenant_id", "app_id", "created_at"), - db.Index("workflow_app_log_workflow_run_idx", "workflow_run_id"), + db.Index("workflow_app_log_app_idx", "tenant_id", "app_id"), ) id: Mapped[str] = mapped_column(StringUUID, server_default=db.text("uuid_generate_v4()")) tenant_id: Mapped[str] = mapped_column(StringUUID) app_id: Mapped[str] = mapped_column(StringUUID) - workflow_id = db.Column(StringUUID, nullable=False) + workflow_id: Mapped[str] = mapped_column(StringUUID, nullable=False) workflow_run_id: Mapped[str] = mapped_column(StringUUID) - created_from = db.Column(db.String(255), nullable=False) - created_by_role = db.Column(db.String(255), nullable=False) - created_by = db.Column(StringUUID, nullable=False) - created_at = db.Column(db.DateTime, nullable=False, server_default=func.current_timestamp()) + created_from: Mapped[str] = mapped_column(db.String(255), nullable=False) + created_by_role: Mapped[str] = mapped_column(db.String(255), nullable=False) + created_by: Mapped[str] = mapped_column(StringUUID, nullable=False) + created_at: Mapped[datetime] = mapped_column(db.DateTime, nullable=False, server_default=func.current_timestamp()) @property def workflow_run(self): @@ -756,31 +783,28 @@ class WorkflowAppLog(Base): @property def created_by_account(self): - created_by_role = CreatedByRole(self.created_by_role) - return db.session.get(Account, self.created_by) if created_by_role == CreatedByRole.ACCOUNT else None + created_by_role = CreatorUserRole(self.created_by_role) + return db.session.get(Account, self.created_by) if created_by_role == CreatorUserRole.ACCOUNT else None @property def created_by_end_user(self): from models.model import EndUser - created_by_role = CreatedByRole(self.created_by_role) - return db.session.get(EndUser, self.created_by) if created_by_role == CreatedByRole.END_USER else None + created_by_role = CreatorUserRole(self.created_by_role) + return db.session.get(EndUser, self.created_by) if created_by_role == CreatorUserRole.END_USER else None class ConversationVariable(Base): __tablename__ = "workflow_conversation_variables" - __table_args__ = ( - PrimaryKeyConstraint("id", "conversation_id", name="workflow_conversation_variables_pkey"), - Index("workflow__conversation_variables_app_id_idx", "app_id"), - Index("workflow__conversation_variables_created_at_idx", "created_at"), - ) id: Mapped[str] = mapped_column(StringUUID, primary_key=True) - conversation_id: Mapped[str] = mapped_column(StringUUID, nullable=False, primary_key=True) - app_id: Mapped[str] = mapped_column(StringUUID, nullable=False) - data = mapped_column(db.Text, nullable=False) - created_at = mapped_column(db.DateTime, nullable=False, server_default=func.current_timestamp()) - updated_at = mapped_column( + conversation_id: Mapped[str] = mapped_column(StringUUID, nullable=False, primary_key=True, index=True) + app_id: Mapped[str] = mapped_column(StringUUID, nullable=False, index=True) + data: Mapped[str] = mapped_column(db.Text, nullable=False) + created_at: Mapped[datetime] = mapped_column( + db.DateTime, nullable=False, server_default=func.current_timestamp(), index=True + ) + updated_at: Mapped[datetime] = mapped_column( db.DateTime, nullable=False, server_default=func.current_timestamp(), onupdate=func.current_timestamp() ) @@ -803,3 +827,216 @@ class ConversationVariable(Base): def to_variable(self) -> Variable: mapping = json.loads(self.data) return variable_factory.build_conversation_variable_from_mapping(mapping) + + +# Only `sys.query` and `sys.files` could be modified. +_EDITABLE_SYSTEM_VARIABLE = frozenset(["query", "files"]) + + +def _naive_utc_datetime(): + return datetime.now(UTC).replace(tzinfo=None) + + +class WorkflowDraftVariable(Base): + @staticmethod + def unique_columns() -> list[str]: + return [ + "app_id", + "node_id", + "name", + ] + + __tablename__ = "workflow_draft_variables" + __table_args__ = (UniqueConstraint(*unique_columns()),) + + # id is the unique identifier of a draft variable. + id: Mapped[str] = mapped_column(StringUUID, primary_key=True, server_default=db.text("uuid_generate_v4()")) + + created_at: Mapped[datetime] = mapped_column( + db.DateTime, + nullable=False, + default=_naive_utc_datetime, + server_default=func.current_timestamp(), + ) + + updated_at: Mapped[datetime] = mapped_column( + db.DateTime, + nullable=False, + default=_naive_utc_datetime, + server_default=func.current_timestamp(), + onupdate=func.current_timestamp(), + ) + + # "`app_id` maps to the `id` field in the `model.App` model." + app_id: Mapped[str] = mapped_column(StringUUID, nullable=False) + + # `last_edited_at` records when the value of a given draft variable + # is edited. + # + # If it's not edited after creation, its value is `None`. + last_edited_at: Mapped[datetime | None] = mapped_column( + db.DateTime, + nullable=True, + default=None, + ) + + # The `node_id` field is special. + # + # If the variable is a conversation variable or a system variable, then the value of `node_id` + # is `conversation` or `sys`, respective. + # + # Otherwise, if the variable is a variable belonging to a specific node, the value of `_node_id` is + # the identity of correspond node in graph definition. An example of node id is `"1745769620734"`. + # + # However, there's one caveat. The id of the first "Answer" node in chatflow is "answer". (Other + # "Answer" node conform the rules above.) + node_id: Mapped[str] = mapped_column(sa.String(255), nullable=False, name="node_id") + + # From `VARIABLE_PATTERN`, we may conclude that the length of a top level variable is less than + # 80 chars. + # + # ref: api/core/workflow/entities/variable_pool.py:18 + name: Mapped[str] = mapped_column(sa.String(255), nullable=False) + description: Mapped[str] = mapped_column( + sa.String(255), + default="", + nullable=False, + ) + + selector: Mapped[str] = mapped_column(sa.String(255), nullable=False, name="selector") + + # The data type of this variable's value + value_type: Mapped[SegmentType] = mapped_column(EnumText(SegmentType, length=20)) + + # The variable's value serialized as a JSON string + value: Mapped[str] = mapped_column(sa.Text, nullable=False, name="value") + + # Controls whether the variable should be displayed in the variable inspection panel + visible: Mapped[bool] = mapped_column(sa.Boolean, nullable=False, default=True) + + # Determines whether this variable can be modified by users + editable: Mapped[bool] = mapped_column(sa.Boolean, nullable=False, default=False) + + # The `node_execution_id` field identifies the workflow node execution that created this variable. + # It corresponds to the `id` field in the `WorkflowNodeExecutionModel` model. + # + # This field is not `None` for system variables and node variables, and is `None` + # for conversation variables. + node_execution_id: Mapped[str | None] = mapped_column( + StringUUID, + nullable=True, + default=None, + ) + + def get_selector(self) -> list[str]: + selector = json.loads(self.selector) + if not isinstance(selector, list): + _logger.error( + "invalid selector loaded from database, type=%s, value=%s", + type(selector), + self.selector, + ) + raise ValueError("invalid selector.") + return selector + + def _set_selector(self, value: list[str]): + self.selector = json.dumps(value) + + def get_value(self) -> Segment | None: + return build_segment(json.loads(self.value)) + + def set_name(self, name: str): + self.name = name + self._set_selector([self.node_id, name]) + + def set_value(self, value: Segment): + self.value = json.dumps(value.value) + self.value_type = value.value_type + + def get_node_id(self) -> str | None: + if self.get_variable_type() == DraftVariableType.NODE: + return self.node_id + else: + return None + + def get_variable_type(self) -> DraftVariableType: + match self.node_id: + case DraftVariableType.CONVERSATION: + return DraftVariableType.CONVERSATION + case DraftVariableType.SYS: + return DraftVariableType.SYS + case _: + return DraftVariableType.NODE + + @classmethod + def _new( + cls, + *, + app_id: str, + node_id: str, + name: str, + value: Segment, + description: str = "", + ) -> "WorkflowDraftVariable": + variable = WorkflowDraftVariable() + variable.created_at = _naive_utc_datetime() + variable.updated_at = _naive_utc_datetime() + variable.description = description + variable.app_id = app_id + variable.node_id = node_id + variable.name = name + variable.set_value(value) + variable._set_selector(list(variable_utils.to_selector(node_id, name))) + return variable + + @classmethod + def new_conversation_variable( + cls, + *, + app_id: str, + name: str, + value: Segment, + ) -> "WorkflowDraftVariable": + variable = cls._new( + app_id=app_id, + node_id=CONVERSATION_VARIABLE_NODE_ID, + name=name, + value=value, + ) + return variable + + @classmethod + def new_sys_variable( + cls, + *, + app_id: str, + name: str, + value: Segment, + editable: bool = False, + ) -> "WorkflowDraftVariable": + variable = cls._new(app_id=app_id, node_id=SYSTEM_VARIABLE_NODE_ID, name=name, value=value) + variable.editable = editable + return variable + + @classmethod + def new_node_variable( + cls, + *, + app_id: str, + node_id: str, + name: str, + value: Segment, + visible: bool = True, + ) -> "WorkflowDraftVariable": + variable = cls._new(app_id=app_id, node_id=node_id, name=name, value=value) + variable.visible = visible + variable.editable = True + return variable + + @property + def edited(self): + return self.last_edited_at is not None + + +def is_system_variable_editable(name: str) -> bool: + return name in _EDITABLE_SYSTEM_VARIABLE diff --git a/api/mypy.ini b/api/mypy.ini index 2898b9b52d..12fe529b08 100644 --- a/api/mypy.ini +++ b/api/mypy.ini @@ -2,8 +2,20 @@ warn_return_any = True warn_unused_configs = True check_untyped_defs = True +cache_fine_grained = True +sqlite_cache = True exclude = (?x)( core/model_runtime/model_providers/ | tests/ | migrations/ ) + +[mypy-flask_login] +ignore_missing_imports=True + +[mypy-flask_restful] +ignore_missing_imports=True + +[mypy-flask_restful.inputs] +ignore_missing_imports=True + diff --git a/api/pyproject.toml b/api/pyproject.toml index 4992178423..9631586ed4 100644 --- a/api/pyproject.toml +++ b/api/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "dify-api" -version = "1.2.0" +dynamic = ["version"] requires-python = ">=3.11,<3.13" dependencies = [ @@ -10,11 +10,11 @@ dependencies = [ "boto3==1.35.99", "bs4~=0.0.1", "cachetools~=5.3.0", - "celery~=5.4.0", + "celery~=5.5.2", "chardet~=5.1.0", "flask~=3.1.0", "flask-compress~=1.17", - "flask-cors~=4.0.0", + "flask-cors~=6.0.0", "flask-login~=0.6.3", "flask-migrate~=4.0.7", "flask-restful~=0.3.10", @@ -36,10 +36,9 @@ dependencies = [ "mailchimp-transactional~=1.0.50", "markdown~=3.5.1", "numpy~=1.26.4", - "oci~=2.135.1", "openai~=1.61.0", "openpyxl~=3.1.5", - "opik~=1.3.4", + "opik~=1.7.25", "opentelemetry-api==1.27.0", "opentelemetry-distro==0.48b0", "opentelemetry-exporter-otlp==1.27.0", @@ -57,38 +56,41 @@ dependencies = [ "opentelemetry-sdk==1.27.0", "opentelemetry-semantic-conventions==0.48b0", "opentelemetry-util-http==0.48b0", - "pandas-stubs~=2.2.3.241009", "pandas[excel,output-formatting,performance]~=2.2.2", "pandoc~=2.4", "psycogreen~=1.0.2", "psycopg2-binary~=2.9.6", "pycryptodome==3.19.1", - "pydantic~=2.9.2", - "pydantic-extra-types~=2.9.0", - "pydantic-settings~=2.6.0", + "pydantic~=2.11.4", + "pydantic-extra-types~=2.10.3", + "pydantic-settings~=2.9.1", "pyjwt~=2.8.0", - "pypdfium2~=4.30.0", + "pypdfium2==4.30.0", "python-docx~=1.1.0", "python-dotenv==1.0.1", "pyyaml~=6.0.1", - "readabilipy==0.2.0", - "redis[hiredis]~=5.0.3", - "resend~=0.7.0", - "sentry-sdk[flask]~=1.44.1", + "readabilipy~=0.3.0", + "redis[hiredis]~=6.1.0", + "resend~=2.9.0", + "sentry-sdk[flask]~=2.28.0", "sqlalchemy~=2.0.29", "starlette==0.41.0", - "tiktoken~=0.8.0", - "tokenizers~=0.15.0", - "transformers~=4.35.0", + "tiktoken~=0.9.0", + "transformers~=4.51.0", "unstructured[docx,epub,md,ppt,pptx]~=0.16.1", - "validators==0.21.0", + "weave~=0.51.0", "yarl~=1.18.3", + "webvtt-py~=0.5.1", ] # Before adding new dependency, consider place it in # alphabet order (a-z) and suitable group. +[tool.setuptools] +packages = [] + [tool.uv] default-groups = ["storage", "tools", "vdb"] +package = false [dependency-groups] @@ -101,7 +103,7 @@ dev = [ "dotenv-linter~=0.5.0", "faker~=32.1.0", "lxml-stubs~=0.5.1", - "mypy~=1.15.0", + "mypy~=1.16.0", "ruff~=0.11.5", "pytest~=8.3.2", "pytest-benchmark~=4.0.0", @@ -115,6 +117,7 @@ dev = [ "types-defusedxml~=0.7.0", "types-deprecated~=1.2.15", "types-docutils~=0.21.0", + "types-jsonschema~=4.23.0", "types-flask-cors~=5.0.0", "types-flask-migrate~=4.1.0", "types-gevent~=24.11.0", @@ -138,11 +141,18 @@ dev = [ "types-requests~=2.32.0", "types-requests-oauthlib~=2.0.0", "types-shapely~=2.0.0", - "types-simplejson~=3.20.0", - "types-six~=1.17.0", - "types-tensorflow~=2.18.0", - "types-tqdm~=4.67.0", - "types-ujson~=5.10.0", + "types-simplejson>=3.20.0", + "types-six>=1.17.0", + "types-tensorflow>=2.18.0", + "types-tqdm>=4.67.0", + "types-ujson>=5.10.0", + "boto3-stubs>=1.38.20", + "types-jmespath>=1.0.2.20240106", + "types_pyOpenSSL>=24.1.0", + "types_cffi>=1.17.0", + "types_setuptools>=80.9.0", + "pandas-stubs~=2.2.3", + "scipy-stubs>=1.15.3.0", ] ############################################################ @@ -184,12 +194,12 @@ vdb = [ "pymilvus~=2.5.0", "pymochow==1.3.1", "pyobvector~=0.1.6", - "qdrant-client==1.7.3", + "qdrant-client==1.9.0", "tablestore==6.1.0", "tcvectordb~=1.6.4", "tidb-vector==0.0.9", "upstash-vector==0.6.0", - "volcengine-compat~=1.0.156", - "weaviate-client~=3.21.0", + "volcengine-compat~=1.0.0", + "weaviate-client~=3.24.0", "xinference-client~=1.2.2", ] diff --git a/api/pytest.ini b/api/pytest.ini index 618e921825..eb49619481 100644 --- a/api/pytest.ini +++ b/api/pytest.ini @@ -1,5 +1,4 @@ [pytest] -continue-on-collection-errors = true addopts = --cov=./api --cov-report=json --cov-report=xml env = ANTHROPIC_API_KEY = sk-ant-api11-IamNotARealKeyJustForMockTestKawaiiiiiiiiii-NotBaka-ASkksz diff --git a/api/repositories/__init__.py b/api/repositories/__init__.py deleted file mode 100644 index 4cc339688b..0000000000 --- a/api/repositories/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -""" -Repository implementations for data access. - -This package contains concrete implementations of the repository interfaces -defined in the core.repository package. -""" diff --git a/api/repositories/repository_registry.py b/api/repositories/repository_registry.py deleted file mode 100644 index aa0a208d8e..0000000000 --- a/api/repositories/repository_registry.py +++ /dev/null @@ -1,87 +0,0 @@ -""" -Registry for repository implementations. - -This module is responsible for registering factory functions with the repository factory. -""" - -import logging -from collections.abc import Mapping -from typing import Any - -from sqlalchemy.orm import sessionmaker - -from configs import dify_config -from core.repository.repository_factory import RepositoryFactory -from extensions.ext_database import db -from repositories.workflow_node_execution import SQLAlchemyWorkflowNodeExecutionRepository - -logger = logging.getLogger(__name__) - -# Storage type constants -STORAGE_TYPE_RDBMS = "rdbms" -STORAGE_TYPE_HYBRID = "hybrid" - - -def register_repositories() -> None: - """ - Register repository factory functions with the RepositoryFactory. - - This function reads configuration settings to determine which repository - implementations to register. - """ - # Configure WorkflowNodeExecutionRepository factory based on configuration - workflow_node_execution_storage = dify_config.WORKFLOW_NODE_EXECUTION_STORAGE - - # Check storage type and register appropriate implementation - if workflow_node_execution_storage == STORAGE_TYPE_RDBMS: - # Register SQLAlchemy implementation for RDBMS storage - logger.info("Registering WorkflowNodeExecution repository with RDBMS storage") - RepositoryFactory.register_workflow_node_execution_factory(create_workflow_node_execution_repository) - elif workflow_node_execution_storage == STORAGE_TYPE_HYBRID: - # Hybrid storage is not yet implemented - raise NotImplementedError("Hybrid storage for WorkflowNodeExecution repository is not yet implemented") - else: - # Unknown storage type - raise ValueError( - f"Unknown storage type '{workflow_node_execution_storage}' for WorkflowNodeExecution repository. " - f"Supported types: {STORAGE_TYPE_RDBMS}" - ) - - -def create_workflow_node_execution_repository(params: Mapping[str, Any]) -> SQLAlchemyWorkflowNodeExecutionRepository: - """ - Create a WorkflowNodeExecutionRepository instance using SQLAlchemy implementation. - - This factory function creates a repository for the RDBMS storage type. - - Args: - params: Parameters for creating the repository, including: - - tenant_id: Required. The tenant ID for multi-tenancy. - - app_id: Optional. The application ID for filtering. - - session_factory: Optional. A SQLAlchemy sessionmaker instance. If not provided, - a new sessionmaker will be created using the global database engine. - - Returns: - A WorkflowNodeExecutionRepository instance - - Raises: - ValueError: If required parameters are missing - """ - # Extract required parameters - tenant_id = params.get("tenant_id") - if tenant_id is None: - raise ValueError("tenant_id is required for WorkflowNodeExecution repository with RDBMS storage") - - # Extract optional parameters - app_id = params.get("app_id") - - # Use the session_factory from params if provided, otherwise create one using the global db engine - session_factory = params.get("session_factory") - if session_factory is None: - # Create a sessionmaker using the same engine as the global db session - session_factory = sessionmaker(bind=db.engine) - - # Create and return the repository - return SQLAlchemyWorkflowNodeExecutionRepository( - session_factory=session_factory, tenant_id=tenant_id, app_id=app_id - ) diff --git a/api/repositories/workflow_node_execution/__init__.py b/api/repositories/workflow_node_execution/__init__.py deleted file mode 100644 index eed827bd05..0000000000 --- a/api/repositories/workflow_node_execution/__init__.py +++ /dev/null @@ -1,9 +0,0 @@ -""" -WorkflowNodeExecution repository implementations. -""" - -from repositories.workflow_node_execution.sqlalchemy_repository import SQLAlchemyWorkflowNodeExecutionRepository - -__all__ = [ - "SQLAlchemyWorkflowNodeExecutionRepository", -] diff --git a/api/repositories/workflow_node_execution/sqlalchemy_repository.py b/api/repositories/workflow_node_execution/sqlalchemy_repository.py deleted file mode 100644 index e0ad384be6..0000000000 --- a/api/repositories/workflow_node_execution/sqlalchemy_repository.py +++ /dev/null @@ -1,196 +0,0 @@ -""" -SQLAlchemy implementation of the WorkflowNodeExecutionRepository. -""" - -import logging -from collections.abc import Sequence -from typing import Optional - -from sqlalchemy import UnaryExpression, asc, delete, desc, select -from sqlalchemy.engine import Engine -from sqlalchemy.orm import sessionmaker - -from core.repository.workflow_node_execution_repository import OrderConfig -from models.workflow import WorkflowNodeExecution, WorkflowNodeExecutionStatus, WorkflowNodeExecutionTriggeredFrom - -logger = logging.getLogger(__name__) - - -class SQLAlchemyWorkflowNodeExecutionRepository: - """ - SQLAlchemy implementation of the WorkflowNodeExecutionRepository interface. - - This implementation supports multi-tenancy by filtering operations based on tenant_id. - Each method creates its own session, handles the transaction, and commits changes - to the database. This prevents long-running connections in the workflow core. - """ - - def __init__(self, session_factory: sessionmaker | Engine, tenant_id: str, app_id: Optional[str] = None): - """ - Initialize the repository with a SQLAlchemy sessionmaker or engine and tenant context. - - Args: - session_factory: SQLAlchemy sessionmaker or engine for creating sessions - tenant_id: Tenant ID for multi-tenancy - app_id: Optional app ID for filtering by application - """ - # If an engine is provided, create a sessionmaker from it - if isinstance(session_factory, Engine): - self._session_factory = sessionmaker(bind=session_factory, expire_on_commit=False) - elif isinstance(session_factory, sessionmaker): - self._session_factory = session_factory - else: - raise ValueError( - f"Invalid session_factory type {type(session_factory).__name__}; expected sessionmaker or Engine" - ) - - self._tenant_id = tenant_id - self._app_id = app_id - - def save(self, execution: WorkflowNodeExecution) -> None: - """ - Save a WorkflowNodeExecution instance and commit changes to the database. - - Args: - execution: The WorkflowNodeExecution instance to save - """ - with self._session_factory() as session: - # Ensure tenant_id is set - if not execution.tenant_id: - execution.tenant_id = self._tenant_id - - # Set app_id if provided and not already set - if self._app_id and not execution.app_id: - execution.app_id = self._app_id - - session.add(execution) - session.commit() - - def get_by_node_execution_id(self, node_execution_id: str) -> Optional[WorkflowNodeExecution]: - """ - Retrieve a WorkflowNodeExecution by its node_execution_id. - - Args: - node_execution_id: The node execution ID - - Returns: - The WorkflowNodeExecution instance if found, None otherwise - """ - with self._session_factory() as session: - stmt = select(WorkflowNodeExecution).where( - WorkflowNodeExecution.node_execution_id == node_execution_id, - WorkflowNodeExecution.tenant_id == self._tenant_id, - ) - - if self._app_id: - stmt = stmt.where(WorkflowNodeExecution.app_id == self._app_id) - - return session.scalar(stmt) - - def get_by_workflow_run( - self, - workflow_run_id: str, - order_config: Optional[OrderConfig] = None, - ) -> Sequence[WorkflowNodeExecution]: - """ - Retrieve all WorkflowNodeExecution instances for a specific workflow run. - - Args: - workflow_run_id: The workflow run ID - order_config: Optional configuration for ordering results - order_config.order_by: List of fields to order by (e.g., ["index", "created_at"]) - order_config.order_direction: Direction to order ("asc" or "desc") - - Returns: - A list of WorkflowNodeExecution instances - """ - with self._session_factory() as session: - stmt = select(WorkflowNodeExecution).where( - WorkflowNodeExecution.workflow_run_id == workflow_run_id, - WorkflowNodeExecution.tenant_id == self._tenant_id, - WorkflowNodeExecution.triggered_from == WorkflowNodeExecutionTriggeredFrom.WORKFLOW_RUN, - ) - - if self._app_id: - stmt = stmt.where(WorkflowNodeExecution.app_id == self._app_id) - - # Apply ordering if provided - if order_config and order_config.order_by: - order_columns: list[UnaryExpression] = [] - for field in order_config.order_by: - column = getattr(WorkflowNodeExecution, field, None) - if not column: - continue - if order_config.order_direction == "desc": - order_columns.append(desc(column)) - else: - order_columns.append(asc(column)) - - if order_columns: - stmt = stmt.order_by(*order_columns) - - return session.scalars(stmt).all() - - def get_running_executions(self, workflow_run_id: str) -> Sequence[WorkflowNodeExecution]: - """ - Retrieve all running WorkflowNodeExecution instances for a specific workflow run. - - Args: - workflow_run_id: The workflow run ID - - Returns: - A list of running WorkflowNodeExecution instances - """ - with self._session_factory() as session: - stmt = select(WorkflowNodeExecution).where( - WorkflowNodeExecution.workflow_run_id == workflow_run_id, - WorkflowNodeExecution.tenant_id == self._tenant_id, - WorkflowNodeExecution.status == WorkflowNodeExecutionStatus.RUNNING, - WorkflowNodeExecution.triggered_from == WorkflowNodeExecutionTriggeredFrom.WORKFLOW_RUN, - ) - - if self._app_id: - stmt = stmt.where(WorkflowNodeExecution.app_id == self._app_id) - - return session.scalars(stmt).all() - - def update(self, execution: WorkflowNodeExecution) -> None: - """ - Update an existing WorkflowNodeExecution instance and commit changes to the database. - - Args: - execution: The WorkflowNodeExecution instance to update - """ - with self._session_factory() as session: - # Ensure tenant_id is set - if not execution.tenant_id: - execution.tenant_id = self._tenant_id - - # Set app_id if provided and not already set - if self._app_id and not execution.app_id: - execution.app_id = self._app_id - - session.merge(execution) - session.commit() - - def clear(self) -> None: - """ - Clear all WorkflowNodeExecution records for the current tenant_id and app_id. - - This method deletes all WorkflowNodeExecution records that match the tenant_id - and app_id (if provided) associated with this repository instance. - """ - with self._session_factory() as session: - stmt = delete(WorkflowNodeExecution).where(WorkflowNodeExecution.tenant_id == self._tenant_id) - - if self._app_id: - stmt = stmt.where(WorkflowNodeExecution.app_id == self._app_id) - - result = session.execute(stmt) - session.commit() - - deleted_count = result.rowcount - logger.info( - f"Cleared {deleted_count} workflow node execution records for tenant {self._tenant_id}" - + (f" and app {self._app_id}" if self._app_id else "") - ) diff --git a/api/schedule/clean_messages.py b/api/schedule/clean_messages.py index 5e4d3ec323..d02bc81f33 100644 --- a/api/schedule/clean_messages.py +++ b/api/schedule/clean_messages.py @@ -1,4 +1,5 @@ import datetime +import logging import time import click @@ -20,6 +21,8 @@ from models.model import ( from models.web import SavedMessage from services.feature_service import FeatureService +_logger = logging.getLogger(__name__) + @app.celery.task(queue="dataset") def clean_messages(): @@ -31,9 +34,8 @@ def clean_messages(): while True: try: # Main query with join and filter - # FIXME:for mypy no paginate method error messages = ( - db.session.query(Message) # type: ignore + db.session.query(Message) .filter(Message.created_at < plan_sandbox_clean_message_day) .order_by(Message.created_at.desc()) .limit(100) @@ -46,7 +48,14 @@ def clean_messages(): break for message in messages: plan_sandbox_clean_message_day = message.created_at - app = App.query.filter_by(id=message.app_id).first() + app = db.session.query(App).filter_by(id=message.app_id).first() + if not app: + _logger.warning( + "Expected App record to exist, but none was found, app_id=%s, message_id=%s", + message.app_id, + message.id, + ) + continue features_cache_key = f"features:{app.tenant_id}" plan_cache = redis_client.get(features_cache_key) if plan_cache is None: diff --git a/api/schedule/clean_unused_datasets_task.py b/api/schedule/clean_unused_datasets_task.py index 4e7e443c2c..c0cd42a226 100644 --- a/api/schedule/clean_unused_datasets_task.py +++ b/api/schedule/clean_unused_datasets_task.py @@ -2,7 +2,7 @@ import datetime import time import click -from sqlalchemy import func +from sqlalchemy import func, select from werkzeug.exceptions import NotFound import app @@ -51,8 +51,9 @@ def clean_unused_datasets_task(): ) # Main query with join and filter - datasets = ( - Dataset.query.outerjoin(document_subquery_new, Dataset.id == document_subquery_new.c.dataset_id) + stmt = ( + select(Dataset) + .outerjoin(document_subquery_new, Dataset.id == document_subquery_new.c.dataset_id) .outerjoin(document_subquery_old, Dataset.id == document_subquery_old.c.dataset_id) .filter( Dataset.created_at < plan_sandbox_clean_day, @@ -60,9 +61,10 @@ def clean_unused_datasets_task(): func.coalesce(document_subquery_old.c.document_count, 0) > 0, ) .order_by(Dataset.created_at.desc()) - .paginate(page=1, per_page=50) ) + datasets = db.paginate(stmt, page=1, per_page=50) + except NotFound: break if datasets.items is None or len(datasets.items) == 0: @@ -99,7 +101,7 @@ def clean_unused_datasets_task(): # update document update_params = {Document.enabled: False} - Document.query.filter_by(dataset_id=dataset.id).update(update_params) + db.session.query(Document).filter_by(dataset_id=dataset.id).update(update_params) db.session.commit() click.echo(click.style("Cleaned unused dataset {} from db success!".format(dataset.id), fg="green")) except Exception as e: @@ -135,8 +137,9 @@ def clean_unused_datasets_task(): ) # Main query with join and filter - datasets = ( - Dataset.query.outerjoin(document_subquery_new, Dataset.id == document_subquery_new.c.dataset_id) + stmt = ( + select(Dataset) + .outerjoin(document_subquery_new, Dataset.id == document_subquery_new.c.dataset_id) .outerjoin(document_subquery_old, Dataset.id == document_subquery_old.c.dataset_id) .filter( Dataset.created_at < plan_pro_clean_day, @@ -144,8 +147,8 @@ def clean_unused_datasets_task(): func.coalesce(document_subquery_old.c.document_count, 0) > 0, ) .order_by(Dataset.created_at.desc()) - .paginate(page=1, per_page=50) ) + datasets = db.paginate(stmt, page=1, per_page=50) except NotFound: break @@ -175,7 +178,7 @@ def clean_unused_datasets_task(): # update document update_params = {Document.enabled: False} - Document.query.filter_by(dataset_id=dataset.id).update(update_params) + db.session.query(Document).filter_by(dataset_id=dataset.id).update(update_params) db.session.commit() click.echo( click.style("Cleaned unused dataset {} from db success!".format(dataset.id), fg="green") diff --git a/api/schedule/create_tidb_serverless_task.py b/api/schedule/create_tidb_serverless_task.py index 1c985461c6..8a02278de8 100644 --- a/api/schedule/create_tidb_serverless_task.py +++ b/api/schedule/create_tidb_serverless_task.py @@ -19,7 +19,9 @@ def create_tidb_serverless_task(): while True: try: # check the number of idle tidb serverless - idle_tidb_serverless_number = TidbAuthBinding.query.filter(TidbAuthBinding.active == False).count() + idle_tidb_serverless_number = ( + db.session.query(TidbAuthBinding).filter(TidbAuthBinding.active == False).count() + ) if idle_tidb_serverless_number >= tidb_serverless_number: break # create tidb serverless diff --git a/api/schedule/mail_clean_document_notify_task.py b/api/schedule/mail_clean_document_notify_task.py index b3d0e09784..5ee813e1de 100644 --- a/api/schedule/mail_clean_document_notify_task.py +++ b/api/schedule/mail_clean_document_notify_task.py @@ -29,7 +29,9 @@ def mail_clean_document_notify_task(): # send document clean notify mail try: - dataset_auto_disable_logs = DatasetAutoDisableLog.query.filter(DatasetAutoDisableLog.notified == False).all() + dataset_auto_disable_logs = ( + db.session.query(DatasetAutoDisableLog).filter(DatasetAutoDisableLog.notified == False).all() + ) # group by tenant_id dataset_auto_disable_logs_map: dict[str, list[DatasetAutoDisableLog]] = defaultdict(list) for dataset_auto_disable_log in dataset_auto_disable_logs: @@ -43,14 +45,16 @@ def mail_clean_document_notify_task(): if plan != "sandbox": knowledge_details = [] # check tenant - tenant = Tenant.query.filter(Tenant.id == tenant_id).first() + tenant = db.session.query(Tenant).filter(Tenant.id == tenant_id).first() if not tenant: continue # check current owner - current_owner_join = TenantAccountJoin.query.filter_by(tenant_id=tenant.id, role="owner").first() + current_owner_join = ( + db.session.query(TenantAccountJoin).filter_by(tenant_id=tenant.id, role="owner").first() + ) if not current_owner_join: continue - account = Account.query.filter(Account.id == current_owner_join.account_id).first() + account = db.session.query(Account).filter(Account.id == current_owner_join.account_id).first() if not account: continue @@ -63,7 +67,7 @@ def mail_clean_document_notify_task(): ) for dataset_id, document_ids in dataset_auto_dataset_map.items(): - dataset = Dataset.query.filter(Dataset.id == dataset_id).first() + dataset = db.session.query(Dataset).filter(Dataset.id == dataset_id).first() if dataset: document_count = len(document_ids) knowledge_details.append(rf"Knowledge base {dataset.name}: {document_count} documents") diff --git a/api/schedule/queue_monitor_task.py b/api/schedule/queue_monitor_task.py new file mode 100644 index 0000000000..e3a7021b9d --- /dev/null +++ b/api/schedule/queue_monitor_task.py @@ -0,0 +1,62 @@ +import logging +from datetime import datetime +from urllib.parse import urlparse + +import click +from flask import render_template +from redis import Redis + +import app +from configs import dify_config +from extensions.ext_database import db +from extensions.ext_mail import mail + +# Create a dedicated Redis connection (using the same configuration as Celery) +celery_broker_url = dify_config.CELERY_BROKER_URL + +parsed = urlparse(celery_broker_url) +host = parsed.hostname or "localhost" +port = parsed.port or 6379 +password = parsed.password or None +redis_db = parsed.path.strip("/") or "1" # type: ignore + +celery_redis = Redis(host=host, port=port, password=password, db=redis_db) + + +@app.celery.task(queue="monitor") +def queue_monitor_task(): + queue_name = "dataset" + threshold = dify_config.QUEUE_MONITOR_THRESHOLD + + try: + queue_length = celery_redis.llen(f"{queue_name}") + logging.info(click.style(f"Start monitor {queue_name}", fg="green")) + logging.info(click.style(f"Queue length: {queue_length}", fg="green")) + + if queue_length >= threshold: + warning_msg = f"Queue {queue_name} task count exceeded the limit.: {queue_length}/{threshold}" + logging.warning(click.style(warning_msg, fg="red")) + alter_emails = dify_config.QUEUE_MONITOR_ALERT_EMAILS + if alter_emails: + to_list = alter_emails.split(",") + for to in to_list: + try: + current_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S") + html_content = render_template( + "queue_monitor_alert_email_template_en-US.html", + queue_name=queue_name, + queue_length=queue_length, + threshold=threshold, + alert_time=current_time, + ) + mail.send( + to=to, subject="Alert: Dataset Queue pending tasks exceeded the limit", html=html_content + ) + except Exception as e: + logging.exception(click.style("Exception occurred during sending email", fg="red")) + + except Exception as e: + logging.exception(click.style("Exception occurred during queue monitoring", fg="red")) + finally: + if db.session.is_active: + db.session.close() diff --git a/api/schedule/update_tidb_serverless_status_task.py b/api/schedule/update_tidb_serverless_status_task.py index 11a39e60ee..ce4ecb6e7c 100644 --- a/api/schedule/update_tidb_serverless_status_task.py +++ b/api/schedule/update_tidb_serverless_status_task.py @@ -5,6 +5,7 @@ import click import app from configs import dify_config from core.rag.datasource.vdb.tidb_on_qdrant.tidb_service import TidbService +from extensions.ext_database import db from models.dataset import TidbAuthBinding @@ -14,9 +15,11 @@ def update_tidb_serverless_status_task(): start_at = time.perf_counter() try: # check the number of idle tidb serverless - tidb_serverless_list = TidbAuthBinding.query.filter( - TidbAuthBinding.active == False, TidbAuthBinding.status == "CREATING" - ).all() + tidb_serverless_list = ( + db.session.query(TidbAuthBinding) + .filter(TidbAuthBinding.active == False, TidbAuthBinding.status == "CREATING") + .all() + ) if len(tidb_serverless_list) == 0: return # update tidb serverless status diff --git a/api/services/account_service.py b/api/services/account_service.py index f930ef910b..14d238467d 100644 --- a/api/services/account_service.py +++ b/api/services/account_service.py @@ -1,7 +1,6 @@ import base64 import json import logging -import random import secrets import uuid from datetime import UTC, datetime, timedelta @@ -49,7 +48,7 @@ from services.errors.account import ( RoleAlreadyAssignedError, TenantNotFoundError, ) -from services.errors.workspace import WorkSpaceNotAllowedCreateError +from services.errors.workspace import WorkSpaceNotAllowedCreateError, WorkspacesLimitExceededError from services.feature_service import FeatureService from tasks.delete_account_task import delete_account_task from tasks.mail_account_deletion_task import send_account_deletion_verification_code @@ -108,17 +107,20 @@ class AccountService: if account.status == AccountStatus.BANNED.value: raise Unauthorized("Account is banned.") - current_tenant = TenantAccountJoin.query.filter_by(account_id=account.id, current=True).first() + current_tenant = db.session.query(TenantAccountJoin).filter_by(account_id=account.id, current=True).first() if current_tenant: - account.current_tenant_id = current_tenant.tenant_id + account.set_tenant_id(current_tenant.tenant_id) else: available_ta = ( - TenantAccountJoin.query.filter_by(account_id=account.id).order_by(TenantAccountJoin.id.asc()).first() + db.session.query(TenantAccountJoin) + .filter_by(account_id=account.id) + .order_by(TenantAccountJoin.id.asc()) + .first() ) if not available_ta: return None - account.current_tenant_id = available_ta.tenant_id + account.set_tenant_id(available_ta.tenant_id) available_ta.current = True db.session.commit() @@ -258,7 +260,7 @@ class AccountService: @staticmethod def generate_account_deletion_verification_code(account: Account) -> tuple[str, str]: - code = "".join([str(random.randint(0, 9)) for _ in range(6)]) + code = "".join([str(secrets.randbelow(exclusive_upper_bound=10)) for _ in range(6)]) token = TokenManager.generate_token( account=account, token_type="account_deletion", additional_data={"code": code} ) @@ -297,9 +299,9 @@ class AccountService: """Link account integrate""" try: # Query whether there is an existing binding record for the same provider - account_integrate: Optional[AccountIntegrate] = AccountIntegrate.query.filter_by( - account_id=account.id, provider=provider - ).first() + account_integrate: Optional[AccountIntegrate] = ( + db.session.query(AccountIntegrate).filter_by(account_id=account.id, provider=provider).first() + ) if account_integrate: # If it exists, update the record @@ -426,7 +428,7 @@ class AccountService: additional_data: dict[str, Any] = {}, ): if not code: - code = "".join([str(random.randint(0, 9)) for _ in range(6)]) + code = "".join([str(secrets.randbelow(exclusive_upper_bound=10)) for _ in range(6)]) additional_data["code"] = code token = TokenManager.generate_token( account=account, email=email, token_type="reset_password", additional_data=additional_data @@ -453,7 +455,7 @@ class AccountService: raise EmailCodeLoginRateLimitExceededError() - code = "".join([str(random.randint(0, 9)) for _ in range(6)]) + code = "".join([str(secrets.randbelow(exclusive_upper_bound=10)) for _ in range(6)]) token = TokenManager.generate_token( account=account, email=email, token_type="email_code_login", additional_data={"code": code} ) @@ -612,7 +614,10 @@ class TenantService: ): """Check if user have a workspace or not""" available_ta = ( - TenantAccountJoin.query.filter_by(account_id=account.id).order_by(TenantAccountJoin.id.asc()).first() + db.session.query(TenantAccountJoin) + .filter_by(account_id=account.id) + .order_by(TenantAccountJoin.id.asc()) + .first() ) if available_ta: @@ -622,6 +627,10 @@ class TenantService: if not FeatureService.get_system_features().is_allow_create_workspace and not is_setup: raise WorkSpaceNotAllowedCreateError() + workspaces = FeatureService.get_system_features().license.workspaces + if not workspaces.is_available(): + raise WorkspacesLimitExceededError() + if name: tenant = TenantService.create_tenant(name=name, is_setup=is_setup) else: @@ -666,7 +675,7 @@ class TenantService: if not tenant: raise TenantNotFoundError("Tenant not found.") - ta = TenantAccountJoin.query.filter_by(tenant_id=tenant.id, account_id=account.id).first() + ta = db.session.query(TenantAccountJoin).filter_by(tenant_id=tenant.id, account_id=account.id).first() if ta: tenant.role = ta.role else: @@ -695,12 +704,12 @@ class TenantService: if not tenant_account_join: raise AccountNotLinkTenantError("Tenant not found or account is not a member of the tenant.") else: - TenantAccountJoin.query.filter( + db.session.query(TenantAccountJoin).filter( TenantAccountJoin.account_id == account.id, TenantAccountJoin.tenant_id != tenant_id ).update({"current": False}) tenant_account_join.current = True # Set the current tenant for the account - account.current_tenant_id = tenant_account_join.tenant_id + account.set_tenant_id(tenant_account_join.tenant_id) db.session.commit() @staticmethod @@ -787,7 +796,7 @@ class TenantService: if operator.id == member.id: raise CannotOperateSelfError("Cannot operate self.") - ta_operator = TenantAccountJoin.query.filter_by(tenant_id=tenant.id, account_id=operator.id).first() + ta_operator = db.session.query(TenantAccountJoin).filter_by(tenant_id=tenant.id, account_id=operator.id).first() if not ta_operator or ta_operator.role not in perms[action]: raise NoPermissionError(f"No permission to {action} member.") @@ -800,7 +809,7 @@ class TenantService: TenantService.check_member_permission(tenant, operator, account, "remove") - ta = TenantAccountJoin.query.filter_by(tenant_id=tenant.id, account_id=account.id).first() + ta = db.session.query(TenantAccountJoin).filter_by(tenant_id=tenant.id, account_id=account.id).first() if not ta: raise MemberNotInTenantError("Member not in tenant.") @@ -812,15 +821,23 @@ class TenantService: """Update member role""" TenantService.check_member_permission(tenant, operator, member, "update") - target_member_join = TenantAccountJoin.query.filter_by(tenant_id=tenant.id, account_id=member.id).first() + target_member_join = ( + db.session.query(TenantAccountJoin).filter_by(tenant_id=tenant.id, account_id=member.id).first() + ) + + if not target_member_join: + raise MemberNotInTenantError("Member not in tenant.") if target_member_join.role == new_role: raise RoleAlreadyAssignedError("The provided role is already assigned to the member.") if new_role == "owner": # Find the current owner and change their role to 'admin' - current_owner_join = TenantAccountJoin.query.filter_by(tenant_id=tenant.id, role="owner").first() - current_owner_join.role = "admin" + current_owner_join = ( + db.session.query(TenantAccountJoin).filter_by(tenant_id=tenant.id, role="owner").first() + ) + if current_owner_join: + current_owner_join.role = "admin" # Update the role of the target member target_member_join.role = new_role @@ -837,7 +854,7 @@ class TenantService: @staticmethod def get_custom_config(tenant_id: str) -> dict: - tenant = Tenant.query.filter(Tenant.id == tenant_id).one_or_404() + tenant = db.get_or_404(Tenant, tenant_id) return cast(dict, tenant.custom_config_dict) @@ -914,7 +931,11 @@ class RegisterService: if open_id is not None and provider is not None: AccountService.link_account_integrate(provider, open_id, account) - if FeatureService.get_system_features().is_allow_create_workspace and create_workspace_required: + if ( + FeatureService.get_system_features().is_allow_create_workspace + and create_workspace_required + and FeatureService.get_system_features().license.workspaces.is_available() + ): tenant = TenantService.create_tenant(f"{account.name}'s Workspace") TenantService.create_tenant_member(tenant, account, role="owner") account.current_tenant = tenant @@ -959,7 +980,7 @@ class RegisterService: TenantService.switch_tenant(account, tenant.id) else: TenantService.check_member_permission(tenant, inviter, account, "add") - ta = TenantAccountJoin.query.filter_by(tenant_id=tenant.id, account_id=account.id).first() + ta = db.session.query(TenantAccountJoin).filter_by(tenant_id=tenant.id, account_id=account.id).first() if not ta: TenantService.create_tenant_member(tenant, account, role) diff --git a/api/services/agent_service.py b/api/services/agent_service.py index 0ff144052f..503b31ede2 100644 --- a/api/services/agent_service.py +++ b/api/services/agent_service.py @@ -2,12 +2,12 @@ import threading from typing import Optional import pytz -from flask_login import current_user # type: ignore +from flask_login import current_user import contexts from core.app.app_config.easy_ui_based_app.agent.manager import AgentConfigManager -from core.plugin.manager.agent import PluginAgentManager -from core.plugin.manager.exc import PluginDaemonClientSideError +from core.plugin.impl.agent import PluginAgentClient +from core.plugin.impl.exc import PluginDaemonClientSideError from core.tools.tool_manager import ToolManager from extensions.ext_database import db from models.account import Account @@ -161,7 +161,7 @@ class AgentService: """ List agent providers """ - manager = PluginAgentManager() + manager = PluginAgentClient() return manager.fetch_agent_strategy_providers(tenant_id) @classmethod @@ -169,7 +169,7 @@ class AgentService: """ Get agent provider """ - manager = PluginAgentManager() + manager = PluginAgentClient() try: return manager.fetch_agent_strategy_provider(tenant_id, provider_name) except PluginDaemonClientSideError as e: diff --git a/api/services/annotation_service.py b/api/services/annotation_service.py index 45ec1e9b5a..8c950abc24 100644 --- a/api/services/annotation_service.py +++ b/api/services/annotation_service.py @@ -3,8 +3,8 @@ import uuid from typing import cast import pandas as pd -from flask_login import current_user # type: ignore -from sqlalchemy import or_ +from flask_login import current_user +from sqlalchemy import or_, select from werkzeug.datastructures import FileStorage from werkzeug.exceptions import NotFound @@ -124,8 +124,9 @@ class AppAnnotationService: if not app: raise NotFound("App not found") if keyword: - annotations = ( - MessageAnnotation.query.filter(MessageAnnotation.app_id == app_id) + stmt = ( + select(MessageAnnotation) + .filter(MessageAnnotation.app_id == app_id) .filter( or_( MessageAnnotation.question.ilike("%{}%".format(keyword)), @@ -133,14 +134,14 @@ class AppAnnotationService: ) ) .order_by(MessageAnnotation.created_at.desc(), MessageAnnotation.id.desc()) - .paginate(page=page, per_page=limit, max_per_page=100, error_out=False) ) else: - annotations = ( - MessageAnnotation.query.filter(MessageAnnotation.app_id == app_id) + stmt = ( + select(MessageAnnotation) + .filter(MessageAnnotation.app_id == app_id) .order_by(MessageAnnotation.created_at.desc(), MessageAnnotation.id.desc()) - .paginate(page=page, per_page=limit, max_per_page=100, error_out=False) ) + annotations = db.paginate(select=stmt, page=page, per_page=limit, max_per_page=100, error_out=False) return annotations.items, annotations.total @classmethod @@ -325,13 +326,16 @@ class AppAnnotationService: if not annotation: raise NotFound("Annotation not found") - annotation_hit_histories = ( - AppAnnotationHitHistory.query.filter( + stmt = ( + select(AppAnnotationHitHistory) + .filter( AppAnnotationHitHistory.app_id == app_id, AppAnnotationHitHistory.annotation_id == annotation_id, ) .order_by(AppAnnotationHitHistory.created_at.desc()) - .paginate(page=page, per_page=limit, max_per_page=100, error_out=False) + ) + annotation_hit_histories = db.paginate( + select=stmt, page=page, per_page=limit, max_per_page=100, error_out=False ) return annotation_hit_histories.items, annotation_hit_histories.total diff --git a/api/services/app_dsl_service.py b/api/services/app_dsl_service.py index 936101c78c..d2875180d8 100644 --- a/api/services/app_dsl_service.py +++ b/api/services/app_dsl_service.py @@ -40,7 +40,7 @@ IMPORT_INFO_REDIS_KEY_PREFIX = "app_import_info:" CHECK_DEPENDENCIES_REDIS_KEY_PREFIX = "app_check_dependencies:" IMPORT_INFO_REDIS_EXPIRY = 10 * 60 # 10 minutes DSL_MAX_SIZE = 10 * 1024 * 1024 # 10MB -CURRENT_DSL_VERSION = "0.2.0" +CURRENT_DSL_VERSION = "0.3.0" class ImportMode(StrEnum): @@ -77,13 +77,19 @@ def _check_version_compatibility(imported_version: str) -> ImportStatus: except version.InvalidVersion: return ImportStatus.FAILED - # Compare major version and minor version - if current_ver.major != imported_ver.major or current_ver.minor != imported_ver.minor: + # If imported version is newer than current, always return PENDING + if imported_ver > current_ver: return ImportStatus.PENDING - if current_ver.micro != imported_ver.micro: + # If imported version is older than current's major, return PENDING + if imported_ver.major < current_ver.major: + return ImportStatus.PENDING + + # If imported version is older than current's minor, return COMPLETED_WITH_WARNINGS + if imported_ver.minor < current_ver.minor: return ImportStatus.COMPLETED_WITH_WARNINGS + # If imported version equals or is older than current's micro, return COMPLETED return ImportStatus.COMPLETED diff --git a/api/services/app_service.py b/api/services/app_service.py index e87a1c7931..d08462d001 100644 --- a/api/services/app_service.py +++ b/api/services/app_service.py @@ -3,7 +3,7 @@ import logging from datetime import UTC, datetime from typing import Optional, cast -from flask_login import current_user # type: ignore +from flask_login import current_user from flask_sqlalchemy.pagination import Pagination from configs import dify_config @@ -18,8 +18,10 @@ from core.tools.utils.configuration import ToolParameterConfigurationManager from events.app_event import app_was_created from extensions.ext_database import db from models.account import Account -from models.model import App, AppMode, AppModelConfig +from models.model import App, AppMode, AppModelConfig, Site from models.tools import ApiToolProvider +from services.enterprise.enterprise_service import EnterpriseService +from services.feature_service import FeatureService from services.tag_service import TagService from tasks.remove_app_and_related_data_task import remove_app_and_related_data_task @@ -155,6 +157,10 @@ class AppService: app_was_created.send(app, account=account) + if FeatureService.get_system_features().webapp_auth.enabled: + # update web app setting as private + EnterpriseService.WebAppAuth.update_app_access_mode(app.id, "private") + return app def get_app(self, app: App) -> App: @@ -307,6 +313,10 @@ class AppService: db.session.delete(app) db.session.commit() + # clean up web app settings + if FeatureService.get_system_features().webapp_auth.enabled: + EnterpriseService.WebAppAuth.cleanup_webapp(app.id) + # Trigger asynchronous deletion of app and related data remove_app_and_related_data_task.delay(tenant_id=app.tenant_id, app_id=app.id) @@ -373,3 +383,27 @@ class AppService: meta["tool_icons"][tool_name] = {"background": "#252525", "content": "\ud83d\ude01"} return meta + + @staticmethod + def get_app_code_by_id(app_id: str) -> str: + """ + Get app code by app id + :param app_id: app id + :return: app code + """ + site = db.session.query(Site).filter(Site.app_id == app_id).first() + if not site: + raise ValueError(f"App with id {app_id} not found") + return str(site.code) + + @staticmethod + def get_app_id_by_code(app_code: str) -> str: + """ + Get app id by app code + :param app_code: app code + :return: app id + """ + site = db.session.query(Site).filter(Site.code == app_code).first() + if not site: + raise ValueError(f"App with code {app_code} not found") + return str(site.app_id) diff --git a/api/services/clear_free_plan_tenant_expired_logs.py b/api/services/clear_free_plan_tenant_expired_logs.py index 5762bf9600..1fd560d581 100644 --- a/api/services/clear_free_plan_tenant_expired_logs.py +++ b/api/services/clear_free_plan_tenant_expired_logs.py @@ -14,7 +14,7 @@ from extensions.ext_database import db from extensions.ext_storage import storage from models.account import Tenant from models.model import App, Conversation, Message -from models.workflow import WorkflowNodeExecution, WorkflowRun +from models.workflow import WorkflowNodeExecutionModel, WorkflowRun from services.billing_service import BillingService logger = logging.getLogger(__name__) @@ -108,10 +108,11 @@ class ClearFreePlanTenantExpiredLogs: while True: with Session(db.engine).no_autoflush as session: workflow_node_executions = ( - session.query(WorkflowNodeExecution) + session.query(WorkflowNodeExecutionModel) .filter( - WorkflowNodeExecution.tenant_id == tenant_id, - WorkflowNodeExecution.created_at < datetime.datetime.now() - datetime.timedelta(days=days), + WorkflowNodeExecutionModel.tenant_id == tenant_id, + WorkflowNodeExecutionModel.created_at + < datetime.datetime.now() - datetime.timedelta(days=days), ) .limit(batch) .all() @@ -135,8 +136,8 @@ class ClearFreePlanTenantExpiredLogs: ] # delete workflow node executions - session.query(WorkflowNodeExecution).filter( - WorkflowNodeExecution.id.in_(workflow_node_execution_ids), + session.query(WorkflowNodeExecutionModel).filter( + WorkflowNodeExecutionModel.id.in_(workflow_node_execution_ids), ).delete(synchronize_session=False) session.commit() diff --git a/api/services/conversation_service.py b/api/services/conversation_service.py index 6485cbf37d..afdaa49465 100644 --- a/api/services/conversation_service.py +++ b/api/services/conversation_service.py @@ -9,9 +9,14 @@ from core.app.entities.app_invoke_entities import InvokeFrom from core.llm_generator.llm_generator import LLMGenerator from extensions.ext_database import db from libs.infinite_scroll_pagination import InfiniteScrollPagination +from models import ConversationVariable from models.account import Account from models.model import App, Conversation, EndUser, Message -from services.errors.conversation import ConversationNotExistsError, LastConversationNotExistsError +from services.errors.conversation import ( + ConversationNotExistsError, + ConversationVariableNotExistsError, + LastConversationNotExistsError, +) from services.errors.message import MessageNotExistsError @@ -166,3 +171,50 @@ class ConversationService: conversation.is_deleted = True conversation.updated_at = datetime.now(UTC).replace(tzinfo=None) db.session.commit() + + @classmethod + def get_conversational_variable( + cls, + app_model: App, + conversation_id: str, + user: Optional[Union[Account, EndUser]], + limit: int, + last_id: Optional[str], + ) -> InfiniteScrollPagination: + conversation = cls.get_conversation(app_model, conversation_id, user) + + stmt = ( + select(ConversationVariable) + .where(ConversationVariable.app_id == app_model.id) + .where(ConversationVariable.conversation_id == conversation.id) + .order_by(ConversationVariable.created_at) + ) + + with Session(db.engine) as session: + if last_id: + last_variable = session.scalar(stmt.where(ConversationVariable.id == last_id)) + if not last_variable: + raise ConversationVariableNotExistsError() + + # Filter for variables created after the last_id + stmt = stmt.where(ConversationVariable.created_at > last_variable.created_at) + + # Apply limit to query + query_stmt = stmt.limit(limit) # Get one extra to check if there are more + rows = session.scalars(query_stmt).all() + + has_more = False + if len(rows) > limit: + has_more = True + rows = rows[:limit] # Remove the extra item + + variables = [ + { + "created_at": row.created_at, + "updated_at": row.updated_at, + **row.to_variable().model_dump(), + } + for row in rows + ] + + return InfiniteScrollPagination(variables, limit, has_more) diff --git a/api/services/dataset_service.py b/api/services/dataset_service.py index 44d2594ee8..e98b47921f 100644 --- a/api/services/dataset_service.py +++ b/api/services/dataset_service.py @@ -2,14 +2,14 @@ import copy import datetime import json import logging -import random +import secrets import time import uuid from collections import Counter from typing import Any, Optional -from flask_login import current_user # type: ignore -from sqlalchemy import func +from flask_login import current_user +from sqlalchemy import func, select from sqlalchemy.orm import Session from werkzeug.exceptions import NotFound @@ -77,11 +77,13 @@ from tasks.sync_website_document_indexing_task import sync_website_document_inde class DatasetService: @staticmethod def get_datasets(page, per_page, tenant_id=None, user=None, search=None, tag_ids=None, include_all=False): - query = Dataset.query.filter(Dataset.tenant_id == tenant_id).order_by(Dataset.created_at.desc()) + query = select(Dataset).filter(Dataset.tenant_id == tenant_id).order_by(Dataset.created_at.desc()) if user: # get permitted dataset ids - dataset_permission = DatasetPermission.query.filter_by(account_id=user.id, tenant_id=tenant_id).all() + dataset_permission = ( + db.session.query(DatasetPermission).filter_by(account_id=user.id, tenant_id=tenant_id).all() + ) permitted_dataset_ids = {dp.dataset_id for dp in dataset_permission} if dataset_permission else None if user.current_role == TenantAccountRole.DATASET_OPERATOR: @@ -129,7 +131,7 @@ class DatasetService: else: return [], 0 - datasets = query.paginate(page=page, per_page=per_page, max_per_page=100, error_out=False) + datasets = db.paginate(select=query, page=page, per_page=per_page, max_per_page=100, error_out=False) return datasets.items, datasets.total @@ -153,9 +155,10 @@ class DatasetService: @staticmethod def get_datasets_by_ids(ids, tenant_id): - datasets = Dataset.query.filter(Dataset.id.in_(ids), Dataset.tenant_id == tenant_id).paginate( - page=1, per_page=len(ids), max_per_page=len(ids), error_out=False - ) + stmt = select(Dataset).filter(Dataset.id.in_(ids), Dataset.tenant_id == tenant_id) + + datasets = db.paginate(select=stmt, page=1, per_page=len(ids), max_per_page=len(ids), error_out=False) + return datasets.items, datasets.total @staticmethod @@ -174,7 +177,7 @@ class DatasetService: retrieval_model: Optional[RetrievalModel] = None, ): # check if dataset name already exists - if Dataset.query.filter_by(name=name, tenant_id=tenant_id).first(): + if db.session.query(Dataset).filter_by(name=name, tenant_id=tenant_id).first(): raise DatasetNameDuplicateError(f"Dataset with name {name} already exists.") embedding_model = None if indexing_technique == "high_quality": @@ -235,7 +238,7 @@ class DatasetService: @staticmethod def get_dataset(dataset_id) -> Optional[Dataset]: - dataset: Optional[Dataset] = Dataset.query.filter_by(id=dataset_id).first() + dataset: Optional[Dataset] = db.session.query(Dataset).filter_by(id=dataset_id).first() return dataset @staticmethod @@ -436,7 +439,7 @@ class DatasetService: # update Retrieval model filtered_data["retrieval_model"] = data["retrieval_model"] - dataset.query.filter_by(id=dataset_id).update(filtered_data) + db.session.query(Dataset).filter_by(id=dataset_id).update(filtered_data) db.session.commit() if action: @@ -460,7 +463,7 @@ class DatasetService: @staticmethod def dataset_use_check(dataset_id) -> bool: - count = AppDatasetJoin.query.filter_by(dataset_id=dataset_id).count() + count = db.session.query(AppDatasetJoin).filter_by(dataset_id=dataset_id).count() if count > 0: return True return False @@ -474,15 +477,15 @@ class DatasetService: if dataset.permission == DatasetPermissionEnum.ONLY_ME and dataset.created_by != user.id: logging.debug(f"User {user.id} does not have permission to access dataset {dataset.id}") raise NoPermissionError("You do not have permission to access this dataset.") - if dataset.permission == "partial_members": - user_permission = DatasetPermission.query.filter_by(dataset_id=dataset.id, account_id=user.id).first() - if ( - not user_permission - and dataset.tenant_id != user.current_tenant_id - and dataset.created_by != user.id - ): - logging.debug(f"User {user.id} does not have permission to access dataset {dataset.id}") - raise NoPermissionError("You do not have permission to access this dataset.") + if dataset.permission == DatasetPermissionEnum.PARTIAL_TEAM: + # For partial team permission, user needs explicit permission or be the creator + if dataset.created_by != user.id: + user_permission = ( + db.session.query(DatasetPermission).filter_by(dataset_id=dataset.id, account_id=user.id).first() + ) + if not user_permission: + logging.debug(f"User {user.id} does not have permission to access dataset {dataset.id}") + raise NoPermissionError("You do not have permission to access this dataset.") @staticmethod def check_dataset_operator_permission(user: Optional[Account] = None, dataset: Optional[Dataset] = None): @@ -499,23 +502,24 @@ class DatasetService: elif dataset.permission == DatasetPermissionEnum.PARTIAL_TEAM: if not any( - dp.dataset_id == dataset.id for dp in DatasetPermission.query.filter_by(account_id=user.id).all() + dp.dataset_id == dataset.id + for dp in db.session.query(DatasetPermission).filter_by(account_id=user.id).all() ): raise NoPermissionError("You do not have permission to access this dataset.") @staticmethod def get_dataset_queries(dataset_id: str, page: int, per_page: int): - dataset_queries = ( - DatasetQuery.query.filter_by(dataset_id=dataset_id) - .order_by(db.desc(DatasetQuery.created_at)) - .paginate(page=page, per_page=per_page, max_per_page=100, error_out=False) - ) + stmt = select(DatasetQuery).filter_by(dataset_id=dataset_id).order_by(db.desc(DatasetQuery.created_at)) + + dataset_queries = db.paginate(select=stmt, page=page, per_page=per_page, max_per_page=100, error_out=False) + return dataset_queries.items, dataset_queries.total @staticmethod def get_related_apps(dataset_id: str): return ( - AppDatasetJoin.query.filter(AppDatasetJoin.dataset_id == dataset_id) + db.session.query(AppDatasetJoin) + .filter(AppDatasetJoin.dataset_id == dataset_id) .order_by(db.desc(AppDatasetJoin.created_at)) .all() ) @@ -530,10 +534,14 @@ class DatasetService: } # get recent 30 days auto disable logs start_date = datetime.datetime.now() - datetime.timedelta(days=30) - dataset_auto_disable_logs = DatasetAutoDisableLog.query.filter( - DatasetAutoDisableLog.dataset_id == dataset_id, - DatasetAutoDisableLog.created_at >= start_date, - ).all() + dataset_auto_disable_logs = ( + db.session.query(DatasetAutoDisableLog) + .filter( + DatasetAutoDisableLog.dataset_id == dataset_id, + DatasetAutoDisableLog.created_at >= start_date, + ) + .all() + ) if dataset_auto_disable_logs: return { "document_ids": [log.document_id for log in dataset_auto_disable_logs], @@ -873,7 +881,9 @@ class DocumentService: @staticmethod def get_documents_position(dataset_id): - document = Document.query.filter_by(dataset_id=dataset_id).order_by(Document.position.desc()).first() + document = ( + db.session.query(Document).filter_by(dataset_id=dataset_id).order_by(Document.position.desc()).first() + ) if document: return document.position + 1 else: @@ -948,11 +958,11 @@ class DocumentService: "score_threshold_enabled": False, } - dataset.retrieval_model = ( - knowledge_config.retrieval_model.model_dump() - if knowledge_config.retrieval_model - else default_retrieval_model - ) # type: ignore + dataset.retrieval_model = ( + knowledge_config.retrieval_model.model_dump() + if knowledge_config.retrieval_model + else default_retrieval_model + ) # type: ignore documents = [] if knowledge_config.original_document_id: @@ -960,7 +970,7 @@ class DocumentService: documents.append(document) batch = document.batch else: - batch = time.strftime("%Y%m%d%H%M%S") + str(random.randint(100000, 999999)) + batch = time.strftime("%Y%m%d%H%M%S") + str(100000 + secrets.randbelow(exclusive_upper_bound=900000)) # save process rule if not dataset_process_rule: process_rule = knowledge_config.process_rule @@ -980,7 +990,7 @@ class DocumentService: created_by=account.id, ) else: - logging.warn( + logging.warning( f"Invalid process rule mode: {process_rule.mode}, can not find dataset process rule" ) return @@ -1010,13 +1020,17 @@ class DocumentService: } # check duplicate if knowledge_config.duplicate: - document = Document.query.filter_by( - dataset_id=dataset.id, - tenant_id=current_user.current_tenant_id, - data_source_type="upload_file", - enabled=True, - name=file_name, - ).first() + document = ( + db.session.query(Document) + .filter_by( + dataset_id=dataset.id, + tenant_id=current_user.current_tenant_id, + data_source_type="upload_file", + enabled=True, + name=file_name, + ) + .first() + ) if document: document.dataset_process_rule_id = dataset_process_rule.id # type: ignore document.updated_at = datetime.datetime.now(datetime.UTC).replace(tzinfo=None) @@ -1054,12 +1068,16 @@ class DocumentService: raise ValueError("No notion info list found.") exist_page_ids = [] exist_document = {} - documents = Document.query.filter_by( - dataset_id=dataset.id, - tenant_id=current_user.current_tenant_id, - data_source_type="notion_import", - enabled=True, - ).all() + documents = ( + db.session.query(Document) + .filter_by( + dataset_id=dataset.id, + tenant_id=current_user.current_tenant_id, + data_source_type="notion_import", + enabled=True, + ) + .all() + ) if documents: for document in documents: data_source_info = json.loads(document.data_source_info) @@ -1067,14 +1085,18 @@ class DocumentService: exist_document[data_source_info["notion_page_id"]] = document.id for notion_info in notion_info_list: workspace_id = notion_info.workspace_id - data_source_binding = DataSourceOauthBinding.query.filter( - db.and_( - DataSourceOauthBinding.tenant_id == current_user.current_tenant_id, - DataSourceOauthBinding.provider == "notion", - DataSourceOauthBinding.disabled == False, - DataSourceOauthBinding.source_info["workspace_id"] == f'"{workspace_id}"', + data_source_binding = ( + db.session.query(DataSourceOauthBinding) + .filter( + db.and_( + DataSourceOauthBinding.tenant_id == current_user.current_tenant_id, + DataSourceOauthBinding.provider == "notion", + DataSourceOauthBinding.disabled == False, + DataSourceOauthBinding.source_info["workspace_id"] == f'"{workspace_id}"', + ) ) - ).first() + .first() + ) if not data_source_binding: raise ValueError("Data source binding not found.") for page in notion_info.pages: @@ -1206,12 +1228,16 @@ class DocumentService: @staticmethod def get_tenant_documents_count(): - documents_count = Document.query.filter( - Document.completed_at.isnot(None), - Document.enabled == True, - Document.archived == False, - Document.tenant_id == current_user.current_tenant_id, - ).count() + documents_count = ( + db.session.query(Document) + .filter( + Document.completed_at.isnot(None), + Document.enabled == True, + Document.archived == False, + Document.tenant_id == current_user.current_tenant_id, + ) + .count() + ) return documents_count @staticmethod @@ -1278,14 +1304,18 @@ class DocumentService: notion_info_list = document_data.data_source.info_list.notion_info_list for notion_info in notion_info_list: workspace_id = notion_info.workspace_id - data_source_binding = DataSourceOauthBinding.query.filter( - db.and_( - DataSourceOauthBinding.tenant_id == current_user.current_tenant_id, - DataSourceOauthBinding.provider == "notion", - DataSourceOauthBinding.disabled == False, - DataSourceOauthBinding.source_info["workspace_id"] == f'"{workspace_id}"', + data_source_binding = ( + db.session.query(DataSourceOauthBinding) + .filter( + db.and_( + DataSourceOauthBinding.tenant_id == current_user.current_tenant_id, + DataSourceOauthBinding.provider == "notion", + DataSourceOauthBinding.disabled == False, + DataSourceOauthBinding.source_info["workspace_id"] == f'"{workspace_id}"', + ) ) - ).first() + .first() + ) if not data_source_binding: raise ValueError("Data source binding not found.") for page in notion_info.pages: @@ -1328,7 +1358,7 @@ class DocumentService: db.session.commit() # update document segment update_params = {DocumentSegment.status: "re_segment"} - DocumentSegment.query.filter_by(document_id=document.id).update(update_params) + db.session.query(DocumentSegment).filter_by(document_id=document.id).update(update_params) db.session.commit() # trigger async task document_indexing_update_task.delay(document.dataset_id, document.id) @@ -1918,7 +1948,8 @@ class SegmentService: @classmethod def delete_segments(cls, segment_ids: list, document: Document, dataset: Dataset): index_node_ids = ( - DocumentSegment.query.with_entities(DocumentSegment.index_node_id) + db.session.query(DocumentSegment) + .with_entities(DocumentSegment.index_node_id) .filter( DocumentSegment.id.in_(segment_ids), DocumentSegment.dataset_id == dataset.id, @@ -2157,20 +2188,28 @@ class SegmentService: def get_child_chunks( cls, segment_id: str, document_id: str, dataset_id: str, page: int, limit: int, keyword: Optional[str] = None ): - query = ChildChunk.query.filter_by( - tenant_id=current_user.current_tenant_id, - dataset_id=dataset_id, - document_id=document_id, - segment_id=segment_id, - ).order_by(ChildChunk.position.asc()) + query = ( + select(ChildChunk) + .filter_by( + tenant_id=current_user.current_tenant_id, + dataset_id=dataset_id, + document_id=document_id, + segment_id=segment_id, + ) + .order_by(ChildChunk.position.asc()) + ) if keyword: query = query.where(ChildChunk.content.ilike(f"%{keyword}%")) - return query.paginate(page=page, per_page=limit, max_per_page=100, error_out=False) + return db.paginate(select=query, page=page, per_page=limit, max_per_page=100, error_out=False) @classmethod def get_child_chunk_by_id(cls, child_chunk_id: str, tenant_id: str) -> Optional[ChildChunk]: """Get a child chunk by its ID.""" - result = ChildChunk.query.filter(ChildChunk.id == child_chunk_id, ChildChunk.tenant_id == tenant_id).first() + result = ( + db.session.query(ChildChunk) + .filter(ChildChunk.id == child_chunk_id, ChildChunk.tenant_id == tenant_id) + .first() + ) return result if isinstance(result, ChildChunk) else None @classmethod @@ -2184,7 +2223,7 @@ class SegmentService: limit: int = 20, ): """Get segments for a document with optional filtering.""" - query = DocumentSegment.query.filter( + query = select(DocumentSegment).filter( DocumentSegment.document_id == document_id, DocumentSegment.tenant_id == tenant_id ) @@ -2194,9 +2233,8 @@ class SegmentService: if keyword: query = query.filter(DocumentSegment.content.ilike(f"%{keyword}%")) - paginated_segments = query.order_by(DocumentSegment.position.asc()).paginate( - page=page, per_page=limit, max_per_page=100, error_out=False - ) + query = query.order_by(DocumentSegment.position.asc()) + paginated_segments = db.paginate(select=query, page=page, per_page=limit, max_per_page=100, error_out=False) return paginated_segments.items, paginated_segments.total @@ -2236,9 +2274,11 @@ class SegmentService: raise ValueError(ex.description) # check segment - segment = DocumentSegment.query.filter( - DocumentSegment.id == segment_id, DocumentSegment.tenant_id == user_id - ).first() + segment = ( + db.session.query(DocumentSegment) + .filter(DocumentSegment.id == segment_id, DocumentSegment.tenant_id == user_id) + .first() + ) if not segment: raise NotFound("Segment not found.") @@ -2251,9 +2291,11 @@ class SegmentService: @classmethod def get_segment_by_id(cls, segment_id: str, tenant_id: str) -> Optional[DocumentSegment]: """Get a segment by its ID.""" - result = DocumentSegment.query.filter( - DocumentSegment.id == segment_id, DocumentSegment.tenant_id == tenant_id - ).first() + result = ( + db.session.query(DocumentSegment) + .filter(DocumentSegment.id == segment_id, DocumentSegment.tenant_id == tenant_id) + .first() + ) return result if isinstance(result, DocumentSegment) else None diff --git a/api/services/enterprise/enterprise_service.py b/api/services/enterprise/enterprise_service.py index abc01ddf8f..8c06ee9386 100644 --- a/api/services/enterprise/enterprise_service.py +++ b/api/services/enterprise/enterprise_service.py @@ -1,11 +1,114 @@ +from datetime import datetime + +from pydantic import BaseModel, Field + from services.enterprise.base import EnterpriseRequest +class WebAppSettings(BaseModel): + access_mode: str = Field( + description="Access mode for the web app. Can be 'public', 'private', 'private_all', 'sso_verified'", + default="private", + alias="accessMode", + ) + + class EnterpriseService: @classmethod def get_info(cls): return EnterpriseRequest.send_request("GET", "/info") @classmethod - def get_app_web_sso_enabled(cls, app_code): - return EnterpriseRequest.send_request("GET", f"/app-sso-setting?appCode={app_code}") + def get_workspace_info(cls, tenant_id: str): + return EnterpriseRequest.send_request("GET", f"/workspace/{tenant_id}/info") + + @classmethod + def get_app_sso_settings_last_update_time(cls) -> datetime: + data = EnterpriseRequest.send_request("GET", "/sso/app/last-update-time") + if not data: + raise ValueError("No data found.") + try: + # parse the UTC timestamp from the response + return datetime.fromisoformat(data.replace("Z", "+00:00")) + except ValueError as e: + raise ValueError(f"Invalid date format: {data}") from e + + @classmethod + def get_workspace_sso_settings_last_update_time(cls) -> datetime: + data = EnterpriseRequest.send_request("GET", "/sso/workspace/last-update-time") + if not data: + raise ValueError("No data found.") + try: + # parse the UTC timestamp from the response + return datetime.fromisoformat(data.replace("Z", "+00:00")) + except ValueError as e: + raise ValueError(f"Invalid date format: {data}") from e + + class WebAppAuth: + @classmethod + def is_user_allowed_to_access_webapp(cls, user_id: str, app_code: str): + params = {"userId": user_id, "appCode": app_code} + data = EnterpriseRequest.send_request("GET", "/webapp/permission", params=params) + + return data.get("result", False) + + @classmethod + def get_app_access_mode_by_id(cls, app_id: str) -> WebAppSettings: + if not app_id: + raise ValueError("app_id must be provided.") + params = {"appId": app_id} + data = EnterpriseRequest.send_request("GET", "/webapp/access-mode/id", params=params) + if not data: + raise ValueError("No data found.") + return WebAppSettings(**data) + + @classmethod + def batch_get_app_access_mode_by_id(cls, app_ids: list[str]) -> dict[str, WebAppSettings]: + if not app_ids: + return {} + body = {"appIds": app_ids} + data: dict[str, str] = EnterpriseRequest.send_request("POST", "/webapp/access-mode/batch/id", json=body) + if not data: + raise ValueError("No data found.") + + if not isinstance(data["accessModes"], dict): + raise ValueError("Invalid data format.") + + ret = {} + for key, value in data["accessModes"].items(): + curr = WebAppSettings() + curr.access_mode = value + ret[key] = curr + + return ret + + @classmethod + def get_app_access_mode_by_code(cls, app_code: str) -> WebAppSettings: + if not app_code: + raise ValueError("app_code must be provided.") + params = {"appCode": app_code} + data = EnterpriseRequest.send_request("GET", "/webapp/access-mode/code", params=params) + if not data: + raise ValueError("No data found.") + return WebAppSettings(**data) + + @classmethod + def update_app_access_mode(cls, app_id: str, access_mode: str): + if not app_id: + raise ValueError("app_id must be provided.") + if access_mode not in ["public", "private", "private_all"]: + raise ValueError("access_mode must be either 'public', 'private', or 'private_all'") + + data = {"appId": app_id, "accessMode": access_mode} + + response = EnterpriseRequest.send_request("POST", "/webapp/access-mode", json=data) + + return response.get("result", False) + + @classmethod + def cleanup_webapp(cls, app_id: str): + if not app_id: + raise ValueError("app_id must be provided.") + + body = {"appId": app_id} + EnterpriseRequest.send_request("DELETE", "/webapp/clean", json=body) diff --git a/api/services/enterprise/mail_service.py b/api/services/enterprise/mail_service.py new file mode 100644 index 0000000000..630e7679ac --- /dev/null +++ b/api/services/enterprise/mail_service.py @@ -0,0 +1,18 @@ +from pydantic import BaseModel + +from tasks.mail_enterprise_task import send_enterprise_email_task + + +class DifyMail(BaseModel): + to: list[str] + subject: str + body: str + substitutions: dict[str, str] = {} + + +class EnterpriseMailService: + @classmethod + def send_mail(cls, mail: DifyMail): + send_enterprise_email_task.delay( + to=mail.to, subject=mail.subject, body=mail.body, substitutions=mail.substitutions + ) diff --git a/api/services/errors/__init__.py b/api/services/errors/__init__.py index eb1f055708..697e691224 100644 --- a/api/services/errors/__init__.py +++ b/api/services/errors/__init__.py @@ -4,7 +4,6 @@ from . import ( app_model_config, audio, base, - completion, conversation, dataset, document, @@ -19,7 +18,6 @@ __all__ = [ "app_model_config", "audio", "base", - "completion", "conversation", "dataset", "document", diff --git a/api/services/errors/account.py b/api/services/errors/account.py index 5aca12ffeb..4d3d150e07 100644 --- a/api/services/errors/account.py +++ b/api/services/errors/account.py @@ -55,7 +55,3 @@ class MemberNotInTenantError(BaseServiceError): class RoleAlreadyAssignedError(BaseServiceError): pass - - -class RateLimitExceededError(BaseServiceError): - pass diff --git a/api/services/errors/completion.py b/api/services/errors/completion.py deleted file mode 100644 index 7fc50a588e..0000000000 --- a/api/services/errors/completion.py +++ /dev/null @@ -1,5 +0,0 @@ -from services.errors.base import BaseServiceError - - -class CompletionStoppedError(BaseServiceError): - pass diff --git a/api/services/errors/conversation.py b/api/services/errors/conversation.py index 139dd9a70a..f8051e3417 100644 --- a/api/services/errors/conversation.py +++ b/api/services/errors/conversation.py @@ -11,3 +11,7 @@ class ConversationNotExistsError(BaseServiceError): class ConversationCompletedError(Exception): pass + + +class ConversationVariableNotExistsError(BaseServiceError): + pass diff --git a/api/services/errors/workspace.py b/api/services/errors/workspace.py index 714064ffdf..577238507f 100644 --- a/api/services/errors/workspace.py +++ b/api/services/errors/workspace.py @@ -7,3 +7,7 @@ class WorkSpaceNotAllowedCreateError(BaseServiceError): class WorkSpaceNotFoundError(BaseServiceError): pass + + +class WorkspacesLimitExceededError(BaseServiceError): + pass diff --git a/api/services/external_knowledge_service.py b/api/services/external_knowledge_service.py index d9ee221a3c..eb50d79494 100644 --- a/api/services/external_knowledge_service.py +++ b/api/services/external_knowledge_service.py @@ -2,9 +2,10 @@ import json from copy import deepcopy from datetime import UTC, datetime from typing import Any, Optional, Union, cast +from urllib.parse import urlparse import httpx -import validators +from sqlalchemy import select from constants import HIDDEN_VALUE from core.helper import ssrf_proxy @@ -24,14 +25,20 @@ from services.errors.dataset import DatasetNameDuplicateError class ExternalDatasetService: @staticmethod - def get_external_knowledge_apis(page, per_page, tenant_id, search=None) -> tuple[list[ExternalKnowledgeApis], int]: - query = ExternalKnowledgeApis.query.filter(ExternalKnowledgeApis.tenant_id == tenant_id).order_by( - ExternalKnowledgeApis.created_at.desc() + def get_external_knowledge_apis( + page, per_page, tenant_id, search=None + ) -> tuple[list[ExternalKnowledgeApis], int | None]: + query = ( + select(ExternalKnowledgeApis) + .filter(ExternalKnowledgeApis.tenant_id == tenant_id) + .order_by(ExternalKnowledgeApis.created_at.desc()) ) if search: query = query.filter(ExternalKnowledgeApis.name.ilike(f"%{search}%")) - external_knowledge_apis = query.paginate(page=page, per_page=per_page, max_per_page=100, error_out=False) + external_knowledge_apis = db.paginate( + select=query, page=page, per_page=per_page, max_per_page=100, error_out=False + ) return external_knowledge_apis.items, external_knowledge_apis.total @@ -72,7 +79,9 @@ class ExternalDatasetService: endpoint = f"{settings['endpoint']}/retrieval" api_key = settings["api_key"] - if not validators.url(endpoint, simple_host=True): + + parsed_url = urlparse(endpoint) + if not all([parsed_url.scheme, parsed_url.netloc]): if not endpoint.startswith("http://") and not endpoint.startswith("https://"): raise ValueError(f"invalid endpoint: {endpoint} must start with http:// or https://") else: @@ -90,18 +99,18 @@ class ExternalDatasetService: @staticmethod def get_external_knowledge_api(external_knowledge_api_id: str) -> ExternalKnowledgeApis: - external_knowledge_api: Optional[ExternalKnowledgeApis] = ExternalKnowledgeApis.query.filter_by( - id=external_knowledge_api_id - ).first() + external_knowledge_api: Optional[ExternalKnowledgeApis] = ( + db.session.query(ExternalKnowledgeApis).filter_by(id=external_knowledge_api_id).first() + ) if external_knowledge_api is None: raise ValueError("api template not found") return external_knowledge_api @staticmethod def update_external_knowledge_api(tenant_id, user_id, external_knowledge_api_id, args) -> ExternalKnowledgeApis: - external_knowledge_api: Optional[ExternalKnowledgeApis] = ExternalKnowledgeApis.query.filter_by( - id=external_knowledge_api_id, tenant_id=tenant_id - ).first() + external_knowledge_api: Optional[ExternalKnowledgeApis] = ( + db.session.query(ExternalKnowledgeApis).filter_by(id=external_knowledge_api_id, tenant_id=tenant_id).first() + ) if external_knowledge_api is None: raise ValueError("api template not found") if args.get("settings") and args.get("settings").get("api_key") == HIDDEN_VALUE: @@ -118,9 +127,9 @@ class ExternalDatasetService: @staticmethod def delete_external_knowledge_api(tenant_id: str, external_knowledge_api_id: str): - external_knowledge_api = ExternalKnowledgeApis.query.filter_by( - id=external_knowledge_api_id, tenant_id=tenant_id - ).first() + external_knowledge_api = ( + db.session.query(ExternalKnowledgeApis).filter_by(id=external_knowledge_api_id, tenant_id=tenant_id).first() + ) if external_knowledge_api is None: raise ValueError("api template not found") @@ -129,25 +138,29 @@ class ExternalDatasetService: @staticmethod def external_knowledge_api_use_check(external_knowledge_api_id: str) -> tuple[bool, int]: - count = ExternalKnowledgeBindings.query.filter_by(external_knowledge_api_id=external_knowledge_api_id).count() + count = ( + db.session.query(ExternalKnowledgeBindings) + .filter_by(external_knowledge_api_id=external_knowledge_api_id) + .count() + ) if count > 0: return True, count return False, 0 @staticmethod def get_external_knowledge_binding_with_dataset_id(tenant_id: str, dataset_id: str) -> ExternalKnowledgeBindings: - external_knowledge_binding: Optional[ExternalKnowledgeBindings] = ExternalKnowledgeBindings.query.filter_by( - dataset_id=dataset_id, tenant_id=tenant_id - ).first() + external_knowledge_binding: Optional[ExternalKnowledgeBindings] = ( + db.session.query(ExternalKnowledgeBindings).filter_by(dataset_id=dataset_id, tenant_id=tenant_id).first() + ) if not external_knowledge_binding: raise ValueError("external knowledge binding not found") return external_knowledge_binding @staticmethod def document_create_args_validate(tenant_id: str, external_knowledge_api_id: str, process_parameter: dict): - external_knowledge_api = ExternalKnowledgeApis.query.filter_by( - id=external_knowledge_api_id, tenant_id=tenant_id - ).first() + external_knowledge_api = ( + db.session.query(ExternalKnowledgeApis).filter_by(id=external_knowledge_api_id, tenant_id=tenant_id).first() + ) if external_knowledge_api is None: raise ValueError("api template not found") settings = json.loads(external_knowledge_api.settings) @@ -210,11 +223,13 @@ class ExternalDatasetService: @staticmethod def create_external_dataset(tenant_id: str, user_id: str, args: dict) -> Dataset: # check if dataset name already exists - if Dataset.query.filter_by(name=args.get("name"), tenant_id=tenant_id).first(): + if db.session.query(Dataset).filter_by(name=args.get("name"), tenant_id=tenant_id).first(): raise DatasetNameDuplicateError(f"Dataset with name {args.get('name')} already exists.") - external_knowledge_api = ExternalKnowledgeApis.query.filter_by( - id=args.get("external_knowledge_api_id"), tenant_id=tenant_id - ).first() + external_knowledge_api = ( + db.session.query(ExternalKnowledgeApis) + .filter_by(id=args.get("external_knowledge_api_id"), tenant_id=tenant_id) + .first() + ) if external_knowledge_api is None: raise ValueError("api template not found") @@ -252,15 +267,17 @@ class ExternalDatasetService: external_retrieval_parameters: dict, metadata_condition: Optional[MetadataCondition] = None, ) -> list: - external_knowledge_binding = ExternalKnowledgeBindings.query.filter_by( - dataset_id=dataset_id, tenant_id=tenant_id - ).first() + external_knowledge_binding = ( + db.session.query(ExternalKnowledgeBindings).filter_by(dataset_id=dataset_id, tenant_id=tenant_id).first() + ) if not external_knowledge_binding: raise ValueError("external knowledge binding not found") - external_knowledge_api = ExternalKnowledgeApis.query.filter_by( - id=external_knowledge_binding.external_knowledge_api_id - ).first() + external_knowledge_api = ( + db.session.query(ExternalKnowledgeApis) + .filter_by(id=external_knowledge_binding.external_knowledge_api_id) + .first() + ) if not external_knowledge_api: raise ValueError("external api template not found") diff --git a/api/services/feature_service.py b/api/services/feature_service.py index c2226c319f..be85a03e80 100644 --- a/api/services/feature_service.py +++ b/api/services/feature_service.py @@ -1,6 +1,6 @@ from enum import StrEnum -from pydantic import BaseModel, ConfigDict +from pydantic import BaseModel, ConfigDict, Field from configs import dify_config from services.billing_service import BillingService @@ -27,6 +27,32 @@ class LimitationModel(BaseModel): limit: int = 0 +class LicenseLimitationModel(BaseModel): + """ + - enabled: whether this limit is enforced + - size: current usage count + - limit: maximum allowed count; 0 means unlimited + """ + + enabled: bool = Field(False, description="Whether this limit is currently active") + size: int = Field(0, description="Number of resources already consumed") + limit: int = Field(0, description="Maximum number of resources allowed; 0 means no limit") + + def is_available(self, required: int = 1) -> bool: + """ + Determine whether the requested amount can be allocated. + + Returns True if: + - this limit is not active, or + - the limit is zero (unlimited), or + - there is enough remaining quota. + """ + if not self.enabled or self.limit == 0: + return True + + return (self.limit - self.size) >= required + + class LicenseStatus(StrEnum): NONE = "none" INACTIVE = "inactive" @@ -39,6 +65,27 @@ class LicenseStatus(StrEnum): class LicenseModel(BaseModel): status: LicenseStatus = LicenseStatus.NONE expired_at: str = "" + workspaces: LicenseLimitationModel = LicenseLimitationModel(enabled=False, size=0, limit=0) + + +class BrandingModel(BaseModel): + enabled: bool = False + application_title: str = "" + login_page_logo: str = "" + workspace_logo: str = "" + favicon: str = "" + + +class WebAppAuthSSOModel(BaseModel): + protocol: str = "" + + +class WebAppAuthModel(BaseModel): + enabled: bool = False + allow_sso: bool = False + sso_config: WebAppAuthSSOModel = WebAppAuthSSOModel() + allow_email_code_login: bool = False + allow_email_password_login: bool = False class FeatureModel(BaseModel): @@ -54,6 +101,8 @@ class FeatureModel(BaseModel): can_replace_logo: bool = False model_load_balancing_enabled: bool = False dataset_operator_enabled: bool = False + webapp_copyright_enabled: bool = False + workspace_members: LicenseLimitationModel = LicenseLimitationModel(enabled=False, size=0, limit=0) # pydantic configs model_config = ConfigDict(protected_namespaces=()) @@ -68,9 +117,6 @@ class KnowledgeRateLimitModel(BaseModel): class SystemFeatureModel(BaseModel): sso_enforced_for_signin: bool = False sso_enforced_for_signin_protocol: str = "" - sso_enforced_for_web: bool = False - sso_enforced_for_web_protocol: str = "" - enable_web_sso_switch_component: bool = False enable_marketplace: bool = False max_plugin_package_size: int = dify_config.PLUGIN_MAX_PACKAGE_SIZE enable_email_code_login: bool = False @@ -80,6 +126,8 @@ class SystemFeatureModel(BaseModel): is_allow_create_workspace: bool = False is_email_setup: bool = False license: LicenseModel = LicenseModel() + branding: BrandingModel = BrandingModel() + webapp_auth: WebAppAuthModel = WebAppAuthModel() class FeatureService: @@ -92,6 +140,10 @@ class FeatureService: if dify_config.BILLING_ENABLED and tenant_id: cls._fulfill_params_from_billing_api(features, tenant_id) + if dify_config.ENTERPRISE_ENABLED: + features.webapp_copyright_enabled = True + cls._fulfill_params_from_workspace_info(features, tenant_id) + return features @classmethod @@ -111,8 +163,8 @@ class FeatureService: cls._fulfill_system_params_from_env(system_features) if dify_config.ENTERPRISE_ENABLED: - system_features.enable_web_sso_switch_component = True - + system_features.branding.enabled = True + system_features.webapp_auth.enabled = True cls._fulfill_params_from_enterprise(system_features) if dify_config.MARKETPLACE_ENABLED: @@ -136,6 +188,14 @@ class FeatureService: features.dataset_operator_enabled = dify_config.DATASET_OPERATOR_ENABLED features.education.enabled = dify_config.EDUCATION_ENABLED + @classmethod + def _fulfill_params_from_workspace_info(cls, features: FeatureModel, tenant_id: str): + workspace_info = EnterpriseService.get_workspace_info(tenant_id) + if "WorkspaceMembers" in workspace_info: + features.workspace_members.size = workspace_info["WorkspaceMembers"]["used"] + features.workspace_members.limit = workspace_info["WorkspaceMembers"]["limit"] + features.workspace_members.enabled = workspace_info["WorkspaceMembers"]["enabled"] + @classmethod def _fulfill_params_from_billing_api(cls, features: FeatureModel, tenant_id: str): billing_info = BillingService.get_info(tenant_id) @@ -145,6 +205,9 @@ class FeatureService: features.billing.subscription.interval = billing_info["subscription"]["interval"] features.education.activated = billing_info["subscription"].get("education", False) + if features.billing.subscription.plan != "sandbox": + features.webapp_copyright_enabled = True + if "members" in billing_info: features.members.size = billing_info["members"]["size"] features.members.limit = billing_info["members"]["limit"] @@ -178,38 +241,53 @@ class FeatureService: features.knowledge_rate_limit = billing_info["knowledge_rate_limit"]["limit"] @classmethod - def _fulfill_params_from_enterprise(cls, features): + def _fulfill_params_from_enterprise(cls, features: SystemFeatureModel): enterprise_info = EnterpriseService.get_info() - if "sso_enforced_for_signin" in enterprise_info: - features.sso_enforced_for_signin = enterprise_info["sso_enforced_for_signin"] + if "SSOEnforcedForSignin" in enterprise_info: + features.sso_enforced_for_signin = enterprise_info["SSOEnforcedForSignin"] - if "sso_enforced_for_signin_protocol" in enterprise_info: - features.sso_enforced_for_signin_protocol = enterprise_info["sso_enforced_for_signin_protocol"] + if "SSOEnforcedForSigninProtocol" in enterprise_info: + features.sso_enforced_for_signin_protocol = enterprise_info["SSOEnforcedForSigninProtocol"] - if "sso_enforced_for_web" in enterprise_info: - features.sso_enforced_for_web = enterprise_info["sso_enforced_for_web"] + if "EnableEmailCodeLogin" in enterprise_info: + features.enable_email_code_login = enterprise_info["EnableEmailCodeLogin"] - if "sso_enforced_for_web_protocol" in enterprise_info: - features.sso_enforced_for_web_protocol = enterprise_info["sso_enforced_for_web_protocol"] + if "EnableEmailPasswordLogin" in enterprise_info: + features.enable_email_password_login = enterprise_info["EnableEmailPasswordLogin"] - if "enable_email_code_login" in enterprise_info: - features.enable_email_code_login = enterprise_info["enable_email_code_login"] + if "IsAllowRegister" in enterprise_info: + features.is_allow_register = enterprise_info["IsAllowRegister"] - if "enable_email_password_login" in enterprise_info: - features.enable_email_password_login = enterprise_info["enable_email_password_login"] + if "IsAllowCreateWorkspace" in enterprise_info: + features.is_allow_create_workspace = enterprise_info["IsAllowCreateWorkspace"] - if "is_allow_register" in enterprise_info: - features.is_allow_register = enterprise_info["is_allow_register"] + if "Branding" in enterprise_info: + features.branding.application_title = enterprise_info["Branding"].get("applicationTitle", "") + features.branding.login_page_logo = enterprise_info["Branding"].get("loginPageLogo", "") + features.branding.workspace_logo = enterprise_info["Branding"].get("workspaceLogo", "") + features.branding.favicon = enterprise_info["Branding"].get("favicon", "") - if "is_allow_create_workspace" in enterprise_info: - features.is_allow_create_workspace = enterprise_info["is_allow_create_workspace"] + if "WebAppAuth" in enterprise_info: + features.webapp_auth.allow_sso = enterprise_info["WebAppAuth"].get("allowSso", False) + features.webapp_auth.allow_email_code_login = enterprise_info["WebAppAuth"].get( + "allowEmailCodeLogin", False + ) + features.webapp_auth.allow_email_password_login = enterprise_info["WebAppAuth"].get( + "allowEmailPasswordLogin", False + ) + features.webapp_auth.sso_config.protocol = enterprise_info.get("SSOEnforcedForWebProtocol", "") - if "license" in enterprise_info: - license_info = enterprise_info["license"] + if "License" in enterprise_info: + license_info = enterprise_info["License"] if "status" in license_info: features.license.status = LicenseStatus(license_info.get("status", LicenseStatus.INACTIVE)) - if "expired_at" in license_info: - features.license.expired_at = license_info["expired_at"] + if "expiredAt" in license_info: + features.license.expired_at = license_info["expiredAt"] + + if "workspaces" in license_info: + features.license.workspaces.enabled = license_info["workspaces"]["enabled"] + features.license.workspaces.limit = license_info["workspaces"]["limit"] + features.license.workspaces.size = license_info["workspaces"]["used"] diff --git a/api/services/file_service.py b/api/services/file_service.py index b4442c36c3..2d68f30c5a 100644 --- a/api/services/file_service.py +++ b/api/services/file_service.py @@ -4,7 +4,7 @@ import os import uuid from typing import Any, Literal, Union -from flask_login import current_user # type: ignore +from flask_login import current_user from werkzeug.exceptions import NotFound from configs import dify_config @@ -19,7 +19,7 @@ from core.rag.extractor.extract_processor import ExtractProcessor from extensions.ext_database import db from extensions.ext_storage import storage from models.account import Account -from models.enums import CreatedByRole +from models.enums import CreatorUserRole from models.model import EndUser, UploadFile from .errors.file import FileTooLargeError, UnsupportedFileTypeError @@ -81,7 +81,7 @@ class FileService: size=file_size, extension=extension, mime_type=mimetype, - created_by_role=(CreatedByRole.ACCOUNT if isinstance(user, Account) else CreatedByRole.END_USER), + created_by_role=(CreatorUserRole.ACCOUNT if isinstance(user, Account) else CreatorUserRole.END_USER), created_by=user.id, created_at=datetime.datetime.now(datetime.UTC).replace(tzinfo=None), used=False, @@ -133,7 +133,7 @@ class FileService: extension="txt", mime_type="text/plain", created_by=current_user.id, - created_by_role=CreatedByRole.ACCOUNT, + created_by_role=CreatorUserRole.ACCOUNT, created_at=datetime.datetime.now(datetime.UTC).replace(tzinfo=None), used=True, used_by=current_user.id, diff --git a/api/services/hit_testing_service.py b/api/services/hit_testing_service.py index 0b98065f5d..519d5abca5 100644 --- a/api/services/hit_testing_service.py +++ b/api/services/hit_testing_service.py @@ -2,8 +2,11 @@ import logging import time from typing import Any +from core.app.app_config.entities import ModelConfig +from core.model_runtime.entities import LLMMode from core.rag.datasource.retrieval_service import RetrievalService from core.rag.models.document import Document +from core.rag.retrieval.dataset_retrieval import DatasetRetrieval from core.rag.retrieval.retrieval_methods import RetrievalMethod from extensions.ext_database import db from models.account import Account @@ -34,7 +37,29 @@ class HitTestingService: # get retrieval model , if the model is not setting , using default if not retrieval_model: retrieval_model = dataset.retrieval_model or default_retrieval_model - + document_ids_filter = None + metadata_filtering_conditions = retrieval_model.get("metadata_filtering_conditions", {}) + if metadata_filtering_conditions: + dataset_retrieval = DatasetRetrieval() + + from core.app.app_config.entities import MetadataFilteringCondition + + metadata_filtering_conditions = MetadataFilteringCondition(**metadata_filtering_conditions) + + metadata_filter_document_ids, metadata_condition = dataset_retrieval.get_metadata_filter_condition( + dataset_ids=[dataset.id], + query=query, + metadata_filtering_mode="manual", + metadata_filtering_conditions=metadata_filtering_conditions, + inputs={}, + tenant_id="", + user_id="", + metadata_model_config=ModelConfig(provider="", name="", mode=LLMMode.CHAT, completion_params={}), + ) + if metadata_filter_document_ids: + document_ids_filter = metadata_filter_document_ids.get(dataset.id, []) + if metadata_condition and not document_ids_filter: + return cls.compact_retrieve_response(query, []) all_documents = RetrievalService.retrieve( retrieval_method=retrieval_model.get("search_method", "semantic_search"), dataset_id=dataset.id, @@ -48,6 +73,7 @@ class HitTestingService: else None, reranking_mode=retrieval_model.get("reranking_mode") or "reranking_model", weights=retrieval_model.get("weights", None), + document_ids_filter=document_ids_filter, ) end = time.perf_counter() @@ -69,6 +95,7 @@ class HitTestingService: query: str, account: Account, external_retrieval_model: dict, + metadata_filtering_conditions: dict, ) -> dict: if dataset.provider != "external": return { @@ -82,6 +109,7 @@ class HitTestingService: dataset_id=dataset.id, query=cls.escape_query_for_search(query), external_retrieval_model=external_retrieval_model, + metadata_filtering_conditions=metadata_filtering_conditions, ) end = time.perf_counter() @@ -97,7 +125,7 @@ class HitTestingService: return dict(cls.compact_external_retrieve_response(dataset, query, all_documents)) @classmethod - def compact_retrieve_response(cls, query: str, documents: list[Document]): + def compact_retrieve_response(cls, query: str, documents: list[Document]) -> dict[Any, Any]: records = RetrievalService.format_retrieval_documents(documents) return { diff --git a/api/services/message_service.py b/api/services/message_service.py index aefab1556c..51b070ece7 100644 --- a/api/services/message_service.py +++ b/api/services/message_service.py @@ -177,6 +177,21 @@ class MessageService: return feedback + @classmethod + def get_all_messages_feedbacks(cls, app_model: App, page: int, limit: int): + """Get all feedbacks of an app""" + offset = (page - 1) * limit + feedbacks = ( + db.session.query(MessageFeedback) + .filter(MessageFeedback.app_id == app_model.id) + .order_by(MessageFeedback.created_at.desc(), MessageFeedback.id.desc()) + .limit(limit) + .offset(offset) + .all() + ) + + return [record.to_dict() for record in feedbacks] + @classmethod def get_message(cls, app_model: App, user: Optional[Union[Account, EndUser]], message_id: str): message = ( diff --git a/api/services/metadata_service.py b/api/services/metadata_service.py index 4cd2f9e8cb..26d6d4ce18 100644 --- a/api/services/metadata_service.py +++ b/api/services/metadata_service.py @@ -3,7 +3,7 @@ import datetime import logging from typing import Optional -from flask_login import current_user # type: ignore +from flask_login import current_user from core.rag.index_processor.constant.built_in_field import BuiltInField, MetadataDataSource from extensions.ext_database import db @@ -20,9 +20,11 @@ class MetadataService: @staticmethod def create_metadata(dataset_id: str, metadata_args: MetadataArgs) -> DatasetMetadata: # check if metadata name already exists - if DatasetMetadata.query.filter_by( - tenant_id=current_user.current_tenant_id, dataset_id=dataset_id, name=metadata_args.name - ).first(): + if ( + db.session.query(DatasetMetadata) + .filter_by(tenant_id=current_user.current_tenant_id, dataset_id=dataset_id, name=metadata_args.name) + .first() + ): raise ValueError("Metadata name already exists.") for field in BuiltInField: if field.value == metadata_args.name: @@ -42,16 +44,18 @@ class MetadataService: def update_metadata_name(dataset_id: str, metadata_id: str, name: str) -> DatasetMetadata: # type: ignore lock_key = f"dataset_metadata_lock_{dataset_id}" # check if metadata name already exists - if DatasetMetadata.query.filter_by( - tenant_id=current_user.current_tenant_id, dataset_id=dataset_id, name=name - ).first(): + if ( + db.session.query(DatasetMetadata) + .filter_by(tenant_id=current_user.current_tenant_id, dataset_id=dataset_id, name=name) + .first() + ): raise ValueError("Metadata name already exists.") for field in BuiltInField: if field.value == name: raise ValueError("Metadata name already exists in Built-in fields.") try: MetadataService.knowledge_base_metadata_lock_check(dataset_id, None) - metadata = DatasetMetadata.query.filter_by(id=metadata_id).first() + metadata = db.session.query(DatasetMetadata).filter_by(id=metadata_id).first() if metadata is None: raise ValueError("Metadata not found.") old_name = metadata.name @@ -60,7 +64,9 @@ class MetadataService: metadata.updated_at = datetime.datetime.now(datetime.UTC).replace(tzinfo=None) # update related documents - dataset_metadata_bindings = DatasetMetadataBinding.query.filter_by(metadata_id=metadata_id).all() + dataset_metadata_bindings = ( + db.session.query(DatasetMetadataBinding).filter_by(metadata_id=metadata_id).all() + ) if dataset_metadata_bindings: document_ids = [binding.document_id for binding in dataset_metadata_bindings] documents = DocumentService.get_document_by_ids(document_ids) @@ -82,13 +88,15 @@ class MetadataService: lock_key = f"dataset_metadata_lock_{dataset_id}" try: MetadataService.knowledge_base_metadata_lock_check(dataset_id, None) - metadata = DatasetMetadata.query.filter_by(id=metadata_id).first() + metadata = db.session.query(DatasetMetadata).filter_by(id=metadata_id).first() if metadata is None: raise ValueError("Metadata not found.") db.session.delete(metadata) # deal related documents - dataset_metadata_bindings = DatasetMetadataBinding.query.filter_by(metadata_id=metadata_id).all() + dataset_metadata_bindings = ( + db.session.query(DatasetMetadataBinding).filter_by(metadata_id=metadata_id).all() + ) if dataset_metadata_bindings: document_ids = [binding.document_id for binding in dataset_metadata_bindings] documents = DocumentService.get_document_by_ids(document_ids) @@ -193,7 +201,7 @@ class MetadataService: db.session.add(document) db.session.commit() # deal metadata binding - DatasetMetadataBinding.query.filter_by(document_id=operation.document_id).delete() + db.session.query(DatasetMetadataBinding).filter_by(document_id=operation.document_id).delete() for metadata_value in operation.metadata_list: dataset_metadata_binding = DatasetMetadataBinding( tenant_id=current_user.current_tenant_id, @@ -230,9 +238,9 @@ class MetadataService: "id": item.get("id"), "name": item.get("name"), "type": item.get("type"), - "count": DatasetMetadataBinding.query.filter_by( - metadata_id=item.get("id"), dataset_id=dataset.id - ).count(), + "count": db.session.query(DatasetMetadataBinding) + .filter_by(metadata_id=item.get("id"), dataset_id=dataset.id) + .count(), } for item in dataset.doc_metadata or [] if item.get("id") != "built-in" diff --git a/api/services/ops_service.py b/api/services/ops_service.py index 06b4732304..792f50703e 100644 --- a/api/services/ops_service.py +++ b/api/services/ops_service.py @@ -1,5 +1,6 @@ -from typing import Optional +from typing import Any, Optional +from core.ops.entities.config_entity import BaseTracingConfig from core.ops.ops_trace_manager import OpsTraceManager, provider_config_map from extensions.ext_database import db from models.model import App, TraceAppConfig @@ -67,7 +68,14 @@ class OpsService: new_decrypt_tracing_config.update({"project_url": project_url}) except Exception: new_decrypt_tracing_config.update({"project_url": "https://www.comet.com/opik/"}) - + if tracing_provider == "weave" and ( + "project_url" not in decrypt_tracing_config or not decrypt_tracing_config.get("project_url") + ): + try: + project_url = OpsTraceManager.get_trace_config_project_url(decrypt_tracing_config, tracing_provider) + new_decrypt_tracing_config.update({"project_url": project_url}) + except Exception: + new_decrypt_tracing_config.update({"project_url": "https://wandb.ai/"}) trace_config_data.tracing_config = new_decrypt_tracing_config return trace_config_data.to_dict() @@ -80,16 +88,17 @@ class OpsService: :param tracing_config: tracing config :return: """ - if tracing_provider not in provider_config_map and tracing_provider: + try: + provider_config_map[tracing_provider] + except KeyError: return {"error": f"Invalid tracing provider: {tracing_provider}"} - config_class, other_keys = ( - provider_config_map[tracing_provider]["config_class"], - provider_config_map[tracing_provider]["other_keys"], - ) - # FIXME: ignore type error - default_config_instance = config_class(**tracing_config) # type: ignore - for key in other_keys: # type: ignore + provider_config: dict[str, Any] = provider_config_map[tracing_provider] + config_class: type[BaseTracingConfig] = provider_config["config_class"] + other_keys: list[str] = provider_config["other_keys"] + + default_config_instance: BaseTracingConfig = config_class(**tracing_config) + for key in other_keys: if key in tracing_config and tracing_config[key] == "": tracing_config[key] = getattr(default_config_instance, key, None) @@ -143,7 +152,9 @@ class OpsService: :param tracing_config: tracing config :return: """ - if tracing_provider not in provider_config_map: + try: + provider_config_map[tracing_provider] + except KeyError: raise ValueError(f"Invalid tracing provider: {tracing_provider}") # check if trace config already exists diff --git a/api/services/plugin/data_migration.py b/api/services/plugin/data_migration.py index 597585588b..1c5abfecba 100644 --- a/api/services/plugin/data_migration.py +++ b/api/services/plugin/data_migration.py @@ -86,9 +86,9 @@ limit 1000""" update_retrieval_model_sql = ", retrieval_model = :retrieval_model" params["retrieval_model"] = json.dumps(retrieval_model) - sql = f"""update {table_name} - set {provider_column_name} = - concat('{DEFAULT_PLUGIN_ID}/', {provider_column_name}, '/', {provider_column_name}) + sql = f"""update {table_name} + set {provider_column_name} = + concat('{DEFAULT_PLUGIN_ID}/', {provider_column_name}, '/', {provider_column_name}) {update_retrieval_model_sql} where id = :record_id""" conn.execute(db.text(sql), params) @@ -131,10 +131,10 @@ limit 1000""" while True: sql = f""" - SELECT id, {provider_column_name} AS provider_name + SELECT id, {provider_column_name} AS provider_name FROM {table_name} - WHERE {provider_column_name} NOT LIKE '%/%' - AND {provider_column_name} IS NOT NULL + WHERE {provider_column_name} NOT LIKE '%/%' + AND {provider_column_name} IS NOT NULL AND {provider_column_name} != '' AND id > :last_id ORDER BY id ASC @@ -183,8 +183,8 @@ limit 1000""" if batch_updates: update_sql = f""" - UPDATE {table_name} - SET {provider_column_name} = :updated_value + UPDATE {table_name} + SET {provider_column_name} = :updated_value WHERE id = :record_id """ conn.execute(db.text(update_sql), [{"updated_value": u, "record_id": r} for u, r in batch_updates]) diff --git a/api/services/plugin/dependencies_analysis.py b/api/services/plugin/dependencies_analysis.py index 07e624b4e8..830d3a4769 100644 --- a/api/services/plugin/dependencies_analysis.py +++ b/api/services/plugin/dependencies_analysis.py @@ -1,7 +1,7 @@ from configs import dify_config from core.helper import marketplace from core.plugin.entities.plugin import ModelProviderID, PluginDependency, PluginInstallationSource, ToolProviderID -from core.plugin.manager.plugin import PluginInstallationManager +from core.plugin.impl.plugin import PluginInstaller class DependenciesAnalysisService: @@ -38,7 +38,7 @@ class DependenciesAnalysisService: for dependency in dependencies: required_plugin_unique_identifiers.append(dependency.value.plugin_unique_identifier) - manager = PluginInstallationManager() + manager = PluginInstaller() # get leaked dependencies missing_plugins = manager.fetch_missing_dependencies(tenant_id, required_plugin_unique_identifiers) @@ -64,7 +64,7 @@ class DependenciesAnalysisService: Generate dependencies through the list of plugin ids """ dependencies = list(set(dependencies)) - manager = PluginInstallationManager() + manager = PluginInstaller() plugins = manager.fetch_plugin_installation_by_ids(tenant_id, dependencies) result = [] for plugin in plugins: diff --git a/api/services/plugin/endpoint_service.py b/api/services/plugin/endpoint_service.py index 35961345a8..11b8e0a3d9 100644 --- a/api/services/plugin/endpoint_service.py +++ b/api/services/plugin/endpoint_service.py @@ -1,10 +1,10 @@ -from core.plugin.manager.endpoint import PluginEndpointManager +from core.plugin.impl.endpoint import PluginEndpointClient class EndpointService: @classmethod def create_endpoint(cls, tenant_id: str, user_id: str, plugin_unique_identifier: str, name: str, settings: dict): - return PluginEndpointManager().create_endpoint( + return PluginEndpointClient().create_endpoint( tenant_id=tenant_id, user_id=user_id, plugin_unique_identifier=plugin_unique_identifier, @@ -14,7 +14,7 @@ class EndpointService: @classmethod def list_endpoints(cls, tenant_id: str, user_id: str, page: int, page_size: int): - return PluginEndpointManager().list_endpoints( + return PluginEndpointClient().list_endpoints( tenant_id=tenant_id, user_id=user_id, page=page, @@ -23,7 +23,7 @@ class EndpointService: @classmethod def list_endpoints_for_single_plugin(cls, tenant_id: str, user_id: str, plugin_id: str, page: int, page_size: int): - return PluginEndpointManager().list_endpoints_for_single_plugin( + return PluginEndpointClient().list_endpoints_for_single_plugin( tenant_id=tenant_id, user_id=user_id, plugin_id=plugin_id, @@ -33,7 +33,7 @@ class EndpointService: @classmethod def update_endpoint(cls, tenant_id: str, user_id: str, endpoint_id: str, name: str, settings: dict): - return PluginEndpointManager().update_endpoint( + return PluginEndpointClient().update_endpoint( tenant_id=tenant_id, user_id=user_id, endpoint_id=endpoint_id, @@ -43,7 +43,7 @@ class EndpointService: @classmethod def delete_endpoint(cls, tenant_id: str, user_id: str, endpoint_id: str): - return PluginEndpointManager().delete_endpoint( + return PluginEndpointClient().delete_endpoint( tenant_id=tenant_id, user_id=user_id, endpoint_id=endpoint_id, @@ -51,7 +51,7 @@ class EndpointService: @classmethod def enable_endpoint(cls, tenant_id: str, user_id: str, endpoint_id: str): - return PluginEndpointManager().enable_endpoint( + return PluginEndpointClient().enable_endpoint( tenant_id=tenant_id, user_id=user_id, endpoint_id=endpoint_id, @@ -59,7 +59,7 @@ class EndpointService: @classmethod def disable_endpoint(cls, tenant_id: str, user_id: str, endpoint_id: str): - return PluginEndpointManager().disable_endpoint( + return PluginEndpointClient().disable_endpoint( tenant_id=tenant_id, user_id=user_id, endpoint_id=endpoint_id, diff --git a/api/services/plugin/oauth_service.py b/api/services/plugin/oauth_service.py new file mode 100644 index 0000000000..461247419b --- /dev/null +++ b/api/services/plugin/oauth_service.py @@ -0,0 +1,7 @@ +from core.plugin.impl.base import BasePluginClient + + +class OAuthService(BasePluginClient): + @classmethod + def get_authorization_url(cls, tenant_id: str, user_id: str, provider_name: str) -> str: + return "1234567890" diff --git a/api/services/plugin/plugin_migration.py b/api/services/plugin/plugin_migration.py index ec9e0aa8dc..dbaaa7160e 100644 --- a/api/services/plugin/plugin_migration.py +++ b/api/services/plugin/plugin_migration.py @@ -17,7 +17,7 @@ from core.agent.entities import AgentToolEntity from core.helper import marketplace from core.plugin.entities.plugin import ModelProviderID, PluginInstallationSource, ToolProviderID from core.plugin.entities.plugin_daemon import PluginInstallTaskStatus -from core.plugin.manager.plugin import PluginInstallationManager +from core.plugin.impl.plugin import PluginInstaller from core.tools.entities.tool_entities import ToolProviderType from models.account import Tenant from models.engine import db @@ -331,7 +331,7 @@ class PluginMigration: """ Install plugins. """ - manager = PluginInstallationManager() + manager = PluginInstaller() plugins = cls.extract_unique_plugins(extracted_plugins) not_installed = [] @@ -426,7 +426,7 @@ class PluginMigration: """ Install plugins for a tenant. """ - manager = PluginInstallationManager() + manager = PluginInstaller() # download all the plugins and upload thread_pool = ThreadPoolExecutor(max_workers=10) diff --git a/api/services/plugin/plugin_service.py b/api/services/plugin/plugin_service.py index 96a07d36b9..a8b64f27db 100644 --- a/api/services/plugin/plugin_service.py +++ b/api/services/plugin/plugin_service.py @@ -17,10 +17,10 @@ from core.plugin.entities.plugin import ( PluginInstallation, PluginInstallationSource, ) -from core.plugin.entities.plugin_daemon import PluginInstallTask, PluginUploadResponse -from core.plugin.manager.asset import PluginAssetManager -from core.plugin.manager.debugging import PluginDebuggingManager -from core.plugin.manager.plugin import PluginInstallationManager +from core.plugin.entities.plugin_daemon import PluginInstallTask, PluginListResponse, PluginUploadResponse +from core.plugin.impl.asset import PluginAssetManager +from core.plugin.impl.debugging import PluginDebuggingClient +from core.plugin.impl.plugin import PluginInstaller from extensions.ext_redis import redis_client logger = logging.getLogger(__name__) @@ -91,7 +91,7 @@ class PluginService: """ get the debugging key of the tenant """ - manager = PluginDebuggingManager() + manager = PluginDebuggingClient() return manager.get_debugging_key(tenant_id) @staticmethod @@ -106,16 +106,25 @@ class PluginService: """ list all plugins of the tenant """ - manager = PluginInstallationManager() + manager = PluginInstaller() plugins = manager.list_plugins(tenant_id) return plugins + @staticmethod + def list_with_total(tenant_id: str, page: int, page_size: int) -> PluginListResponse: + """ + list all plugins of the tenant + """ + manager = PluginInstaller() + plugins = manager.list_plugins_with_total(tenant_id, page, page_size) + return plugins + @staticmethod def list_installations_from_ids(tenant_id: str, ids: Sequence[str]) -> Sequence[PluginInstallation]: """ List plugin installations from ids """ - manager = PluginInstallationManager() + manager = PluginInstaller() return manager.fetch_plugin_installation_by_ids(tenant_id, ids) @staticmethod @@ -133,7 +142,7 @@ class PluginService: """ check if the plugin unique identifier is already installed by other tenant """ - manager = PluginInstallationManager() + manager = PluginInstaller() return manager.fetch_plugin_by_identifier(tenant_id, plugin_unique_identifier) @staticmethod @@ -141,7 +150,7 @@ class PluginService: """ Fetch plugin manifest """ - manager = PluginInstallationManager() + manager = PluginInstaller() return manager.fetch_plugin_manifest(tenant_id, plugin_unique_identifier) @staticmethod @@ -149,12 +158,12 @@ class PluginService: """ Fetch plugin installation tasks """ - manager = PluginInstallationManager() + manager = PluginInstaller() return manager.fetch_plugin_installation_tasks(tenant_id, page, page_size) @staticmethod def fetch_install_task(tenant_id: str, task_id: str) -> PluginInstallTask: - manager = PluginInstallationManager() + manager = PluginInstaller() return manager.fetch_plugin_installation_task(tenant_id, task_id) @staticmethod @@ -162,7 +171,7 @@ class PluginService: """ Delete a plugin installation task """ - manager = PluginInstallationManager() + manager = PluginInstaller() return manager.delete_plugin_installation_task(tenant_id, task_id) @staticmethod @@ -172,7 +181,7 @@ class PluginService: """ Delete all plugin installation task items """ - manager = PluginInstallationManager() + manager = PluginInstaller() return manager.delete_all_plugin_installation_task_items(tenant_id) @staticmethod @@ -180,7 +189,7 @@ class PluginService: """ Delete a plugin installation task item """ - manager = PluginInstallationManager() + manager = PluginInstaller() return manager.delete_plugin_installation_task_item(tenant_id, task_id, identifier) @staticmethod @@ -190,11 +199,14 @@ class PluginService: """ Upgrade plugin with marketplace """ + if not dify_config.MARKETPLACE_ENABLED: + raise ValueError("marketplace is not enabled") + if original_plugin_unique_identifier == new_plugin_unique_identifier: raise ValueError("you should not upgrade plugin with the same plugin") # check if plugin pkg is already downloaded - manager = PluginInstallationManager() + manager = PluginInstaller() try: manager.fetch_plugin_manifest(tenant_id, new_plugin_unique_identifier) @@ -227,7 +239,7 @@ class PluginService: """ Upgrade plugin with github """ - manager = PluginInstallationManager() + manager = PluginInstaller() return manager.upgrade_plugin( tenant_id, original_plugin_unique_identifier, @@ -247,7 +259,7 @@ class PluginService: returns: plugin_unique_identifier """ - manager = PluginInstallationManager() + manager = PluginInstaller() return manager.upload_pkg(tenant_id, pkg, verify_signature) @staticmethod @@ -262,7 +274,7 @@ class PluginService: f"https://github.com/{repo}/releases/download/{version}/{package}", dify_config.PLUGIN_MAX_PACKAGE_SIZE ) - manager = PluginInstallationManager() + manager = PluginInstaller() return manager.upload_pkg( tenant_id, pkg, @@ -276,12 +288,12 @@ class PluginService: """ Upload a plugin bundle and return the dependencies. """ - manager = PluginInstallationManager() + manager = PluginInstaller() return manager.upload_bundle(tenant_id, bundle, verify_signature) @staticmethod def install_from_local_pkg(tenant_id: str, plugin_unique_identifiers: Sequence[str]): - manager = PluginInstallationManager() + manager = PluginInstaller() return manager.install_from_identifiers( tenant_id, plugin_unique_identifiers, @@ -295,7 +307,7 @@ class PluginService: Install plugin from github release package files, returns plugin_unique_identifier """ - manager = PluginInstallationManager() + manager = PluginInstaller() return manager.install_from_identifiers( tenant_id, [plugin_unique_identifier], @@ -316,7 +328,10 @@ class PluginService: """ Fetch marketplace package """ - manager = PluginInstallationManager() + if not dify_config.MARKETPLACE_ENABLED: + raise ValueError("marketplace is not enabled") + + manager = PluginInstaller() try: declaration = manager.fetch_plugin_manifest(tenant_id, plugin_unique_identifier) except Exception: @@ -333,7 +348,10 @@ class PluginService: Install plugin from marketplace package files, returns installation task id """ - manager = PluginInstallationManager() + if not dify_config.MARKETPLACE_ENABLED: + raise ValueError("marketplace is not enabled") + + manager = PluginInstaller() # check if already downloaded for plugin_unique_identifier in plugin_unique_identifiers: @@ -359,7 +377,7 @@ class PluginService: @staticmethod def uninstall(tenant_id: str, plugin_installation_id: str) -> bool: - manager = PluginInstallationManager() + manager = PluginInstaller() return manager.uninstall(tenant_id, plugin_installation_id) @staticmethod @@ -367,5 +385,5 @@ class PluginService: """ Check if the tools exist """ - manager = PluginInstallationManager() + manager = PluginInstaller() return manager.check_tools_existence(tenant_id, provider_ids) diff --git a/api/services/tag_service.py b/api/services/tag_service.py index 1fbaee96e8..74c6150b44 100644 --- a/api/services/tag_service.py +++ b/api/services/tag_service.py @@ -1,7 +1,7 @@ import uuid from typing import Optional -from flask_login import current_user # type: ignore +from flask_login import current_user from sqlalchemy import func from werkzeug.exceptions import NotFound @@ -44,6 +44,19 @@ class TagService: results = [tag_binding.target_id for tag_binding in tag_bindings] return results + @staticmethod + def get_tag_by_tag_name(tag_type: str, current_tenant_id: str, tag_name: str) -> list: + if not tag_type or not tag_name: + return [] + tags = ( + db.session.query(Tag) + .filter(Tag.name == tag_name, Tag.tenant_id == current_tenant_id, Tag.type == tag_type) + .all() + ) + if not tags: + return [] + return tags + @staticmethod def get_tags_by_target_id(tag_type: str, current_tenant_id: str, target_id: str) -> list: tags = ( @@ -62,6 +75,8 @@ class TagService: @staticmethod def save_tags(args: dict) -> Tag: + if TagService.get_tag_by_tag_name(args["type"], current_user.current_tenant_id, args["name"]): + raise ValueError("Tag name already exists") tag = Tag( id=str(uuid.uuid4()), name=args["name"], @@ -75,6 +90,8 @@ class TagService: @staticmethod def update_tags(args: dict, tag_id: str) -> Tag: + if TagService.get_tag_by_tag_name(args.get("type", ""), current_user.current_tenant_id, args.get("name", "")): + raise ValueError("Tag name already exists") tag = db.session.query(Tag).filter(Tag.id == tag_id).first() if not tag: raise NotFound("Tag not found") diff --git a/api/services/tools/builtin_tools_manage_service.py b/api/services/tools/builtin_tools_manage_service.py index 075c60842b..58a4b2f179 100644 --- a/api/services/tools/builtin_tools_manage_service.py +++ b/api/services/tools/builtin_tools_manage_service.py @@ -7,8 +7,8 @@ from sqlalchemy.orm import Session from configs import dify_config from core.helper.position_helper import is_filtered from core.model_runtime.utils.encoders import jsonable_encoder -from core.plugin.entities.plugin import GenericProviderID, ToolProviderID -from core.plugin.manager.exc import PluginDaemonClientSideError +from core.plugin.entities.plugin import ToolProviderID +from core.plugin.impl.exc import PluginDaemonClientSideError from core.tools.builtin_tool.providers._positions import BuiltinToolProviderSort from core.tools.entities.api_entities import ToolApiEntity, ToolProviderApiEntity from core.tools.errors import ToolNotFoundError, ToolProviderCredentialValidationError, ToolProviderNotFoundError @@ -290,7 +290,7 @@ class BuiltinToolManageService: def _fetch_builtin_provider(provider_name: str, tenant_id: str) -> BuiltinToolProvider | None: try: full_provider_name = provider_name - provider_id_entity = GenericProviderID(provider_name) + provider_id_entity = ToolProviderID(provider_name) provider_name = provider_id_entity.provider_name if provider_id_entity.organization != "langgenius": provider_obj = ( @@ -315,7 +315,7 @@ class BuiltinToolManageService: if provider_obj is None: return None - provider_obj.provider = GenericProviderID(provider_obj.provider).to_string() + provider_obj.provider = ToolProviderID(provider_obj.provider).to_string() return provider_obj except Exception: # it's an old provider without organization diff --git a/api/services/vector_service.py b/api/services/vector_service.py index 92422bf29d..19e37f4ee3 100644 --- a/api/services/vector_service.py +++ b/api/services/vector_service.py @@ -1,3 +1,4 @@ +import logging from typing import Optional from core.model_manager import ModelInstance, ModelManager @@ -12,21 +13,30 @@ from models.dataset import ChildChunk, Dataset, DatasetProcessRule, DocumentSegm from models.dataset import Document as DatasetDocument from services.entities.knowledge_entities.knowledge_entities import ParentMode +_logger = logging.getLogger(__name__) + class VectorService: @classmethod def create_segments_vector( cls, keywords_list: Optional[list[list[str]]], segments: list[DocumentSegment], dataset: Dataset, doc_form: str ): - documents = [] + documents: list[Document] = [] for segment in segments: if doc_form == IndexType.PARENT_CHILD_INDEX: - document = DatasetDocument.query.filter_by(id=segment.document_id).first() + dataset_document = db.session.query(DatasetDocument).filter_by(id=segment.document_id).first() + if not dataset_document: + _logger.warning( + "Expected DatasetDocument record to exist, but none was found, document_id=%s, segment_id=%s", + segment.document_id, + segment.id, + ) + continue # get the process rule processing_rule = ( db.session.query(DatasetProcessRule) - .filter(DatasetProcessRule.id == document.dataset_process_rule_id) + .filter(DatasetProcessRule.id == dataset_document.dataset_process_rule_id) .first() ) if not processing_rule: @@ -50,9 +60,11 @@ class VectorService: ) else: raise ValueError("The knowledge base index technique is not high quality!") - cls.generate_child_chunks(segment, document, dataset, embedding_model_instance, processing_rule, False) + cls.generate_child_chunks( + segment, dataset_document, dataset, embedding_model_instance, processing_rule, False + ) else: - document = Document( + rag_document = Document( page_content=segment.content, metadata={ "doc_id": segment.index_node_id, @@ -61,7 +73,7 @@ class VectorService: "dataset_id": segment.dataset_id, }, ) - documents.append(document) + documents.append(rag_document) if len(documents) > 0: index_processor = IndexProcessorFactory(doc_form).init_index_processor() index_processor.load(dataset, documents, with_keywords=True, keywords_list=keywords_list) diff --git a/api/services/webapp_auth_service.py b/api/services/webapp_auth_service.py new file mode 100644 index 0000000000..8f92b3f070 --- /dev/null +++ b/api/services/webapp_auth_service.py @@ -0,0 +1,178 @@ +import enum +import secrets +from datetime import UTC, datetime, timedelta +from typing import Any, Optional, cast + +from werkzeug.exceptions import NotFound, Unauthorized + +from configs import dify_config +from extensions.ext_database import db +from libs.helper import TokenManager +from libs.passport import PassportService +from libs.password import compare_password +from models.account import Account, AccountStatus +from models.model import App, EndUser, Site +from services.app_service import AppService +from services.enterprise.enterprise_service import EnterpriseService +from services.errors.account import AccountLoginError, AccountNotFoundError, AccountPasswordError +from tasks.mail_email_code_login import send_email_code_login_mail_task + + +class WebAppAuthType(enum.StrEnum): + """Enum for web app authentication types.""" + + PUBLIC = "public" + INTERNAL = "internal" + EXTERNAL = "external" + + +class WebAppAuthService: + """Service for web app authentication.""" + + @staticmethod + def authenticate(email: str, password: str) -> Account: + """authenticate account with email and password""" + account = db.session.query(Account).filter_by(email=email).first() + if not account: + raise AccountNotFoundError() + + if account.status == AccountStatus.BANNED.value: + raise AccountLoginError("Account is banned.") + + if account.password is None or not compare_password(password, account.password, account.password_salt): + raise AccountPasswordError("Invalid email or password.") + + return cast(Account, account) + + @classmethod + def login(cls, account: Account) -> str: + access_token = cls._get_account_jwt_token(account=account) + + return access_token + + @classmethod + def get_user_through_email(cls, email: str): + account = db.session.query(Account).filter(Account.email == email).first() + if not account: + return None + + if account.status == AccountStatus.BANNED.value: + raise Unauthorized("Account is banned.") + + return account + + @classmethod + def send_email_code_login_email( + cls, account: Optional[Account] = None, email: Optional[str] = None, language: Optional[str] = "en-US" + ): + email = account.email if account else email + if email is None: + raise ValueError("Email must be provided.") + + code = "".join([str(secrets.randbelow(exclusive_upper_bound=10)) for _ in range(6)]) + token = TokenManager.generate_token( + account=account, email=email, token_type="email_code_login", additional_data={"code": code} + ) + send_email_code_login_mail_task.delay( + language=language, + to=account.email if account else email, + code=code, + ) + + return token + + @classmethod + def get_email_code_login_data(cls, token: str) -> Optional[dict[str, Any]]: + return TokenManager.get_token_data(token, "email_code_login") + + @classmethod + def revoke_email_code_login_token(cls, token: str): + TokenManager.revoke_token(token, "email_code_login") + + @classmethod + def create_end_user(cls, app_code, email) -> EndUser: + site = db.session.query(Site).filter(Site.code == app_code).first() + if not site: + raise NotFound("Site not found.") + app_model = db.session.query(App).filter(App.id == site.app_id).first() + if not app_model: + raise NotFound("App not found.") + end_user = EndUser( + tenant_id=app_model.tenant_id, + app_id=app_model.id, + type="browser", + is_anonymous=False, + session_id=email, + name="enterpriseuser", + external_user_id="enterpriseuser", + ) + db.session.add(end_user) + db.session.commit() + + return end_user + + @classmethod + def _get_account_jwt_token(cls, account: Account) -> str: + exp_dt = datetime.now(UTC) + timedelta(hours=dify_config.ACCESS_TOKEN_EXPIRE_MINUTES * 24) + exp = int(exp_dt.timestamp()) + + payload = { + "sub": "Web API Passport", + "user_id": account.id, + "session_id": account.email, + "token_source": "webapp_login_token", + "auth_type": "internal", + "exp": exp, + } + + token: str = PassportService().issue(payload) + return token + + @classmethod + def is_app_require_permission_check( + cls, app_code: Optional[str] = None, app_id: Optional[str] = None, access_mode: Optional[str] = None + ) -> bool: + """ + Check if the app requires permission check based on its access mode. + """ + modes_requiring_permission_check = [ + "private", + "private_all", + ] + if access_mode: + return access_mode in modes_requiring_permission_check + + if not app_code and not app_id: + raise ValueError("Either app_code or app_id must be provided.") + + if app_code: + app_id = AppService.get_app_id_by_code(app_code) + if not app_id: + raise ValueError("App ID could not be determined from the provided app_code.") + + webapp_settings = EnterpriseService.WebAppAuth.get_app_access_mode_by_id(app_id) + if webapp_settings and webapp_settings.access_mode in modes_requiring_permission_check: + return True + return False + + @classmethod + def get_app_auth_type(cls, app_code: str | None = None, access_mode: str | None = None) -> WebAppAuthType: + """ + Get the authentication type for the app based on its access mode. + """ + if not app_code and not access_mode: + raise ValueError("Either app_code or access_mode must be provided.") + + if access_mode: + if access_mode == "public": + return WebAppAuthType.PUBLIC + elif access_mode in ["private", "private_all"]: + return WebAppAuthType.INTERNAL + elif access_mode == "sso_verified": + return WebAppAuthType.EXTERNAL + + if app_code: + webapp_settings = EnterpriseService.WebAppAuth.get_app_access_mode_by_code(app_code) + return cls.get_app_auth_type(access_mode=webapp_settings.access_mode) + + raise ValueError("Could not determine app authentication type.") diff --git a/api/services/website_service.py b/api/services/website_service.py index 460a637a43..6720932a3a 100644 --- a/api/services/website_service.py +++ b/api/services/website_service.py @@ -3,7 +3,7 @@ import json from typing import Any import requests -from flask_login import current_user # type: ignore +from flask_login import current_user from core.helper import encrypter from core.rag.extractor.firecrawl.firecrawl_app import FirecrawlApp @@ -173,26 +173,27 @@ class WebsiteService: return crawl_status_data @classmethod - def get_crawl_url_data(cls, job_id: str, provider: str, url: str, tenant_id: str) -> dict[Any, Any] | None: + def get_crawl_url_data(cls, job_id: str, provider: str, url: str, tenant_id: str) -> dict[str, Any] | None: credentials = ApiKeyAuthService.get_auth_credentials(tenant_id, "website", provider) # decrypt api_key api_key = encrypter.decrypt_token(tenant_id=tenant_id, token=credentials.get("config").get("api_key")) - # FIXME data is redefine too many times here, use Any to ease the type checking, fix it later - data: Any + if provider == "firecrawl": + crawl_data: list[dict[str, Any]] | None = None file_key = "website_files/" + job_id + ".txt" if storage.exists(file_key): - d = storage.load_once(file_key) - if d: - data = json.loads(d.decode("utf-8")) + stored_data = storage.load_once(file_key) + if stored_data: + crawl_data = json.loads(stored_data.decode("utf-8")) else: firecrawl_app = FirecrawlApp(api_key=api_key, base_url=credentials.get("config").get("base_url", None)) result = firecrawl_app.check_crawl_status(job_id) if result.get("status") != "completed": raise ValueError("Crawl job is not completed") - data = result.get("data") - if data: - for item in data: + crawl_data = result.get("data") + + if crawl_data: + for item in crawl_data: if item.get("source_url") == url: return dict(item) return None @@ -211,23 +212,24 @@ class WebsiteService: raise ValueError("Failed to crawl") return dict(response.json().get("data", {})) else: - api_key = encrypter.decrypt_token(tenant_id=tenant_id, token=credentials.get("config").get("api_key")) - response = requests.post( + # Get crawl status first + status_response = requests.post( "https://adaptivecrawlstatus-kir3wx7b3a-uc.a.run.app", headers={"Content-Type": "application/json", "Authorization": f"Bearer {api_key}"}, json={"taskId": job_id}, ) - data = response.json().get("data", {}) - if data.get("status") != "completed": + status_data = status_response.json().get("data", {}) + if status_data.get("status") != "completed": raise ValueError("Crawl job is not completed") - response = requests.post( + # Get processed data + data_response = requests.post( "https://adaptivecrawlstatus-kir3wx7b3a-uc.a.run.app", headers={"Content-Type": "application/json", "Authorization": f"Bearer {api_key}"}, - json={"taskId": job_id, "urls": list(data.get("processed", {}).keys())}, + json={"taskId": job_id, "urls": list(status_data.get("processed", {}).keys())}, ) - data = response.json().get("data", {}) - for item in data.get("processed", {}).values(): + processed_data = data_response.json().get("data", {}) + for item in processed_data.get("processed", {}).values(): if item.get("data", {}).get("url") == url: return dict(item.get("data", {})) return None diff --git a/api/services/workflow_app_service.py b/api/services/workflow_app_service.py index e526517b51..6b30a70372 100644 --- a/api/services/workflow_app_service.py +++ b/api/services/workflow_app_service.py @@ -4,9 +4,9 @@ from datetime import datetime from sqlalchemy import and_, func, or_, select from sqlalchemy.orm import Session +from core.workflow.entities.workflow_execution import WorkflowExecutionStatus from models import App, EndUser, WorkflowAppLog, WorkflowRun -from models.enums import CreatedByRole -from models.workflow import WorkflowRunStatus +from models.enums import CreatorUserRole class WorkflowAppService: @@ -16,7 +16,7 @@ class WorkflowAppService: session: Session, app_model: App, keyword: str | None = None, - status: WorkflowRunStatus | None = None, + status: WorkflowExecutionStatus | None = None, created_at_before: datetime | None = None, created_at_after: datetime | None = None, page: int = 1, @@ -58,7 +58,7 @@ class WorkflowAppService: stmt = stmt.outerjoin( EndUser, - and_(WorkflowRun.created_by == EndUser.id, WorkflowRun.created_by_role == CreatedByRole.END_USER), + and_(WorkflowRun.created_by == EndUser.id, WorkflowRun.created_by_role == CreatorUserRole.END_USER), ).where(or_(*keyword_conditions)) if status: diff --git a/api/services/workflow_run_service.py b/api/services/workflow_run_service.py index 8b7213eefb..483c0d3086 100644 --- a/api/services/workflow_run_service.py +++ b/api/services/workflow_run_service.py @@ -1,17 +1,21 @@ import threading +from collections.abc import Sequence from typing import Optional import contexts -from core.repository import RepositoryFactory -from core.repository.workflow_node_execution_repository import OrderConfig +from core.repositories import SQLAlchemyWorkflowNodeExecutionRepository +from core.workflow.repositories.workflow_node_execution_repository import OrderConfig from extensions.ext_database import db from libs.infinite_scroll_pagination import InfiniteScrollPagination -from models.enums import WorkflowRunTriggeredFrom -from models.model import App -from models.workflow import ( - WorkflowNodeExecution, +from models import ( + Account, + App, + EndUser, + WorkflowNodeExecutionModel, WorkflowRun, + WorkflowRunTriggeredFrom, ) +from models.workflow import WorkflowNodeExecutionTriggeredFrom class WorkflowRunService: @@ -116,7 +120,12 @@ class WorkflowRunService: return workflow_run - def get_workflow_run_node_executions(self, app_model: App, run_id: str) -> list[WorkflowNodeExecution]: + def get_workflow_run_node_executions( + self, + app_model: App, + run_id: str, + user: Account | EndUser, + ) -> Sequence[WorkflowNodeExecutionModel]: """ Get workflow run node execution list """ @@ -128,17 +137,17 @@ class WorkflowRunService: if not workflow_run: return [] - # Use the repository to get the node executions - repository = RepositoryFactory.create_workflow_node_execution_repository( - params={ - "tenant_id": app_model.tenant_id, - "app_id": app_model.id, - "session_factory": db.session.get_bind(), - } + repository = SQLAlchemyWorkflowNodeExecutionRepository( + session_factory=db.engine, + user=user, + app_id=app_model.id, + triggered_from=WorkflowNodeExecutionTriggeredFrom.WORKFLOW_RUN, ) - # Use the repository to get the node executions with ordering + # Use the repository to get the database models directly order_config = OrderConfig(order_by=["index"], order_direction="desc") - node_executions = repository.get_by_workflow_run(workflow_run_id=run_id, order_config=order_config) + workflow_node_executions = repository.get_db_models_by_workflow_run( + workflow_run_id=run_id, order_config=order_config + ) - return list(node_executions) + return workflow_node_executions diff --git a/api/services/workflow_service.py b/api/services/workflow_service.py index 63e3791147..bc213ccce6 100644 --- a/api/services/workflow_service.py +++ b/api/services/workflow_service.py @@ -10,10 +10,10 @@ from sqlalchemy.orm import Session from core.app.apps.advanced_chat.app_config_manager import AdvancedChatAppConfigManager from core.app.apps.workflow.app_config_manager import WorkflowAppConfigManager -from core.model_runtime.utils.encoders import jsonable_encoder -from core.repository import RepositoryFactory +from core.repositories import SQLAlchemyWorkflowNodeExecutionRepository from core.variables import Variable from core.workflow.entities.node_entities import NodeRunResult +from core.workflow.entities.workflow_node_execution import WorkflowNodeExecution, WorkflowNodeExecutionStatus from core.workflow.errors import WorkflowNodeRunFailedError from core.workflow.graph_engine.entities.event import InNodeEvent from core.workflow.nodes import NodeType @@ -26,13 +26,11 @@ from core.workflow.workflow_entry import WorkflowEntry from events.app_event import app_draft_workflow_was_synced, app_published_workflow_was_updated from extensions.ext_database import db from models.account import Account -from models.enums import CreatedByRole from models.model import App, AppMode from models.tools import WorkflowToolProvider from models.workflow import ( Workflow, - WorkflowNodeExecution, - WorkflowNodeExecutionStatus, + WorkflowNodeExecutionModel, WorkflowNodeExecutionTriggeredFrom, WorkflowType, ) @@ -256,7 +254,7 @@ class WorkflowService: def run_draft_workflow_node( self, app_model: App, node_id: str, user_inputs: dict, account: Account - ) -> WorkflowNodeExecution: + ) -> WorkflowNodeExecutionModel: """ Run draft workflow node """ @@ -268,31 +266,31 @@ class WorkflowService: # run draft workflow node start_at = time.perf_counter() - workflow_node_execution = self._handle_node_run_result( - getter=lambda: WorkflowEntry.single_step_run( + node_execution = self._handle_node_run_result( + invoke_node_fn=lambda: WorkflowEntry.single_step_run( workflow=draft_workflow, node_id=node_id, user_inputs=user_inputs, user_id=account.id, ), start_at=start_at, - tenant_id=app_model.tenant_id, node_id=node_id, ) - workflow_node_execution.app_id = app_model.id - workflow_node_execution.created_by = account.id - workflow_node_execution.workflow_id = draft_workflow.id - - # Use the repository to save the workflow node execution - repository = RepositoryFactory.create_workflow_node_execution_repository( - params={ - "tenant_id": app_model.tenant_id, - "app_id": app_model.id, - "session_factory": db.session.get_bind(), - } + # Set workflow_id on the NodeExecution + node_execution.workflow_id = draft_workflow.id + + # Create repository and save the node execution + repository = SQLAlchemyWorkflowNodeExecutionRepository( + session_factory=db.engine, + user=account, + app_id=app_model.id, + triggered_from=WorkflowNodeExecutionTriggeredFrom.SINGLE_STEP, ) - repository.save(workflow_node_execution) + repository.save(node_execution) + + # Convert node_execution to WorkflowNodeExecution after save + workflow_node_execution = repository.to_db_model(node_execution) return workflow_node_execution @@ -306,7 +304,7 @@ class WorkflowService: start_at = time.perf_counter() workflow_node_execution = self._handle_node_run_result( - getter=lambda: WorkflowEntry.run_free_node( + invoke_node_fn=lambda: WorkflowEntry.run_free_node( node_id=node_id, node_data=node_data, tenant_id=tenant_id, @@ -314,7 +312,6 @@ class WorkflowService: user_inputs=user_inputs, ), start_at=start_at, - tenant_id=tenant_id, node_id=node_id, ) @@ -322,21 +319,12 @@ class WorkflowService: def _handle_node_run_result( self, - getter: Callable[[], tuple[BaseNode, Generator[NodeEvent | InNodeEvent, None, None]]], + invoke_node_fn: Callable[[], tuple[BaseNode, Generator[NodeEvent | InNodeEvent, None, None]]], start_at: float, - tenant_id: str, node_id: str, ) -> WorkflowNodeExecution: - """ - Handle node run result - - :param getter: Callable[[], tuple[BaseNode, Generator[RunEvent | InNodeEvent, None, None]]] - :param start_at: float - :param tenant_id: str - :param node_id: str - """ try: - node_instance, generator = getter() + node_instance, generator = invoke_node_fn() node_run_result: NodeRunResult | None = None for event in generator: @@ -385,20 +373,21 @@ class WorkflowService: node_run_result = None error = e.error - workflow_node_execution = WorkflowNodeExecution() - workflow_node_execution.id = str(uuid4()) - workflow_node_execution.tenant_id = tenant_id - workflow_node_execution.triggered_from = WorkflowNodeExecutionTriggeredFrom.SINGLE_STEP.value - workflow_node_execution.index = 1 - workflow_node_execution.node_id = node_id - workflow_node_execution.node_type = node_instance.node_type - workflow_node_execution.title = node_instance.node_data.title - workflow_node_execution.elapsed_time = time.perf_counter() - start_at - workflow_node_execution.created_by_role = CreatedByRole.ACCOUNT.value - workflow_node_execution.created_at = datetime.now(UTC).replace(tzinfo=None) - workflow_node_execution.finished_at = datetime.now(UTC).replace(tzinfo=None) + # Create a NodeExecution domain model + node_execution = WorkflowNodeExecution( + id=str(uuid4()), + workflow_id="", # This is a single-step execution, so no workflow ID + index=1, + node_id=node_id, + node_type=node_instance.node_type, + title=node_instance.node_data.title, + elapsed_time=time.perf_counter() - start_at, + created_at=datetime.now(UTC).replace(tzinfo=None), + finished_at=datetime.now(UTC).replace(tzinfo=None), + ) + if run_succeeded and node_run_result: - # create workflow node execution + # Set inputs, process_data, and outputs as dictionaries (not JSON strings) inputs = WorkflowEntry.handle_special_values(node_run_result.inputs) if node_run_result.inputs else None process_data = ( WorkflowEntry.handle_special_values(node_run_result.process_data) @@ -407,23 +396,23 @@ class WorkflowService: ) outputs = WorkflowEntry.handle_special_values(node_run_result.outputs) if node_run_result.outputs else None - workflow_node_execution.inputs = json.dumps(inputs) - workflow_node_execution.process_data = json.dumps(process_data) - workflow_node_execution.outputs = json.dumps(outputs) - workflow_node_execution.execution_metadata = ( - json.dumps(jsonable_encoder(node_run_result.metadata)) if node_run_result.metadata else None - ) + node_execution.inputs = inputs + node_execution.process_data = process_data + node_execution.outputs = outputs + node_execution.metadata = node_run_result.metadata + + # Map status from WorkflowNodeExecutionStatus to NodeExecutionStatus if node_run_result.status == WorkflowNodeExecutionStatus.SUCCEEDED: - workflow_node_execution.status = WorkflowNodeExecutionStatus.SUCCEEDED.value + node_execution.status = WorkflowNodeExecutionStatus.SUCCEEDED elif node_run_result.status == WorkflowNodeExecutionStatus.EXCEPTION: - workflow_node_execution.status = WorkflowNodeExecutionStatus.EXCEPTION.value - workflow_node_execution.error = node_run_result.error + node_execution.status = WorkflowNodeExecutionStatus.EXCEPTION + node_execution.error = node_run_result.error else: - # create workflow node execution - workflow_node_execution.status = WorkflowNodeExecutionStatus.FAILED.value - workflow_node_execution.error = error + # Set failed status and error + node_execution.status = WorkflowNodeExecutionStatus.FAILED + node_execution.error = error - return workflow_node_execution + return node_execution def convert_to_workflow(self, app_model: App, account: Account, args: dict) -> App: """ @@ -518,11 +507,11 @@ class WorkflowService: raise DraftWorkflowDeletionError("Cannot delete draft workflow versions") # Check if this workflow is currently referenced by an app - stmt = select(App).where(App.workflow_id == workflow_id) - app = session.scalar(stmt) + app_stmt = select(App).where(App.workflow_id == workflow_id) + app = session.scalar(app_stmt) if app: # Cannot delete a workflow that's currently in use by an app - raise WorkflowInUseError(f"Cannot delete workflow that is currently in use by app '{app.name}'") + raise WorkflowInUseError(f"Cannot delete workflow that is currently in use by app '{app.id}'") # Don't use workflow.tool_published as it's not accurate for specific workflow versions # Check if there's a tool provider using this specific workflow version diff --git a/api/services/workspace_service.py b/api/services/workspace_service.py index e012fd4296..125e0c1b1e 100644 --- a/api/services/workspace_service.py +++ b/api/services/workspace_service.py @@ -1,4 +1,4 @@ -from flask_login import current_user # type: ignore +from flask_login import current_user from configs import dify_config from extensions.ext_database import db diff --git a/api/tasks/add_document_to_index_task.py b/api/tasks/add_document_to_index_task.py index be88881efc..75d648e1b7 100644 --- a/api/tasks/add_document_to_index_task.py +++ b/api/tasks/add_document_to_index_task.py @@ -111,7 +111,7 @@ def add_document_to_index_task(dataset_document_id: str): logging.exception("add document to index failed") dataset_document.enabled = False dataset_document.disabled_at = datetime.datetime.now(datetime.UTC).replace(tzinfo=None) - dataset_document.status = "error" + dataset_document.indexing_status = "error" dataset_document.error = str(e) db.session.commit() finally: diff --git a/api/tasks/batch_create_segment_to_index_task.py b/api/tasks/batch_create_segment_to_index_task.py index f32bc4f187..51b6343fdc 100644 --- a/api/tasks/batch_create_segment_to_index_task.py +++ b/api/tasks/batch_create_segment_to_index_task.py @@ -5,7 +5,7 @@ import uuid import click from celery import shared_task # type: ignore -from sqlalchemy import func, select +from sqlalchemy import func from sqlalchemy.orm import Session from core.model_manager import ModelManager @@ -68,11 +68,6 @@ def batch_create_segment_to_index_task( model_type=ModelType.TEXT_EMBEDDING, model=dataset.embedding_model, ) - word_count_change = 0 - segments_to_insert: list[str] = [] - max_position_stmt = select(func.max(DocumentSegment.position)).where( - DocumentSegment.document_id == dataset_document.id - ) word_count_change = 0 if embedding_model: tokens_list = embedding_model.get_text_embedding_num_tokens( diff --git a/api/tasks/create_segment_to_index_task.py b/api/tasks/create_segment_to_index_task.py index 4500b2a44b..a3f811faa1 100644 --- a/api/tasks/create_segment_to_index_task.py +++ b/api/tasks/create_segment_to_index_task.py @@ -41,7 +41,7 @@ def create_segment_to_index_task(segment_id: str, keywords: Optional[list[str]] DocumentSegment.status: "indexing", DocumentSegment.indexing_at: datetime.datetime.now(datetime.UTC).replace(tzinfo=None), } - DocumentSegment.query.filter_by(id=segment.id).update(update_params) + db.session.query(DocumentSegment).filter_by(id=segment.id).update(update_params) db.session.commit() document = Document( page_content=segment.content, @@ -78,7 +78,7 @@ def create_segment_to_index_task(segment_id: str, keywords: Optional[list[str]] DocumentSegment.status: "completed", DocumentSegment.completed_at: datetime.datetime.now(datetime.UTC).replace(tzinfo=None), } - DocumentSegment.query.filter_by(id=segment.id).update(update_params) + db.session.query(DocumentSegment).filter_by(id=segment.id).update(update_params) db.session.commit() end_at = time.perf_counter() diff --git a/api/tasks/deal_dataset_vector_index_task.py b/api/tasks/deal_dataset_vector_index_task.py index 075453e283..a27207f2f1 100644 --- a/api/tasks/deal_dataset_vector_index_task.py +++ b/api/tasks/deal_dataset_vector_index_task.py @@ -24,7 +24,7 @@ def deal_dataset_vector_index_task(dataset_id: str, action: str): start_at = time.perf_counter() try: - dataset = Dataset.query.filter_by(id=dataset_id).first() + dataset = db.session.query(Dataset).filter_by(id=dataset_id).first() if not dataset: raise Exception("Dataset not found") diff --git a/api/tasks/document_indexing_sync_task.py b/api/tasks/document_indexing_sync_task.py index 2e68dcb0fb..b4848be192 100644 --- a/api/tasks/document_indexing_sync_task.py +++ b/api/tasks/document_indexing_sync_task.py @@ -44,14 +44,18 @@ def document_indexing_sync_task(dataset_id: str, document_id: str): page_id = data_source_info["notion_page_id"] page_type = data_source_info["type"] page_edited_time = data_source_info["last_edited_time"] - data_source_binding = DataSourceOauthBinding.query.filter( - db.and_( - DataSourceOauthBinding.tenant_id == document.tenant_id, - DataSourceOauthBinding.provider == "notion", - DataSourceOauthBinding.disabled == False, - DataSourceOauthBinding.source_info["workspace_id"] == f'"{workspace_id}"', + data_source_binding = ( + db.session.query(DataSourceOauthBinding) + .filter( + db.and_( + DataSourceOauthBinding.tenant_id == document.tenant_id, + DataSourceOauthBinding.provider == "notion", + DataSourceOauthBinding.disabled == False, + DataSourceOauthBinding.source_info["workspace_id"] == f'"{workspace_id}"', + ) ) - ).first() + .first() + ) if not data_source_binding: raise ValueError("Data source binding not found.") @@ -110,4 +114,4 @@ def document_indexing_sync_task(dataset_id: str, document_id: str): except DocumentIsPausedError as ex: logging.info(click.style(str(ex), fg="yellow")) except Exception: - pass + logging.exception("document_indexing_sync_task failed, document_id: {}".format(document_id)) diff --git a/api/tasks/document_indexing_task.py b/api/tasks/document_indexing_task.py index ee470d44e8..55cac6a9af 100644 --- a/api/tasks/document_indexing_task.py +++ b/api/tasks/document_indexing_task.py @@ -81,6 +81,6 @@ def document_indexing_task(dataset_id: str, document_ids: list): except DocumentIsPausedError as ex: logging.info(click.style(str(ex), fg="yellow")) except Exception: - pass + logging.exception("Document indexing task failed, dataset_id: {}".format(dataset_id)) finally: db.session.close() diff --git a/api/tasks/document_indexing_update_task.py b/api/tasks/document_indexing_update_task.py index b9ed11a8da..167b928f5d 100644 --- a/api/tasks/document_indexing_update_task.py +++ b/api/tasks/document_indexing_update_task.py @@ -73,6 +73,6 @@ def document_indexing_update_task(dataset_id: str, document_id: str): except DocumentIsPausedError as ex: logging.info(click.style(str(ex), fg="yellow")) except Exception: - pass + logging.exception("document_indexing_update_task failed, document_id: {}".format(document_id)) finally: db.session.close() diff --git a/api/tasks/duplicate_document_indexing_task.py b/api/tasks/duplicate_document_indexing_task.py index 100fc257ce..a6c93e110e 100644 --- a/api/tasks/duplicate_document_indexing_task.py +++ b/api/tasks/duplicate_document_indexing_task.py @@ -99,6 +99,6 @@ def duplicate_document_indexing_task(dataset_id: str, document_ids: list): except DocumentIsPausedError as ex: logging.info(click.style(str(ex), fg="yellow")) except Exception: - pass + logging.exception("duplicate_document_indexing_task failed, dataset_id: {}".format(dataset_id)) finally: db.session.close() diff --git a/api/tasks/mail_email_code_login.py b/api/tasks/mail_email_code_login.py index 5dc935548f..ddad331725 100644 --- a/api/tasks/mail_email_code_login.py +++ b/api/tasks/mail_email_code_login.py @@ -6,6 +6,7 @@ from celery import shared_task # type: ignore from flask import render_template from extensions.ext_mail import mail +from services.feature_service import FeatureService @shared_task(queue="mail") @@ -25,10 +26,24 @@ def send_email_code_login_mail_task(language: str, to: str, code: str): # send email code login mail using different languages try: if language == "zh-Hans": - html_content = render_template("email_code_login_mail_template_zh-CN.html", to=to, code=code) + template = "email_code_login_mail_template_zh-CN.html" + system_features = FeatureService.get_system_features() + if system_features.branding.enabled: + application_title = system_features.branding.application_title + template = "without-brand/email_code_login_mail_template_zh-CN.html" + html_content = render_template(template, to=to, code=code, application_title=application_title) + else: + html_content = render_template(template, to=to, code=code) mail.send(to=to, subject="邮箱验证码", html=html_content) else: - html_content = render_template("email_code_login_mail_template_en-US.html", to=to, code=code) + template = "email_code_login_mail_template_en-US.html" + system_features = FeatureService.get_system_features() + if system_features.branding.enabled: + application_title = system_features.branding.application_title + template = "without-brand/email_code_login_mail_template_en-US.html" + html_content = render_template(template, to=to, code=code, application_title=application_title) + else: + html_content = render_template(template, to=to, code=code) mail.send(to=to, subject="Email Code", html=html_content) end_at = time.perf_counter() diff --git a/api/tasks/mail_enterprise_task.py b/api/tasks/mail_enterprise_task.py new file mode 100644 index 0000000000..b9d8fd55df --- /dev/null +++ b/api/tasks/mail_enterprise_task.py @@ -0,0 +1,33 @@ +import logging +import time + +import click +from celery import shared_task # type: ignore +from flask import render_template_string + +from extensions.ext_mail import mail + + +@shared_task(queue="mail") +def send_enterprise_email_task(to, subject, body, substitutions): + if not mail.is_inited(): + return + + logging.info(click.style("Start enterprise mail to {} with subject {}".format(to, subject), fg="green")) + start_at = time.perf_counter() + + try: + html_content = render_template_string(body, **substitutions) + + if isinstance(to, list): + for t in to: + mail.send(to=t, subject=subject, html=html_content) + else: + mail.send(to=to, subject=subject, html=html_content) + + end_at = time.perf_counter() + logging.info( + click.style("Send enterprise mail to {} succeeded: latency: {}".format(to, end_at - start_at), fg="green") + ) + except Exception: + logging.exception("Send enterprise mail to {} failed".format(to)) diff --git a/api/tasks/mail_invite_member_task.py b/api/tasks/mail_invite_member_task.py index 3094527fd4..7ca85c7f2d 100644 --- a/api/tasks/mail_invite_member_task.py +++ b/api/tasks/mail_invite_member_task.py @@ -7,6 +7,7 @@ from flask import render_template from configs import dify_config from extensions.ext_mail import mail +from services.feature_service import FeatureService @shared_task(queue="mail") @@ -33,23 +34,45 @@ def send_invite_member_mail_task(language: str, to: str, token: str, inviter_nam try: url = f"{dify_config.CONSOLE_WEB_URL}/activate?token={token}" if language == "zh-Hans": - html_content = render_template( - "invite_member_mail_template_zh-CN.html", - to=to, - inviter_name=inviter_name, - workspace_name=workspace_name, - url=url, - ) - mail.send(to=to, subject="立即加入 Dify 工作空间", html=html_content) + template = "invite_member_mail_template_zh-CN.html" + system_features = FeatureService.get_system_features() + if system_features.branding.enabled: + application_title = system_features.branding.application_title + template = "without-brand/invite_member_mail_template_zh-CN.html" + html_content = render_template( + template, + to=to, + inviter_name=inviter_name, + workspace_name=workspace_name, + url=url, + application_title=application_title, + ) + mail.send(to=to, subject=f"立即加入 {application_title} 工作空间", html=html_content) + else: + html_content = render_template( + template, to=to, inviter_name=inviter_name, workspace_name=workspace_name, url=url + ) + mail.send(to=to, subject="立即加入 Dify 工作空间", html=html_content) else: - html_content = render_template( - "invite_member_mail_template_en-US.html", - to=to, - inviter_name=inviter_name, - workspace_name=workspace_name, - url=url, - ) - mail.send(to=to, subject="Join Dify Workspace Now", html=html_content) + template = "invite_member_mail_template_en-US.html" + system_features = FeatureService.get_system_features() + if system_features.branding.enabled: + application_title = system_features.branding.application_title + template = "without-brand/invite_member_mail_template_en-US.html" + html_content = render_template( + template, + to=to, + inviter_name=inviter_name, + workspace_name=workspace_name, + url=url, + application_title=application_title, + ) + mail.send(to=to, subject=f"Join {application_title} Workspace Now", html=html_content) + else: + html_content = render_template( + template, to=to, inviter_name=inviter_name, workspace_name=workspace_name, url=url + ) + mail.send(to=to, subject="Join Dify Workspace Now", html=html_content) end_at = time.perf_counter() logging.info( diff --git a/api/tasks/mail_reset_password_task.py b/api/tasks/mail_reset_password_task.py index d5be94431b..d4f4482a48 100644 --- a/api/tasks/mail_reset_password_task.py +++ b/api/tasks/mail_reset_password_task.py @@ -6,6 +6,7 @@ from celery import shared_task # type: ignore from flask import render_template from extensions.ext_mail import mail +from services.feature_service import FeatureService @shared_task(queue="mail") @@ -25,11 +26,27 @@ def send_reset_password_mail_task(language: str, to: str, code: str): # send reset password mail using different languages try: if language == "zh-Hans": - html_content = render_template("reset_password_mail_template_zh-CN.html", to=to, code=code) - mail.send(to=to, subject="设置您的 Dify 密码", html=html_content) + template = "reset_password_mail_template_zh-CN.html" + system_features = FeatureService.get_system_features() + if system_features.branding.enabled: + application_title = system_features.branding.application_title + template = "without-brand/reset_password_mail_template_zh-CN.html" + html_content = render_template(template, to=to, code=code, application_title=application_title) + mail.send(to=to, subject=f"设置您的 {application_title} 密码", html=html_content) + else: + html_content = render_template(template, to=to, code=code) + mail.send(to=to, subject="设置您的 Dify 密码", html=html_content) else: - html_content = render_template("reset_password_mail_template_en-US.html", to=to, code=code) - mail.send(to=to, subject="Set Your Dify Password", html=html_content) + template = "reset_password_mail_template_en-US.html" + system_features = FeatureService.get_system_features() + if system_features.branding.enabled: + application_title = system_features.branding.application_title + template = "without-brand/reset_password_mail_template_en-US.html" + html_content = render_template(template, to=to, code=code, application_title=application_title) + mail.send(to=to, subject=f"Set Your {application_title} Password", html=html_content) + else: + html_content = render_template(template, to=to, code=code) + mail.send(to=to, subject="Set Your Dify Password", html=html_content) end_at = time.perf_counter() logging.info( diff --git a/api/tasks/ops_trace_task.py b/api/tasks/ops_trace_task.py index 2b49e4bb23..2e77332ffe 100644 --- a/api/tasks/ops_trace_task.py +++ b/api/tasks/ops_trace_task.py @@ -44,7 +44,10 @@ def process_trace_tasks(file_info): trace_info = trace_type(**trace_info) trace_instance.trace(trace_info) logging.info(f"Processing trace tasks success, app_id: {app_id}") - except Exception: + except Exception as e: + logging.info( + f"error:\n\n\n{e}\n\n\n\n", + ) failed_key = f"{OPS_TRACE_FAILED_KEY}_{app_id}" redis_client.incr(failed_key) logging.info(f"Processing trace tasks failed, app_id: {app_id}") diff --git a/api/tasks/recover_document_indexing_task.py b/api/tasks/recover_document_indexing_task.py index eada2ff9db..e7d49c78dc 100644 --- a/api/tasks/recover_document_indexing_task.py +++ b/api/tasks/recover_document_indexing_task.py @@ -43,6 +43,6 @@ def recover_document_indexing_task(dataset_id: str, document_id: str): except DocumentIsPausedError as ex: logging.info(click.style(str(ex), fg="yellow")) except Exception: - pass + logging.exception("recover_document_indexing_task failed, document_id: {}".format(document_id)) finally: db.session.close() diff --git a/api/tasks/remove_app_and_related_data_task.py b/api/tasks/remove_app_and_related_data_task.py index cd8981abf6..d366efd6f2 100644 --- a/api/tasks/remove_app_and_related_data_task.py +++ b/api/tasks/remove_app_and_related_data_task.py @@ -7,13 +7,12 @@ from celery import shared_task # type: ignore from sqlalchemy import delete from sqlalchemy.exc import SQLAlchemyError -from core.repository import RepositoryFactory from extensions.ext_database import db -from models.dataset import AppDatasetJoin -from models.model import ( +from models import ( ApiToken, AppAnnotationHitHistory, AppAnnotationSetting, + AppDatasetJoin, AppModelConfig, Conversation, EndUser, @@ -31,7 +30,7 @@ from models.model import ( ) from models.tools import WorkflowToolProvider from models.web import PinnedConversation, SavedMessage -from models.workflow import ConversationVariable, Workflow, WorkflowAppLog, WorkflowRun +from models.workflow import ConversationVariable, Workflow, WorkflowAppLog, WorkflowNodeExecutionModel, WorkflowRun @shared_task(queue="app_deletion", bind=True, max_retries=3) @@ -188,19 +187,17 @@ def _delete_app_workflow_runs(tenant_id: str, app_id: str): def _delete_app_workflow_node_executions(tenant_id: str, app_id: str): - # Create a repository instance for WorkflowNodeExecution - repository = RepositoryFactory.create_workflow_node_execution_repository( - params={ - "tenant_id": tenant_id, - "app_id": app_id, - "session_factory": db.session.get_bind(), - } - ) - - # Use the clear method to delete all records for this tenant_id and app_id - repository.clear() + def del_workflow_node_execution(workflow_node_execution_id: str): + db.session.query(WorkflowNodeExecutionModel).filter( + WorkflowNodeExecutionModel.id == workflow_node_execution_id + ).delete(synchronize_session=False) - logging.info(click.style(f"Deleted workflow node executions for tenant {tenant_id} and app {app_id}", fg="green")) + _delete_records( + """select id from workflow_node_executions where tenant_id=:tenant_id and app_id=:app_id limit 1000""", + {"tenant_id": tenant_id, "app_id": app_id}, + del_workflow_node_execution, + "workflow node execution", + ) def _delete_app_workflow_app_logs(tenant_id: str, app_id: str): diff --git a/api/tasks/retry_document_indexing_task.py b/api/tasks/retry_document_indexing_task.py index 7e50eb9f8d..8f8c3f9d81 100644 --- a/api/tasks/retry_document_indexing_task.py +++ b/api/tasks/retry_document_indexing_task.py @@ -30,11 +30,11 @@ def retry_document_indexing_task(dataset_id: str, document_ids: list[str]): logging.info(click.style("Dataset not found: {}".format(dataset_id), fg="red")) db.session.close() return - + tenant_id = dataset.tenant_id for document_id in document_ids: retry_indexing_cache_key = "document_{}_is_retried".format(document_id) # check document limit - features = FeatureService.get_features(dataset.tenant_id) + features = FeatureService.get_features(tenant_id) try: if features.billing.enabled: vector_space = features.vector_space @@ -95,7 +95,7 @@ def retry_document_indexing_task(dataset_id: str, document_ids: list[str]): db.session.commit() logging.info(click.style(str(ex), fg="yellow")) redis_client.delete(retry_indexing_cache_key) - pass + logging.exception("retry_document_indexing_task failed, document_id: {}".format(document_id)) finally: db.session.close() end_at = time.perf_counter() diff --git a/api/tasks/sync_website_document_indexing_task.py b/api/tasks/sync_website_document_indexing_task.py index e75252edbe..dba0a39c2d 100644 --- a/api/tasks/sync_website_document_indexing_task.py +++ b/api/tasks/sync_website_document_indexing_task.py @@ -87,6 +87,6 @@ def sync_website_document_indexing_task(dataset_id: str, document_id: str): db.session.commit() logging.info(click.style(str(ex), fg="yellow")) redis_client.delete(sync_indexing_cache_key) - pass + logging.exception("sync_website_document_indexing_task failed, document_id: {}".format(document_id)) end_at = time.perf_counter() logging.info(click.style("Sync document: {} latency: {}".format(document_id, end_at - start_at), fg="green")) diff --git a/api/templates/clean_document_job_mail_template-US.html b/api/templates/clean_document_job_mail_template-US.html index 88e78f41c7..2d8f78b46a 100644 --- a/api/templates/clean_document_job_mail_template-US.html +++ b/api/templates/clean_document_job_mail_template-US.html @@ -69,7 +69,7 @@

- \ No newline at end of file + diff --git a/api/templates/delete_account_code_email_template_en-US.html b/api/templates/delete_account_code_email_template_en-US.html index 7707385334..f7f720513c 100644 --- a/api/templates/delete_account_code_email_template_en-US.html +++ b/api/templates/delete_account_code_email_template_en-US.html @@ -99,7 +99,7 @@
- Dify Logo + Dify Logo

Dify.AI Account Deletion and Verification

We received a request to delete your Dify account. To ensure the security of your account and @@ -122,4 +122,4 @@

- \ No newline at end of file + diff --git a/api/templates/delete_account_success_template_en-US.html b/api/templates/delete_account_success_template_en-US.html index c5df75cabc..5e149c3b8a 100644 --- a/api/templates/delete_account_success_template_en-US.html +++ b/api/templates/delete_account_success_template_en-US.html @@ -88,7 +88,7 @@
- Dify Logo + Dify Logo

Your Dify.AI Account Has Been Successfully Deleted

We're writing to confirm that your Dify.AI account has been successfully deleted as per your request. Your @@ -102,4 +102,4 @@

- \ No newline at end of file + diff --git a/api/templates/email_code_login_mail_template_en-US.html b/api/templates/email_code_login_mail_template_en-US.html index 066818d10c..72d679dbf1 100644 --- a/api/templates/email_code_login_mail_template_en-US.html +++ b/api/templates/email_code_login_mail_template_en-US.html @@ -61,7 +61,7 @@
- Dify Logo + Dify Logo

Your login code for Dify

Copy and paste this code, this code will only be valid for the next 5 minutes.

diff --git a/api/templates/email_code_login_mail_template_zh-CN.html b/api/templates/email_code_login_mail_template_zh-CN.html index 0c2b63a1f1..69c2d38eb6 100644 --- a/api/templates/email_code_login_mail_template_zh-CN.html +++ b/api/templates/email_code_login_mail_template_zh-CN.html @@ -61,7 +61,7 @@
- Dify Logo + Dify Logo

Dify 的登录验证码

复制并粘贴此验证码,注意验证码仅在接下来的 5 分钟内有效。

diff --git a/api/templates/invite_member_mail_template_en-US.html b/api/templates/invite_member_mail_template_en-US.html index e8bf7f5a52..fb5167f17e 100644 --- a/api/templates/invite_member_mail_template_en-US.html +++ b/api/templates/invite_member_mail_template_en-US.html @@ -54,7 +54,7 @@
- Dify Logo + Dify Logo

Dear {{ to }},

diff --git a/api/templates/invite_member_mail_template_zh-CN.html b/api/templates/invite_member_mail_template_zh-CN.html index ccd9cdbaad..b9d6619fa4 100644 --- a/api/templates/invite_member_mail_template_zh-CN.html +++ b/api/templates/invite_member_mail_template_zh-CN.html @@ -54,7 +54,7 @@
- Dify Logo + Dify Logo

尊敬的 {{ to }},

diff --git a/api/templates/queue_monitor_alert_email_template_en-US.html b/api/templates/queue_monitor_alert_email_template_en-US.html new file mode 100644 index 0000000000..2885210864 --- /dev/null +++ b/api/templates/queue_monitor_alert_email_template_en-US.html @@ -0,0 +1,129 @@ + + + + + + + + +
+
+ Dify Logo +
+

Queue Monitoring Alert

+

Our system has detected an abnormal queue status that requires your attention:

+ +
+
Queue Task Alert
+
+ Queue "{{queue_name}}" has {{queue_length}} pending tasks (Threshold: {{threshold}}) +
+
+ +
+

Recommended actions:

+

1. Check the queue processing status in the system dashboard

+

2. Verify if there are any processing bottlenecks

+

3. Consider scaling up workers if needed

+
+ +

Additional Information:

+
    +
  • Alert triggered at: {{alert_time}}
  • +
+
+ + + diff --git a/api/templates/reset_password_mail_template_en-US.html b/api/templates/reset_password_mail_template_en-US.html index d598fd191c..9372bb0c41 100644 --- a/api/templates/reset_password_mail_template_en-US.html +++ b/api/templates/reset_password_mail_template_en-US.html @@ -61,7 +61,7 @@
- Dify Logo + Dify Logo

Set your Dify password

Copy and paste this code, this code will only be valid for the next 5 minutes.

diff --git a/api/templates/reset_password_mail_template_zh-CN.html b/api/templates/reset_password_mail_template_zh-CN.html index 342c9057a7..c08de6cc5d 100644 --- a/api/templates/reset_password_mail_template_zh-CN.html +++ b/api/templates/reset_password_mail_template_zh-CN.html @@ -61,7 +61,7 @@
- Dify Logo + Dify Logo

设置您的 Dify 账户密码

复制并粘贴此验证码,注意验证码仅在接下来的 5 分钟内有效。

diff --git a/api/templates/without-brand/email_code_login_mail_template_en-US.html b/api/templates/without-brand/email_code_login_mail_template_en-US.html new file mode 100644 index 0000000000..63f34ff1d3 --- /dev/null +++ b/api/templates/without-brand/email_code_login_mail_template_en-US.html @@ -0,0 +1,70 @@ + + + + + + +
+

Your login code for {{application_title}}

+

Copy and paste this code, this code will only be valid for the next 5 minutes.

+
+ {{code}} +
+

If you didn't request a login, don't worry. You can safely ignore this email.

+
+ + diff --git a/api/templates/without-brand/email_code_login_mail_template_zh-CN.html b/api/templates/without-brand/email_code_login_mail_template_zh-CN.html new file mode 100644 index 0000000000..63d2a2ec61 --- /dev/null +++ b/api/templates/without-brand/email_code_login_mail_template_zh-CN.html @@ -0,0 +1,70 @@ + + + + + + +
+

{{application_title}} 的登录验证码

+

复制并粘贴此验证码,注意验证码仅在接下来的 5 分钟内有效。

+
+ {{code}} +
+

如果您没有请求登录,请不要担心。您可以安全地忽略此电子邮件。

+
+ + diff --git a/api/templates/without-brand/invite_member_mail_template_en-US.html b/api/templates/without-brand/invite_member_mail_template_en-US.html new file mode 100644 index 0000000000..45f2ea292c --- /dev/null +++ b/api/templates/without-brand/invite_member_mail_template_en-US.html @@ -0,0 +1,69 @@ + + + + + + +
+
+

Dear {{ to }},

+

{{ inviter_name }} is pleased to invite you to join our workspace on {{application_title}}, a platform specifically designed for LLM application development. On {{application_title}}, you can explore, create, and collaborate to build and operate AI applications.

+

Click the button below to log in to {{application_title}} and join the workspace.

+

Login Here

+
+ +
+ + + diff --git a/api/templates/without-brand/invite_member_mail_template_zh-CN.html b/api/templates/without-brand/invite_member_mail_template_zh-CN.html new file mode 100644 index 0000000000..d4f80c66f8 --- /dev/null +++ b/api/templates/without-brand/invite_member_mail_template_zh-CN.html @@ -0,0 +1,69 @@ + + + + + + + +
+
+

尊敬的 {{ to }},

+

{{ inviter_name }} 现邀请您加入我们在 {{application_title}} 的工作区,这是一个专为 LLM 应用开发而设计的平台。在 {{application_title}} 上,您可以探索、创造和合作,构建和运营 AI 应用。

+

点击下方按钮即可登录 {{application_title}} 并且加入空间。

+

在此登录

+
+ +
+ + diff --git a/api/templates/without-brand/reset_password_mail_template_en-US.html b/api/templates/without-brand/reset_password_mail_template_en-US.html new file mode 100644 index 0000000000..a285ec74a9 --- /dev/null +++ b/api/templates/without-brand/reset_password_mail_template_en-US.html @@ -0,0 +1,70 @@ + + + + + + +
+

Set your {{application_title}} password

+

Copy and paste this code, this code will only be valid for the next 5 minutes.

+
+ {{code}} +
+

If you didn't request, don't worry. You can safely ignore this email.

+
+ + diff --git a/api/templates/without-brand/reset_password_mail_template_zh-CN.html b/api/templates/without-brand/reset_password_mail_template_zh-CN.html new file mode 100644 index 0000000000..3fbaf2e892 --- /dev/null +++ b/api/templates/without-brand/reset_password_mail_template_zh-CN.html @@ -0,0 +1,70 @@ + + + + + + +
+

设置您的 {{application_title}} 账户密码

+

复制并粘贴此验证码,注意验证码仅在接下来的 5 分钟内有效。

+
+ {{code}} +
+

如果您没有请求,请不要担心。您可以安全地忽略此电子邮件。

+
+ + diff --git a/api/tests/integration_tests/.gitignore b/api/tests/integration_tests/.gitignore index 426667562b..ed9875073f 100644 --- a/api/tests/integration_tests/.gitignore +++ b/api/tests/integration_tests/.gitignore @@ -1 +1 @@ -.env.test \ No newline at end of file +.env.test diff --git a/api/tests/integration_tests/model_runtime/__mock/plugin_daemon.py b/api/tests/integration_tests/model_runtime/__mock/plugin_daemon.py index 6dfc01ab4c..e3c592b583 100644 --- a/api/tests/integration_tests/model_runtime/__mock/plugin_daemon.py +++ b/api/tests/integration_tests/model_runtime/__mock/plugin_daemon.py @@ -6,7 +6,7 @@ import pytest # import monkeypatch from _pytest.monkeypatch import MonkeyPatch -from core.plugin.manager.model import PluginModelManager +from core.plugin.impl.model import PluginModelClient from tests.integration_tests.model_runtime.__mock.plugin_model import MockModelClass @@ -23,9 +23,9 @@ def mock_plugin_daemon( def unpatch() -> None: monkeypatch.undo() - monkeypatch.setattr(PluginModelManager, "invoke_llm", MockModelClass.invoke_llm) - monkeypatch.setattr(PluginModelManager, "fetch_model_providers", MockModelClass.fetch_model_providers) - monkeypatch.setattr(PluginModelManager, "get_model_schema", MockModelClass.get_model_schema) + monkeypatch.setattr(PluginModelClient, "invoke_llm", MockModelClass.invoke_llm) + monkeypatch.setattr(PluginModelClient, "fetch_model_providers", MockModelClass.fetch_model_providers) + monkeypatch.setattr(PluginModelClient, "get_model_schema", MockModelClass.get_model_schema) return unpatch diff --git a/api/tests/integration_tests/model_runtime/__mock/plugin_model.py b/api/tests/integration_tests/model_runtime/__mock/plugin_model.py index 50913662e2..d699866fb4 100644 --- a/api/tests/integration_tests/model_runtime/__mock/plugin_model.py +++ b/api/tests/integration_tests/model_runtime/__mock/plugin_model.py @@ -19,10 +19,10 @@ from core.model_runtime.entities.model_entities import ( ) from core.model_runtime.entities.provider_entities import ConfigurateMethod, ProviderEntity from core.plugin.entities.plugin_daemon import PluginModelProviderEntity -from core.plugin.manager.model import PluginModelManager +from core.plugin.impl.model import PluginModelClient -class MockModelClass(PluginModelManager): +class MockModelClass(PluginModelClient): def fetch_model_providers(self, tenant_id: str) -> Sequence[PluginModelProviderEntity]: """ Fetch model providers for the given tenant. @@ -232,7 +232,7 @@ class MockModelClass(PluginModelManager): ) def invoke_llm( - self: PluginModelManager, + self: PluginModelClient, *, tenant_id: str, user_id: str, diff --git a/api/tests/integration_tests/plugin/tools/test_fetch_all_tools.py b/api/tests/integration_tests/plugin/tools/test_fetch_all_tools.py index c6d836ed6d..b6d583e338 100644 --- a/api/tests/integration_tests/plugin/tools/test_fetch_all_tools.py +++ b/api/tests/integration_tests/plugin/tools/test_fetch_all_tools.py @@ -1,4 +1,4 @@ -from core.plugin.manager.tool import PluginToolManager +from core.plugin.impl.tool import PluginToolManager from tests.integration_tests.plugin.__mock.http import setup_http_mock diff --git a/api/tests/integration_tests/tools/__mock/http.py b/api/tests/integration_tests/tools/__mock/http.py index 42cf87e317..de9711ab38 100644 --- a/api/tests/integration_tests/tools/__mock/http.py +++ b/api/tests/integration_tests/tools/__mock/http.py @@ -5,6 +5,8 @@ import httpx import pytest from _pytest.monkeypatch import MonkeyPatch +from core.helper import ssrf_proxy + class MockedHttp: @staticmethod @@ -29,6 +31,6 @@ class MockedHttp: @pytest.fixture def setup_http_mock(request, monkeypatch: MonkeyPatch): - monkeypatch.setattr(httpx, "request", MockedHttp.httpx_request) + monkeypatch.setattr(ssrf_proxy, "make_request", MockedHttp.httpx_request) yield monkeypatch.undo() diff --git a/api/tests/integration_tests/tools/__mock_server/openapi_todo.py b/api/tests/integration_tests/tools/__mock_server/openapi_todo.py index 2860739f0e..83f4d70ce9 100644 --- a/api/tests/integration_tests/tools/__mock_server/openapi_todo.py +++ b/api/tests/integration_tests/tools/__mock_server/openapi_todo.py @@ -1,5 +1,5 @@ from flask import Flask, request -from flask_restful import Api, Resource # type: ignore +from flask_restful import Api, Resource app = Flask(__name__) api = Api(app) diff --git a/api/tests/integration_tests/tools/api_tool/test_api_tool.py b/api/tests/integration_tests/tools/api_tool/test_api_tool.py index 9acc94e110..7c1a200c8f 100644 --- a/api/tests/integration_tests/tools/api_tool/test_api_tool.py +++ b/api/tests/integration_tests/tools/api_tool/test_api_tool.py @@ -34,10 +34,11 @@ parameters = { def test_api_tool(setup_http_mock): tool = ApiTool( entity=ToolEntity( - identity=ToolIdentity(provider="", author="", name="", label=I18nObject()), + identity=ToolIdentity(provider="", author="", name="", label=I18nObject(en_US="test tool")), ), api_bundle=ApiToolBundle(**tool_bundle), runtime=ToolRuntime(tenant_id="", credentials={"auth_type": "none"}), + provider_id="test_tool", ) headers = tool.assembling_request(parameters) response = tool.do_http_request(tool.api_bundle.server_url, tool.api_bundle.method, headers, parameters) diff --git a/api/tests/integration_tests/vdb/__mock/huaweicloudvectordb.py b/api/tests/integration_tests/vdb/__mock/huaweicloudvectordb.py index e1aba4e2c1..9706c52455 100644 --- a/api/tests/integration_tests/vdb/__mock/huaweicloudvectordb.py +++ b/api/tests/integration_tests/vdb/__mock/huaweicloudvectordb.py @@ -2,9 +2,10 @@ import os import pytest from _pytest.monkeypatch import MonkeyPatch -from api.core.rag.datasource.vdb.field import Field from elasticsearch import Elasticsearch +from core.rag.datasource.vdb.field import Field + class MockIndicesClient: def __init__(self): diff --git a/api/tests/integration_tests/vdb/oceanbase/check_oceanbase_ready.py b/api/tests/integration_tests/vdb/oceanbase/check_oceanbase_ready.py new file mode 100644 index 0000000000..94a51292ff --- /dev/null +++ b/api/tests/integration_tests/vdb/oceanbase/check_oceanbase_ready.py @@ -0,0 +1,49 @@ +import time + +import pymysql + + +def check_oceanbase_ready() -> bool: + try: + connection = pymysql.connect( + host="localhost", + port=2881, + user="root", + password="difyai123456", + ) + affected_rows = connection.query("SELECT 1") + return affected_rows == 1 + except Exception as e: + print(f"Oceanbase is not ready. Exception: {e}") + return False + finally: + if connection: + connection.close() + + +def main(): + max_attempts = 50 + retry_interval_seconds = 2 + is_oceanbase_ready = False + for attempt in range(max_attempts): + try: + is_oceanbase_ready = check_oceanbase_ready() + except Exception as e: + print(f"Oceanbase is not ready. Exception: {e}") + is_oceanbase_ready = False + + if is_oceanbase_ready: + break + else: + print(f"Attempt {attempt + 1} failed, retry in {retry_interval_seconds} seconds...") + time.sleep(retry_interval_seconds) + + if is_oceanbase_ready: + print("Oceanbase is ready.") + else: + print(f"Oceanbase is not ready after {max_attempts} attempting checks.") + exit(1) + + +if __name__ == "__main__": + main() diff --git a/api/tests/integration_tests/vdb/oceanbase/test_oceanbase.py b/api/tests/integration_tests/vdb/oceanbase/test_oceanbase.py index ebcb134168..8fbbbe61b8 100644 --- a/api/tests/integration_tests/vdb/oceanbase/test_oceanbase.py +++ b/api/tests/integration_tests/vdb/oceanbase/test_oceanbase.py @@ -1,15 +1,11 @@ -from unittest.mock import MagicMock, patch - import pytest from core.rag.datasource.vdb.oceanbase.oceanbase_vector import ( OceanBaseVector, OceanBaseVectorConfig, ) -from tests.integration_tests.vdb.__mock.tcvectordb import setup_tcvectordb_mock from tests.integration_tests.vdb.test_vector_store import ( AbstractVectorTest, - get_example_text, setup_mock_redis, ) @@ -20,10 +16,11 @@ def oceanbase_vector(): "dify_test_collection", config=OceanBaseVectorConfig( host="127.0.0.1", - port="2881", - user="root@test", + port=2881, + user="root", database="test", - password="test", + password="difyai123456", + enable_hybrid_search=True, ), ) @@ -33,39 +30,13 @@ class OceanBaseVectorTest(AbstractVectorTest): super().__init__() self.vector = vector - def search_by_vector(self): - hits_by_vector = self.vector.search_by_vector(query_vector=self.example_embedding) - assert len(hits_by_vector) == 0 - - def search_by_full_text(self): - hits_by_full_text = self.vector.search_by_full_text(query=get_example_text()) - assert len(hits_by_full_text) == 0 - - def text_exists(self): - exist = self.vector.text_exists(self.example_doc_id) - assert exist == True - def get_ids_by_metadata_field(self): ids = self.vector.get_ids_by_metadata_field(key="document_id", value=self.example_doc_id) - assert len(ids) == 0 - - -@pytest.fixture -def setup_mock_oceanbase_client(): - with patch("core.rag.datasource.vdb.oceanbase.oceanbase_vector.ObVecClient", new_callable=MagicMock) as mock_client: - yield mock_client - - -@pytest.fixture -def setup_mock_oceanbase_vector(oceanbase_vector): - with patch.object(oceanbase_vector, "_client"): - yield oceanbase_vector + assert len(ids) == 1 def test_oceanbase_vector( setup_mock_redis, - setup_mock_oceanbase_client, - setup_mock_oceanbase_vector, oceanbase_vector, ): OceanBaseVectorTest(oceanbase_vector).run_all_tests() diff --git a/api/tests/integration_tests/vdb/opensearch/test_opensearch.py b/api/tests/integration_tests/vdb/opensearch/test_opensearch.py index 35eed75c2f..2d44dd2924 100644 --- a/api/tests/integration_tests/vdb/opensearch/test_opensearch.py +++ b/api/tests/integration_tests/vdb/opensearch/test_opensearch.py @@ -23,13 +23,70 @@ def setup_mock_redis(): ext_redis.redis_client.lock = MagicMock(return_value=mock_redis_lock) +class TestOpenSearchConfig: + def test_to_opensearch_params(self): + config = OpenSearchConfig( + host="localhost", + port=9200, + secure=True, + user="admin", + password="password", + ) + + params = config.to_opensearch_params() + + assert params["hosts"] == [{"host": "localhost", "port": 9200}] + assert params["use_ssl"] is True + assert params["verify_certs"] is True + assert params["connection_class"].__name__ == "Urllib3HttpConnection" + assert params["http_auth"] == ("admin", "password") + + @patch("boto3.Session") + @patch("core.rag.datasource.vdb.opensearch.opensearch_vector.Urllib3AWSV4SignerAuth") + def test_to_opensearch_params_with_aws_managed_iam( + self, mock_aws_signer_auth: MagicMock, mock_boto_session: MagicMock + ): + mock_credentials = MagicMock() + mock_boto_session.return_value.get_credentials.return_value = mock_credentials + + mock_auth_instance = MagicMock() + mock_aws_signer_auth.return_value = mock_auth_instance + + aws_region = "ap-southeast-2" + aws_service = "aoss" + host = f"aoss-endpoint.{aws_region}.aoss.amazonaws.com" + port = 9201 + + config = OpenSearchConfig( + host=host, + port=port, + secure=True, + auth_method="aws_managed_iam", + aws_region=aws_region, + aws_service=aws_service, + ) + + params = config.to_opensearch_params() + + assert params["hosts"] == [{"host": host, "port": port}] + assert params["use_ssl"] is True + assert params["verify_certs"] is True + assert params["connection_class"].__name__ == "Urllib3HttpConnection" + assert params["http_auth"] is mock_auth_instance + + mock_aws_signer_auth.assert_called_once_with( + credentials=mock_credentials, region=aws_region, service=aws_service + ) + assert mock_boto_session.return_value.get_credentials.called + + class TestOpenSearchVector: def setup_method(self): self.collection_name = "test_collection" self.example_doc_id = "example_doc_id" self.vector = OpenSearchVector( collection_name=self.collection_name, - config=OpenSearchConfig(host="localhost", port=9200, user="admin", password="password", secure=False), + config=OpenSearchConfig(host="localhost", port=9200, secure=False, user="admin", password="password"), ) self.vector._client = MagicMock() diff --git a/api/tests/integration_tests/vdb/pyvastbase/__init__.py b/api/tests/integration_tests/vdb/pyvastbase/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/api/tests/integration_tests/vdb/pyvastbase/test_vastbase_vector.py b/api/tests/integration_tests/vdb/pyvastbase/test_vastbase_vector.py new file mode 100644 index 0000000000..3d7873442b --- /dev/null +++ b/api/tests/integration_tests/vdb/pyvastbase/test_vastbase_vector.py @@ -0,0 +1,27 @@ +from core.rag.datasource.vdb.pyvastbase.vastbase_vector import VastbaseVector, VastbaseVectorConfig +from tests.integration_tests.vdb.test_vector_store import ( + AbstractVectorTest, + get_example_text, + setup_mock_redis, +) + + +class VastbaseVectorTest(AbstractVectorTest): + def __init__(self): + super().__init__() + self.vector = VastbaseVector( + collection_name=self.collection_name, + config=VastbaseVectorConfig( + host="localhost", + port=5434, + user="dify", + password="Difyai123456", + database="dify", + min_connection=1, + max_connection=5, + ), + ) + + +def test_vastbase_vector(setup_mock_redis): + VastbaseVectorTest().run_all_tests() diff --git a/api/tests/integration_tests/workflow/nodes/test_code.py b/api/tests/integration_tests/workflow/nodes/test_code.py index 4de985ae7c..13d78c2d83 100644 --- a/api/tests/integration_tests/workflow/nodes/test_code.py +++ b/api/tests/integration_tests/workflow/nodes/test_code.py @@ -8,6 +8,7 @@ import pytest from core.app.entities.app_invoke_entities import InvokeFrom from core.workflow.entities.node_entities import NodeRunResult from core.workflow.entities.variable_pool import VariablePool +from core.workflow.entities.workflow_node_execution import WorkflowNodeExecutionStatus from core.workflow.enums import SystemVariableKey from core.workflow.graph_engine.entities.graph import Graph from core.workflow.graph_engine.entities.graph_init_params import GraphInitParams @@ -15,7 +16,7 @@ from core.workflow.graph_engine.entities.graph_runtime_state import GraphRuntime from core.workflow.nodes.code.code_node import CodeNode from core.workflow.nodes.code.entities import CodeNodeData from models.enums import UserFrom -from models.workflow import WorkflowNodeExecutionStatus, WorkflowType +from models.workflow import WorkflowType from tests.integration_tests.workflow.nodes.__mock.code_executor import setup_code_executor_mock CODE_MAX_STRING_LENGTH = int(getenv("CODE_MAX_STRING_LENGTH", "10000")) diff --git a/api/tests/integration_tests/workflow/nodes/test_llm.py b/api/tests/integration_tests/workflow/nodes/test_llm.py index 22354df196..6aa48b1cbb 100644 --- a/api/tests/integration_tests/workflow/nodes/test_llm.py +++ b/api/tests/integration_tests/workflow/nodes/test_llm.py @@ -3,12 +3,18 @@ import os import time import uuid from collections.abc import Generator -from unittest.mock import MagicMock +from decimal import Decimal +from unittest.mock import MagicMock, patch import pytest +from app_factory import create_app +from configs import dify_config from core.app.entities.app_invoke_entities import InvokeFrom +from core.model_runtime.entities.llm_entities import LLMResult, LLMUsage +from core.model_runtime.entities.message_entities import AssistantPromptMessage from core.workflow.entities.variable_pool import VariablePool +from core.workflow.entities.workflow_node_execution import WorkflowNodeExecutionStatus from core.workflow.enums import SystemVariableKey from core.workflow.graph_engine.entities.graph import Graph from core.workflow.graph_engine.entities.graph_init_params import GraphInitParams @@ -17,14 +23,28 @@ from core.workflow.nodes.event import RunCompletedEvent from core.workflow.nodes.llm.node import LLMNode from extensions.ext_database import db from models.enums import UserFrom -from models.workflow import WorkflowNodeExecutionStatus, WorkflowType -from tests.integration_tests.workflow.nodes.__mock.model import get_mocked_fetch_model_config +from models.workflow import WorkflowType """FOR MOCK FIXTURES, DO NOT REMOVE""" from tests.integration_tests.model_runtime.__mock.plugin_daemon import setup_model_mock from tests.integration_tests.workflow.nodes.__mock.code_executor import setup_code_executor_mock +@pytest.fixture(scope="session") +def app(): + # Set up storage configuration + os.environ["STORAGE_TYPE"] = "opendal" + os.environ["OPENDAL_SCHEME"] = "fs" + os.environ["OPENDAL_FS_ROOT"] = "storage" + + # Ensure storage directory exists + os.makedirs("storage", exist_ok=True) + + app = create_app() + dify_config.LOGIN_DISABLED = True + return app + + def init_llm_node(config: dict) -> LLMNode: graph_config = { "edges": [ @@ -39,13 +59,19 @@ def init_llm_node(config: dict) -> LLMNode: graph = Graph.init(graph_config=graph_config) + # Use proper UUIDs for database compatibility + tenant_id = "9d2074fc-6f86-45a9-b09d-6ecc63b9056b" + app_id = "9d2074fc-6f86-45a9-b09d-6ecc63b9056c" + workflow_id = "9d2074fc-6f86-45a9-b09d-6ecc63b9056d" + user_id = "9d2074fc-6f86-45a9-b09d-6ecc63b9056e" + init_params = GraphInitParams( - tenant_id="1", - app_id="1", + tenant_id=tenant_id, + app_id=app_id, workflow_type=WorkflowType.WORKFLOW, - workflow_id="1", + workflow_id=workflow_id, graph_config=graph_config, - user_id="1", + user_id=user_id, user_from=UserFrom.ACCOUNT, invoke_from=InvokeFrom.DEBUGGER, call_depth=0, @@ -76,60 +102,200 @@ def init_llm_node(config: dict) -> LLMNode: return node -def test_execute_llm(setup_model_mock): - node = init_llm_node( - config={ - "id": "llm", - "data": { - "title": "123", - "type": "llm", - "model": { - "provider": "langgenius/openai/openai", - "name": "gpt-3.5-turbo", - "mode": "chat", - "completion_params": {}, +def test_execute_llm(app): + with app.app_context(): + node = init_llm_node( + config={ + "id": "llm", + "data": { + "title": "123", + "type": "llm", + "model": { + "provider": "langgenius/openai/openai", + "name": "gpt-3.5-turbo", + "mode": "chat", + "completion_params": {}, + }, + "prompt_template": [ + { + "role": "system", + "text": "you are a helpful assistant.\ntoday's weather is {{#abc.output#}}.", + }, + {"role": "user", "text": "{{#sys.query#}}"}, + ], + "memory": None, + "context": {"enabled": False}, + "vision": {"enabled": False}, }, - "prompt_template": [ - {"role": "system", "text": "you are a helpful assistant.\ntoday's weather is {{#abc.output#}}."}, - {"role": "user", "text": "{{#sys.query#}}"}, - ], - "memory": None, - "context": {"enabled": False}, - "vision": {"enabled": False}, }, - }, - ) + ) - credentials = {"openai_api_key": os.environ.get("OPENAI_API_KEY")} + credentials = {"openai_api_key": os.environ.get("OPENAI_API_KEY")} - # Mock db.session.close() - db.session.close = MagicMock() + # Create a proper LLM result with real entities + mock_usage = LLMUsage( + prompt_tokens=30, + prompt_unit_price=Decimal("0.001"), + prompt_price_unit=Decimal("1000"), + prompt_price=Decimal("0.00003"), + completion_tokens=20, + completion_unit_price=Decimal("0.002"), + completion_price_unit=Decimal("1000"), + completion_price=Decimal("0.00004"), + total_tokens=50, + total_price=Decimal("0.00007"), + currency="USD", + latency=0.5, + ) - node._fetch_model_config = get_mocked_fetch_model_config( - provider="langgenius/openai/openai", - model="gpt-3.5-turbo", - mode="chat", - credentials=credentials, - ) + mock_message = AssistantPromptMessage(content="This is a test response from the mocked LLM.") + + mock_llm_result = LLMResult( + model="gpt-3.5-turbo", + prompt_messages=[], + message=mock_message, + usage=mock_usage, + ) + + # Create a simple mock model instance that doesn't call real providers + mock_model_instance = MagicMock() + mock_model_instance.invoke_llm.return_value = mock_llm_result + + # Create a simple mock model config with required attributes + mock_model_config = MagicMock() + mock_model_config.mode = "chat" + mock_model_config.provider = "langgenius/openai/openai" + mock_model_config.model = "gpt-3.5-turbo" + mock_model_config.provider_model_bundle.configuration.tenant_id = "9d2074fc-6f86-45a9-b09d-6ecc63b9056b" + + # Mock the _fetch_model_config method + def mock_fetch_model_config_func(_node_data_model): + return mock_model_instance, mock_model_config + + # Also mock ModelManager.get_model_instance to avoid database calls + def mock_get_model_instance(_self, **kwargs): + return mock_model_instance - # execute node - result = node._run() - assert isinstance(result, Generator) + with ( + patch.object(node, "_fetch_model_config", mock_fetch_model_config_func), + patch("core.model_manager.ModelManager.get_model_instance", mock_get_model_instance), + ): + # execute node + result = node._run() + assert isinstance(result, Generator) - for item in result: - if isinstance(item, RunCompletedEvent): - assert item.run_result.status == WorkflowNodeExecutionStatus.SUCCEEDED - assert item.run_result.process_data is not None - assert item.run_result.outputs is not None - assert item.run_result.outputs.get("text") is not None - assert item.run_result.outputs.get("usage", {})["total_tokens"] > 0 + for item in result: + if isinstance(item, RunCompletedEvent): + assert item.run_result.status == WorkflowNodeExecutionStatus.SUCCEEDED + assert item.run_result.process_data is not None + assert item.run_result.outputs is not None + assert item.run_result.outputs.get("text") is not None + assert item.run_result.outputs.get("usage", {})["total_tokens"] > 0 @pytest.mark.parametrize("setup_code_executor_mock", [["none"]], indirect=True) -def test_execute_llm_with_jinja2(setup_code_executor_mock, setup_model_mock): +def test_execute_llm_with_jinja2(app, setup_code_executor_mock): """ Test execute LLM node with jinja2 """ + with app.app_context(): + node = init_llm_node( + config={ + "id": "llm", + "data": { + "title": "123", + "type": "llm", + "model": {"provider": "openai", "name": "gpt-3.5-turbo", "mode": "chat", "completion_params": {}}, + "prompt_config": { + "jinja2_variables": [ + {"variable": "sys_query", "value_selector": ["sys", "query"]}, + {"variable": "output", "value_selector": ["abc", "output"]}, + ] + }, + "prompt_template": [ + { + "role": "system", + "text": "you are a helpful assistant.\ntoday's weather is {{#abc.output#}}", + "jinja2_text": "you are a helpful assistant.\ntoday's weather is {{output}}.", + "edition_type": "jinja2", + }, + { + "role": "user", + "text": "{{#sys.query#}}", + "jinja2_text": "{{sys_query}}", + "edition_type": "basic", + }, + ], + "memory": None, + "context": {"enabled": False}, + "vision": {"enabled": False}, + }, + }, + ) + + # Mock db.session.close() + db.session.close = MagicMock() + + # Create a proper LLM result with real entities + mock_usage = LLMUsage( + prompt_tokens=30, + prompt_unit_price=Decimal("0.001"), + prompt_price_unit=Decimal("1000"), + prompt_price=Decimal("0.00003"), + completion_tokens=20, + completion_unit_price=Decimal("0.002"), + completion_price_unit=Decimal("1000"), + completion_price=Decimal("0.00004"), + total_tokens=50, + total_price=Decimal("0.00007"), + currency="USD", + latency=0.5, + ) + + mock_message = AssistantPromptMessage(content="Test response: sunny weather and what's the weather today?") + + mock_llm_result = LLMResult( + model="gpt-3.5-turbo", + prompt_messages=[], + message=mock_message, + usage=mock_usage, + ) + + # Create a simple mock model instance that doesn't call real providers + mock_model_instance = MagicMock() + mock_model_instance.invoke_llm.return_value = mock_llm_result + + # Create a simple mock model config with required attributes + mock_model_config = MagicMock() + mock_model_config.mode = "chat" + mock_model_config.provider = "openai" + mock_model_config.model = "gpt-3.5-turbo" + mock_model_config.provider_model_bundle.configuration.tenant_id = "9d2074fc-6f86-45a9-b09d-6ecc63b9056b" + + # Mock the _fetch_model_config method + def mock_fetch_model_config_func(_node_data_model): + return mock_model_instance, mock_model_config + + # Also mock ModelManager.get_model_instance to avoid database calls + def mock_get_model_instance(_self, **kwargs): + return mock_model_instance + + with ( + patch.object(node, "_fetch_model_config", mock_fetch_model_config_func), + patch("core.model_manager.ModelManager.get_model_instance", mock_get_model_instance), + ): + # execute node + result = node._run() + + for item in result: + if isinstance(item, RunCompletedEvent): + assert item.run_result.status == WorkflowNodeExecutionStatus.SUCCEEDED + assert item.run_result.process_data is not None + assert "sunny" in json.dumps(item.run_result.process_data) + assert "what's the weather today?" in json.dumps(item.run_result.process_data) + + +def test_extract_json(): node = init_llm_node( config={ "id": "llm", @@ -138,50 +304,27 @@ def test_execute_llm_with_jinja2(setup_code_executor_mock, setup_model_mock): "type": "llm", "model": {"provider": "openai", "name": "gpt-3.5-turbo", "mode": "chat", "completion_params": {}}, "prompt_config": { - "jinja2_variables": [ - {"variable": "sys_query", "value_selector": ["sys", "query"]}, - {"variable": "output", "value_selector": ["abc", "output"]}, - ] + "structured_output": { + "enabled": True, + "schema": { + "type": "object", + "properties": {"name": {"type": "string"}, "age": {"type": "number"}}, + }, + } }, - "prompt_template": [ - { - "role": "system", - "text": "you are a helpful assistant.\ntoday's weather is {{#abc.output#}}", - "jinja2_text": "you are a helpful assistant.\ntoday's weather is {{output}}.", - "edition_type": "jinja2", - }, - { - "role": "user", - "text": "{{#sys.query#}}", - "jinja2_text": "{{sys_query}}", - "edition_type": "basic", - }, - ], + "prompt_template": [{"role": "user", "text": "{{#sys.query#}}"}], "memory": None, "context": {"enabled": False}, "vision": {"enabled": False}, }, }, ) - - credentials = {"openai_api_key": os.environ.get("OPENAI_API_KEY")} - - # Mock db.session.close() - db.session.close = MagicMock() - - node._fetch_model_config = get_mocked_fetch_model_config( - provider="langgenius/openai/openai", - model="gpt-3.5-turbo", - mode="chat", - credentials=credentials, - ) - - # execute node - result = node._run() - - for item in result: - if isinstance(item, RunCompletedEvent): - assert item.run_result.status == WorkflowNodeExecutionStatus.SUCCEEDED - assert item.run_result.process_data is not None - assert "sunny" in json.dumps(item.run_result.process_data) - assert "what's the weather today?" in json.dumps(item.run_result.process_data) + llm_texts = [ + '\n\n{"name": "test", "age": 123', # resoning model (deepseek-r1) + '{"name":"test","age":123}', # json schema model (gpt-4o) + '{\n "name": "test",\n "age": 123\n}', # small model (llama-3.2-1b) + '```json\n{"name": "test", "age": 123}\n```', # json markdown (deepseek-chat) + '{"name":"test",age:123}', # without quotes (qwen-2.5-0.5b) + ] + result = {"name": "test", "age": 123} + assert all(node._parse_structured_output(item) == result for item in llm_texts) diff --git a/api/tests/integration_tests/workflow/nodes/test_parameter_extractor.py b/api/tests/integration_tests/workflow/nodes/test_parameter_extractor.py index 5c6bb82024..0df8e8b146 100644 --- a/api/tests/integration_tests/workflow/nodes/test_parameter_extractor.py +++ b/api/tests/integration_tests/workflow/nodes/test_parameter_extractor.py @@ -7,6 +7,7 @@ from unittest.mock import MagicMock from core.app.entities.app_invoke_entities import InvokeFrom from core.model_runtime.entities import AssistantPromptMessage from core.workflow.entities.variable_pool import VariablePool +from core.workflow.entities.workflow_node_execution import WorkflowNodeExecutionStatus from core.workflow.enums import SystemVariableKey from core.workflow.graph_engine.entities.graph import Graph from core.workflow.graph_engine.entities.graph_init_params import GraphInitParams @@ -17,7 +18,7 @@ from models.enums import UserFrom from tests.integration_tests.workflow.nodes.__mock.model import get_mocked_fetch_model_config """FOR MOCK FIXTURES, DO NOT REMOVE""" -from models.workflow import WorkflowNodeExecutionStatus, WorkflowType +from models.workflow import WorkflowType from tests.integration_tests.model_runtime.__mock.plugin_daemon import setup_model_mock @@ -352,7 +353,7 @@ def test_extract_json_from_tool_call(): assert result["location"] == "kawaii" -def test_chat_parameter_extractor_with_memory(setup_model_mock): +def test_chat_parameter_extractor_with_memory(setup_model_mock, monkeypatch): """ Test chat parameter extractor with memory. """ @@ -383,7 +384,8 @@ def test_chat_parameter_extractor_with_memory(setup_model_mock): mode="chat", credentials={"openai_api_key": os.environ.get("OPENAI_API_KEY")}, ) - node._fetch_memory = get_mocked_fetch_memory("customized memory") + # Test the mock before running the actual test + monkeypatch.setattr("core.workflow.nodes.llm.llm_utils.fetch_memory", get_mocked_fetch_memory("customized memory")) db.session.close = MagicMock() result = node._run() diff --git a/api/tests/integration_tests/workflow/nodes/test_template_transform.py b/api/tests/integration_tests/workflow/nodes/test_template_transform.py index 51d61a95ea..a5f2677a59 100644 --- a/api/tests/integration_tests/workflow/nodes/test_template_transform.py +++ b/api/tests/integration_tests/workflow/nodes/test_template_transform.py @@ -5,13 +5,14 @@ import pytest from core.app.entities.app_invoke_entities import InvokeFrom from core.workflow.entities.variable_pool import VariablePool +from core.workflow.entities.workflow_node_execution import WorkflowNodeExecutionStatus from core.workflow.enums import SystemVariableKey from core.workflow.graph_engine.entities.graph import Graph from core.workflow.graph_engine.entities.graph_init_params import GraphInitParams from core.workflow.graph_engine.entities.graph_runtime_state import GraphRuntimeState from core.workflow.nodes.template_transform.template_transform_node import TemplateTransformNode from models.enums import UserFrom -from models.workflow import WorkflowNodeExecutionStatus, WorkflowType +from models.workflow import WorkflowType from tests.integration_tests.workflow.nodes.__mock.code_executor import setup_code_executor_mock diff --git a/api/tests/integration_tests/workflow/nodes/test_tool.py b/api/tests/integration_tests/workflow/nodes/test_tool.py index 5a569a5983..039beedafe 100644 --- a/api/tests/integration_tests/workflow/nodes/test_tool.py +++ b/api/tests/integration_tests/workflow/nodes/test_tool.py @@ -5,6 +5,7 @@ from unittest.mock import MagicMock from core.app.entities.app_invoke_entities import InvokeFrom from core.tools.utils.configuration import ToolParameterConfigurationManager from core.workflow.entities.variable_pool import VariablePool +from core.workflow.entities.workflow_node_execution import WorkflowNodeExecutionStatus from core.workflow.enums import SystemVariableKey from core.workflow.graph_engine.entities.graph import Graph from core.workflow.graph_engine.entities.graph_init_params import GraphInitParams @@ -12,7 +13,7 @@ from core.workflow.graph_engine.entities.graph_runtime_state import GraphRuntime from core.workflow.nodes.event.event import RunCompletedEvent from core.workflow.nodes.tool.tool_node import ToolNode from models.enums import UserFrom -from models.workflow import WorkflowNodeExecutionStatus, WorkflowType +from models.workflow import WorkflowType def init_tool_node(config: dict): diff --git a/api/tests/unit_tests/.gitignore b/api/tests/unit_tests/.gitignore index 426667562b..ed9875073f 100644 --- a/api/tests/unit_tests/.gitignore +++ b/api/tests/unit_tests/.gitignore @@ -1 +1 @@ -.env.test \ No newline at end of file +.env.test diff --git a/api/tests/unit_tests/configs/test_dify_config.py b/api/tests/unit_tests/configs/test_dify_config.py index efa9ea8979..cac0a688cd 100644 --- a/api/tests/unit_tests/configs/test_dify_config.py +++ b/api/tests/unit_tests/configs/test_dify_config.py @@ -1,49 +1,28 @@ import os -from textwrap import dedent -import pytest from flask import Flask from yarl import URL from configs.app_config import DifyConfig -EXAMPLE_ENV_FILENAME = ".env" - -@pytest.fixture -def example_env_file(tmp_path, monkeypatch) -> str: - monkeypatch.chdir(tmp_path) - file_path = tmp_path.joinpath(EXAMPLE_ENV_FILENAME) - file_path.write_text( - dedent( - """ - CONSOLE_API_URL=https://example.com - CONSOLE_WEB_URL=https://example.com - HTTP_REQUEST_MAX_WRITE_TIMEOUT=30 - """ - ) - ) - return str(file_path) - - -def test_dify_config_undefined_entry(example_env_file): - # NOTE: See https://github.com/microsoft/pylance-release/issues/6099 for more details about this type error. - # load dotenv file with pydantic-settings - config = DifyConfig(_env_file=example_env_file) - - # entries not defined in app settings - with pytest.raises(TypeError): - # TypeError: 'AppSettings' object is not subscriptable - assert config["LOG_LEVEL"] == "INFO" - - -# NOTE: If there is a `.env` file in your Workspace, this test might not succeed as expected. -# This is due to `pymilvus` loading all the variables from the `.env` file into `os.environ`. -def test_dify_config(example_env_file): +def test_dify_config(monkeypatch): # clear system environment variables os.environ.clear() + + # Set environment variables using monkeypatch + monkeypatch.setenv("CONSOLE_API_URL", "https://example.com") + monkeypatch.setenv("CONSOLE_WEB_URL", "https://example.com") + monkeypatch.setenv("HTTP_REQUEST_MAX_WRITE_TIMEOUT", "30") + monkeypatch.setenv("DB_USERNAME", "postgres") + monkeypatch.setenv("DB_PASSWORD", "postgres") + monkeypatch.setenv("DB_HOST", "localhost") + monkeypatch.setenv("DB_PORT", "5432") + monkeypatch.setenv("DB_DATABASE", "dify") + monkeypatch.setenv("HTTP_REQUEST_MAX_READ_TIMEOUT", "600") + # load dotenv file with pydantic-settings - config = DifyConfig(_env_file=example_env_file) + config = DifyConfig() # constant values assert config.COMMIT_SHA == "" @@ -54,7 +33,7 @@ def test_dify_config(example_env_file): assert config.SENTRY_TRACES_SAMPLE_RATE == 1.0 # annotated field with default value - assert config.HTTP_REQUEST_MAX_READ_TIMEOUT == 60 + assert config.HTTP_REQUEST_MAX_READ_TIMEOUT == 600 # annotated field with configured value assert config.HTTP_REQUEST_MAX_WRITE_TIMEOUT == 30 @@ -64,11 +43,24 @@ def test_dify_config(example_env_file): # NOTE: If there is a `.env` file in your Workspace, this test might not succeed as expected. # This is due to `pymilvus` loading all the variables from the `.env` file into `os.environ`. -def test_flask_configs(example_env_file): +def test_flask_configs(monkeypatch): flask_app = Flask("app") # clear system environment variables os.environ.clear() - flask_app.config.from_mapping(DifyConfig(_env_file=example_env_file).model_dump()) # pyright: ignore + + # Set environment variables using monkeypatch + monkeypatch.setenv("CONSOLE_API_URL", "https://example.com") + monkeypatch.setenv("CONSOLE_WEB_URL", "https://example.com") + monkeypatch.setenv("HTTP_REQUEST_MAX_WRITE_TIMEOUT", "30") + monkeypatch.setenv("DB_USERNAME", "postgres") + monkeypatch.setenv("DB_PASSWORD", "postgres") + monkeypatch.setenv("DB_HOST", "localhost") + monkeypatch.setenv("DB_PORT", "5432") + monkeypatch.setenv("DB_DATABASE", "dify") + monkeypatch.setenv("WEB_API_CORS_ALLOW_ORIGINS", "http://127.0.0.1:3000,*") + monkeypatch.setenv("CODE_EXECUTION_ENDPOINT", "http://127.0.0.1:8194/") + + flask_app.config.from_mapping(DifyConfig().model_dump()) # pyright: ignore config = flask_app.config # configs read from pydantic-settings @@ -83,7 +75,7 @@ def test_flask_configs(example_env_file): # fallback to alias choices value as CONSOLE_API_URL assert config["FILES_URL"] == "https://example.com" - assert config["SQLALCHEMY_DATABASE_URI"] == "postgresql://postgres:@localhost:5432/dify" + assert config["SQLALCHEMY_DATABASE_URI"] == "postgresql://postgres:postgres@localhost:5432/dify" assert config["SQLALCHEMY_ENGINE_OPTIONS"] == { "connect_args": { "options": "-c timezone=UTC", @@ -96,7 +88,47 @@ def test_flask_configs(example_env_file): assert config["CONSOLE_WEB_URL"] == "https://example.com" assert config["CONSOLE_CORS_ALLOW_ORIGINS"] == ["https://example.com"] - assert config["WEB_API_CORS_ALLOW_ORIGINS"] == ["*"] - - assert str(config["CODE_EXECUTION_ENDPOINT"]) == "http://sandbox:8194/" - assert str(URL(str(config["CODE_EXECUTION_ENDPOINT"])) / "v1") == "http://sandbox:8194/v1" + assert config["WEB_API_CORS_ALLOW_ORIGINS"] == ["http://127.0.0.1:3000", "*"] + + assert str(config["CODE_EXECUTION_ENDPOINT"]) == "http://127.0.0.1:8194/" + assert str(URL(str(config["CODE_EXECUTION_ENDPOINT"])) / "v1") == "http://127.0.0.1:8194/v1" + + +def test_inner_api_config_exist(monkeypatch): + # Set environment variables using monkeypatch + monkeypatch.setenv("CONSOLE_API_URL", "https://example.com") + monkeypatch.setenv("CONSOLE_WEB_URL", "https://example.com") + monkeypatch.setenv("HTTP_REQUEST_MAX_WRITE_TIMEOUT", "30") + monkeypatch.setenv("DB_USERNAME", "postgres") + monkeypatch.setenv("DB_PASSWORD", "postgres") + monkeypatch.setenv("DB_HOST", "localhost") + monkeypatch.setenv("DB_PORT", "5432") + monkeypatch.setenv("DB_DATABASE", "dify") + monkeypatch.setenv("INNER_API_KEY", "test-inner-api-key") + + config = DifyConfig() + assert config.INNER_API is False + assert isinstance(config.INNER_API_KEY, str) + assert len(config.INNER_API_KEY) > 0 + + +def test_db_extras_options_merging(monkeypatch): + """Test that DB_EXTRAS options are properly merged with default timezone setting""" + # Set environment variables + monkeypatch.setenv("DB_USERNAME", "postgres") + monkeypatch.setenv("DB_PASSWORD", "postgres") + monkeypatch.setenv("DB_HOST", "localhost") + monkeypatch.setenv("DB_PORT", "5432") + monkeypatch.setenv("DB_DATABASE", "dify") + monkeypatch.setenv("DB_EXTRAS", "options=-c search_path=myschema") + + # Create config + config = DifyConfig() + + # Get engine options + engine_options = config.SQLALCHEMY_ENGINE_OPTIONS + + # Verify options contains both search_path and timezone + options = engine_options["connect_args"]["options"] + assert "search_path=myschema" in options + assert "timezone=UTC" in options diff --git a/api/tests/unit_tests/core/helper/test_marketplace.py b/api/tests/unit_tests/core/helper/test_marketplace.py deleted file mode 100644 index 6ccce7ac9f..0000000000 --- a/api/tests/unit_tests/core/helper/test_marketplace.py +++ /dev/null @@ -1,7 +0,0 @@ -from core.helper.marketplace import download_plugin_pkg - - -def test_download_plugin_pkg(): - pkg = download_plugin_pkg("langgenius/bing:0.0.1@e58735424d2104f208c2bd683c5142e0332045b425927067acf432b26f3d970b") - assert pkg is not None - assert len(pkg) > 0 diff --git a/api/tests/unit_tests/core/helper/test_ssrf_proxy.py b/api/tests/unit_tests/core/helper/test_ssrf_proxy.py index c688d3952b..37749f0c66 100644 --- a/api/tests/unit_tests/core/helper/test_ssrf_proxy.py +++ b/api/tests/unit_tests/core/helper/test_ssrf_proxy.py @@ -1,4 +1,4 @@ -import random +import secrets from unittest.mock import MagicMock, patch import pytest @@ -34,7 +34,7 @@ def test_retry_logic_success(mock_request): side_effects = [] for _ in range(SSRF_DEFAULT_MAX_RETRIES): - status_code = random.choice(STATUS_FORCELIST) + status_code = secrets.choice(STATUS_FORCELIST) mock_response = MagicMock() mock_response.status_code = status_code side_effects.append(mock_response) diff --git a/api/tests/unit_tests/core/prompt/test_extract_thread_messages.py b/api/tests/unit_tests/core/prompt/test_extract_thread_messages.py index ba3c1eb5e0..e3e500e310 100644 --- a/api/tests/unit_tests/core/prompt/test_extract_thread_messages.py +++ b/api/tests/unit_tests/core/prompt/test_extract_thread_messages.py @@ -4,7 +4,7 @@ from constants import UUID_NIL from core.prompt.utils.extract_thread_messages import extract_thread_messages -class TestMessage: +class MockMessage: def __init__(self, id, parent_message_id): self.id = id self.parent_message_id = parent_message_id @@ -14,7 +14,7 @@ class TestMessage: def test_extract_thread_messages_single_message(): - messages = [TestMessage(str(uuid4()), UUID_NIL)] + messages = [MockMessage(str(uuid4()), UUID_NIL)] result = extract_thread_messages(messages) assert len(result) == 1 assert result[0] == messages[0] @@ -23,11 +23,11 @@ def test_extract_thread_messages_single_message(): def test_extract_thread_messages_linear_thread(): id1, id2, id3, id4, id5 = str(uuid4()), str(uuid4()), str(uuid4()), str(uuid4()), str(uuid4()) messages = [ - TestMessage(id5, id4), - TestMessage(id4, id3), - TestMessage(id3, id2), - TestMessage(id2, id1), - TestMessage(id1, UUID_NIL), + MockMessage(id5, id4), + MockMessage(id4, id3), + MockMessage(id3, id2), + MockMessage(id2, id1), + MockMessage(id1, UUID_NIL), ] result = extract_thread_messages(messages) assert len(result) == 5 @@ -37,10 +37,10 @@ def test_extract_thread_messages_linear_thread(): def test_extract_thread_messages_branched_thread(): id1, id2, id3, id4 = str(uuid4()), str(uuid4()), str(uuid4()), str(uuid4()) messages = [ - TestMessage(id4, id2), - TestMessage(id3, id2), - TestMessage(id2, id1), - TestMessage(id1, UUID_NIL), + MockMessage(id4, id2), + MockMessage(id3, id2), + MockMessage(id2, id1), + MockMessage(id1, UUID_NIL), ] result = extract_thread_messages(messages) assert len(result) == 3 @@ -56,9 +56,9 @@ def test_extract_thread_messages_empty_list(): def test_extract_thread_messages_partially_loaded(): id0, id1, id2, id3 = str(uuid4()), str(uuid4()), str(uuid4()), str(uuid4()) messages = [ - TestMessage(id3, id2), - TestMessage(id2, id1), - TestMessage(id1, id0), + MockMessage(id3, id2), + MockMessage(id2, id1), + MockMessage(id1, id0), ] result = extract_thread_messages(messages) assert len(result) == 3 @@ -68,9 +68,9 @@ def test_extract_thread_messages_partially_loaded(): def test_extract_thread_messages_legacy_messages(): id1, id2, id3 = str(uuid4()), str(uuid4()), str(uuid4()) messages = [ - TestMessage(id3, UUID_NIL), - TestMessage(id2, UUID_NIL), - TestMessage(id1, UUID_NIL), + MockMessage(id3, UUID_NIL), + MockMessage(id2, UUID_NIL), + MockMessage(id1, UUID_NIL), ] result = extract_thread_messages(messages) assert len(result) == 3 @@ -80,11 +80,11 @@ def test_extract_thread_messages_legacy_messages(): def test_extract_thread_messages_mixed_with_legacy_messages(): id1, id2, id3, id4, id5 = str(uuid4()), str(uuid4()), str(uuid4()), str(uuid4()), str(uuid4()) messages = [ - TestMessage(id5, id4), - TestMessage(id4, id2), - TestMessage(id3, id2), - TestMessage(id2, UUID_NIL), - TestMessage(id1, UUID_NIL), + MockMessage(id5, id4), + MockMessage(id4, id2), + MockMessage(id3, id2), + MockMessage(id2, UUID_NIL), + MockMessage(id1, UUID_NIL), ] result = extract_thread_messages(messages) assert len(result) == 4 diff --git a/api/tests/unit_tests/core/rag/datasource/vdb/milvus/test_milvus.py b/api/tests/unit_tests/core/rag/datasource/vdb/milvus/test_milvus.py index bd414c88f4..48cc8a7e1c 100644 --- a/api/tests/unit_tests/core/rag/datasource/vdb/milvus/test_milvus.py +++ b/api/tests/unit_tests/core/rag/datasource/vdb/milvus/test_milvus.py @@ -1,5 +1,5 @@ import pytest -from pydantic.error_wrappers import ValidationError +from pydantic import ValidationError from core.rag.datasource.vdb.milvus.milvus_vector import MilvusConfig diff --git a/api/tests/unit_tests/core/tools/workflow_as_tool/test_tool.py b/api/tests/unit_tests/core/tools/workflow_as_tool/test_tool.py index 15a9e8e9f4..fa6fc3ba32 100644 --- a/api/tests/unit_tests/core/tools/workflow_as_tool/test_tool.py +++ b/api/tests/unit_tests/core/tools/workflow_as_tool/test_tool.py @@ -34,13 +34,13 @@ def test_workflow_tool_should_raise_tool_invoke_error_when_result_has_error_fiel # needs to patch those methods to avoid database access. monkeypatch.setattr(tool, "_get_app", lambda *args, **kwargs: None) monkeypatch.setattr(tool, "_get_workflow", lambda *args, **kwargs: None) - monkeypatch.setattr(tool, "_get_user", lambda *args, **kwargs: None) # replace `WorkflowAppGenerator.generate` 's return value. monkeypatch.setattr( "core.app.apps.workflow.app_generator.WorkflowAppGenerator.generate", lambda *args, **kwargs: {"data": {"error": "oops"}}, ) + monkeypatch.setattr("flask_login.current_user", lambda *args, **kwargs: None) with pytest.raises(ToolInvokeError) as exc_info: # WorkflowTool always returns a generator, so we need to iterate to diff --git a/api/tests/unit_tests/core/workflow/graph_engine/test_graph_engine.py b/api/tests/unit_tests/core/workflow/graph_engine/test_graph_engine.py index 2a29ad3e41..7535ec4866 100644 --- a/api/tests/unit_tests/core/workflow/graph_engine/test_graph_engine.py +++ b/api/tests/unit_tests/core/workflow/graph_engine/test_graph_engine.py @@ -4,8 +4,9 @@ import pytest from flask import Flask from core.app.entities.app_invoke_entities import InvokeFrom -from core.workflow.entities.node_entities import NodeRunMetadataKey, NodeRunResult +from core.workflow.entities.node_entities import NodeRunResult, WorkflowNodeExecutionMetadataKey from core.workflow.entities.variable_pool import VariablePool +from core.workflow.entities.workflow_node_execution import WorkflowNodeExecutionStatus from core.workflow.enums import SystemVariableKey from core.workflow.graph_engine.entities.event import ( BaseNodeEvent, @@ -25,7 +26,7 @@ from core.workflow.nodes.event import RunCompletedEvent, RunStreamChunkEvent from core.workflow.nodes.llm.node import LLMNode from core.workflow.nodes.question_classifier.question_classifier_node import QuestionClassifierNode from models.enums import UserFrom -from models.workflow import WorkflowNodeExecutionStatus, WorkflowType +from models.workflow import WorkflowType @pytest.fixture @@ -201,9 +202,9 @@ def test_run_parallel_in_workflow(mock_close, mock_remove): process_data={}, outputs={}, metadata={ - NodeRunMetadataKey.TOTAL_TOKENS: 1, - NodeRunMetadataKey.TOTAL_PRICE: 1, - NodeRunMetadataKey.CURRENCY: "USD", + WorkflowNodeExecutionMetadataKey.TOTAL_TOKENS: 1, + WorkflowNodeExecutionMetadataKey.TOTAL_PRICE: 1, + WorkflowNodeExecutionMetadataKey.CURRENCY: "USD", }, ) ) @@ -836,9 +837,9 @@ def test_condition_parallel_correct_output(mock_close, mock_remove, app): process_data={}, outputs={"class_name": "financial", "class_id": "1"}, metadata={ - NodeRunMetadataKey.TOTAL_TOKENS: 1, - NodeRunMetadataKey.TOTAL_PRICE: 1, - NodeRunMetadataKey.CURRENCY: "USD", + WorkflowNodeExecutionMetadataKey.TOTAL_TOKENS: 1, + WorkflowNodeExecutionMetadataKey.TOTAL_PRICE: 1, + WorkflowNodeExecutionMetadataKey.CURRENCY: "USD", }, edge_source_handle="1", ) @@ -852,9 +853,9 @@ def test_condition_parallel_correct_output(mock_close, mock_remove, app): process_data={}, outputs={"result": "dify 123"}, metadata={ - NodeRunMetadataKey.TOTAL_TOKENS: 1, - NodeRunMetadataKey.TOTAL_PRICE: 1, - NodeRunMetadataKey.CURRENCY: "USD", + WorkflowNodeExecutionMetadataKey.TOTAL_TOKENS: 1, + WorkflowNodeExecutionMetadataKey.TOTAL_PRICE: 1, + WorkflowNodeExecutionMetadataKey.CURRENCY: "USD", }, ) ) @@ -864,10 +865,11 @@ def test_condition_parallel_correct_output(mock_close, mock_remove, app): with patch.object(CodeNode, "_run", new=code_generator): generator = graph_engine.run() stream_content = "" - res_content = "VAT:\ndify 123" + wrong_content = ["Stamp Duty", "other"] for item in generator: if isinstance(item, NodeRunStreamChunkEvent): stream_content += f"{item.chunk_content}\n" if isinstance(item, GraphRunSucceededEvent): - assert item.outputs == {"answer": res_content} - assert stream_content == res_content + "\n" + assert item.outputs is not None + answer = item.outputs["answer"] + assert all(rc not in answer for rc in wrong_content) diff --git a/api/tests/unit_tests/core/workflow/nodes/answer/test_answer.py b/api/tests/unit_tests/core/workflow/nodes/answer/test_answer.py index 0369f3fa44..b7f78d91fa 100644 --- a/api/tests/unit_tests/core/workflow/nodes/answer/test_answer.py +++ b/api/tests/unit_tests/core/workflow/nodes/answer/test_answer.py @@ -4,6 +4,7 @@ from unittest.mock import MagicMock from core.app.entities.app_invoke_entities import InvokeFrom from core.workflow.entities.variable_pool import VariablePool +from core.workflow.entities.workflow_node_execution import WorkflowNodeExecutionStatus from core.workflow.enums import SystemVariableKey from core.workflow.graph_engine.entities.graph import Graph from core.workflow.graph_engine.entities.graph_init_params import GraphInitParams @@ -11,7 +12,7 @@ from core.workflow.graph_engine.entities.graph_runtime_state import GraphRuntime from core.workflow.nodes.answer.answer_node import AnswerNode from extensions.ext_database import db from models.enums import UserFrom -from models.workflow import WorkflowNodeExecutionStatus, WorkflowType +from models.workflow import WorkflowType def test_execute_answer(): diff --git a/api/tests/unit_tests/core/workflow/nodes/http_request/test_http_request_executor.py b/api/tests/unit_tests/core/workflow/nodes/http_request/test_http_request_executor.py index 58b910e17b..d066fc1e33 100644 --- a/api/tests/unit_tests/core/workflow/nodes/http_request/test_http_request_executor.py +++ b/api/tests/unit_tests/core/workflow/nodes/http_request/test_http_request_executor.py @@ -246,7 +246,9 @@ def test_executor_with_form_data(): assert "multipart/form-data" in executor.headers["Content-Type"] assert executor.params == [] assert executor.json is None - assert executor.files is None + # '__multipart_placeholder__' is expected when no file inputs exist, + # to ensure the request is treated as multipart/form-data by the backend. + assert executor.files == [("__multipart_placeholder__", ("", b"", "application/octet-stream"))] assert executor.content is None # Check that the form data is correctly loaded in executor.data diff --git a/api/tests/unit_tests/core/workflow/nodes/http_request/test_http_request_node.py b/api/tests/unit_tests/core/workflow/nodes/http_request/test_http_request_node.py index 2073d355f0..7fd32a4826 100644 --- a/api/tests/unit_tests/core/workflow/nodes/http_request/test_http_request_node.py +++ b/api/tests/unit_tests/core/workflow/nodes/http_request/test_http_request_node.py @@ -4,6 +4,7 @@ from core.app.entities.app_invoke_entities import InvokeFrom from core.file import File, FileTransferMethod, FileType from core.variables import ArrayFileVariable, FileVariable from core.workflow.entities.variable_pool import VariablePool +from core.workflow.entities.workflow_node_execution import WorkflowNodeExecutionStatus from core.workflow.graph_engine import Graph, GraphInitParams, GraphRuntimeState from core.workflow.nodes.answer import AnswerStreamGenerateRoute from core.workflow.nodes.end import EndStreamParam @@ -15,7 +16,7 @@ from core.workflow.nodes.http_request import ( HttpRequestNodeData, ) from models.enums import UserFrom -from models.workflow import WorkflowNodeExecutionStatus, WorkflowType +from models.workflow import WorkflowType def test_http_request_node_binary_file(monkeypatch): diff --git a/api/tests/unit_tests/core/workflow/nodes/iteration/test_iteration.py b/api/tests/unit_tests/core/workflow/nodes/iteration/test_iteration.py index 29bd4d6c6c..6d854c950d 100644 --- a/api/tests/unit_tests/core/workflow/nodes/iteration/test_iteration.py +++ b/api/tests/unit_tests/core/workflow/nodes/iteration/test_iteration.py @@ -5,6 +5,7 @@ from unittest.mock import patch from core.app.entities.app_invoke_entities import InvokeFrom from core.workflow.entities.node_entities import NodeRunResult from core.workflow.entities.variable_pool import VariablePool +from core.workflow.entities.workflow_node_execution import WorkflowNodeExecutionStatus from core.workflow.enums import SystemVariableKey from core.workflow.graph_engine.entities.graph import Graph from core.workflow.graph_engine.entities.graph_init_params import GraphInitParams @@ -14,7 +15,7 @@ from core.workflow.nodes.iteration.entities import ErrorHandleMode from core.workflow.nodes.iteration.iteration_node import IterationNode from core.workflow.nodes.template_transform.template_transform_node import TemplateTransformNode from models.enums import UserFrom -from models.workflow import WorkflowNodeExecutionStatus, WorkflowType +from models.workflow import WorkflowType def test_run(): diff --git a/api/tests/unit_tests/core/workflow/nodes/llm/test_file_saver.py b/api/tests/unit_tests/core/workflow/nodes/llm/test_file_saver.py new file mode 100644 index 0000000000..7c722660bc --- /dev/null +++ b/api/tests/unit_tests/core/workflow/nodes/llm/test_file_saver.py @@ -0,0 +1,192 @@ +import uuid +from typing import NamedTuple +from unittest import mock + +import httpx +import pytest +from sqlalchemy import Engine + +from core.file import FileTransferMethod, FileType, models +from core.helper import ssrf_proxy +from core.tools import signature +from core.tools.tool_file_manager import ToolFileManager +from core.workflow.nodes.llm.file_saver import ( + FileSaverImpl, + _extract_content_type_and_extension, + _get_extension, + _validate_extension_override, +) +from models import ToolFile + +_PNG_DATA = b"\x89PNG\r\n\x1a\n" + + +def _gen_id(): + return str(uuid.uuid4()) + + +class TestFileSaverImpl: + def test_save_binary_string(self, monkeypatch): + user_id = _gen_id() + tenant_id = _gen_id() + file_type = FileType.IMAGE + mime_type = "image/png" + mock_signed_url = "https://example.com/image.png" + mock_tool_file = ToolFile( + id=_gen_id(), + user_id=user_id, + tenant_id=tenant_id, + conversation_id=None, + file_key="test-file-key", + mimetype=mime_type, + original_url=None, + name=f"{_gen_id()}.png", + size=len(_PNG_DATA), + ) + mocked_tool_file_manager = mock.MagicMock(spec=ToolFileManager) + mocked_engine = mock.MagicMock(spec=Engine) + + mocked_tool_file_manager.create_file_by_raw.return_value = mock_tool_file + monkeypatch.setattr(FileSaverImpl, "_get_tool_file_manager", lambda _: mocked_tool_file_manager) + # Since `File.generate_url` used `ToolFileManager.sign_file` directly, we also need to patch it here. + mocked_sign_file = mock.MagicMock(spec=signature.sign_tool_file) + # Since `File.generate_url` used `signature.sign_tool_file` directly, we also need to patch it here. + monkeypatch.setattr(models, "sign_tool_file", mocked_sign_file) + mocked_sign_file.return_value = mock_signed_url + + storage_file_manager = FileSaverImpl( + user_id=user_id, + tenant_id=tenant_id, + engine_factory=mocked_engine, + ) + + file = storage_file_manager.save_binary_string(_PNG_DATA, mime_type, file_type) + assert file.tenant_id == tenant_id + assert file.type == file_type + assert file.transfer_method == FileTransferMethod.TOOL_FILE + assert file.extension == ".png" + assert file.mime_type == mime_type + assert file.size == len(_PNG_DATA) + assert file.related_id == mock_tool_file.id + + assert file.generate_url() == mock_signed_url + + mocked_tool_file_manager.create_file_by_raw.assert_called_once_with( + user_id=user_id, + tenant_id=tenant_id, + conversation_id=None, + file_binary=_PNG_DATA, + mimetype=mime_type, + ) + mocked_sign_file.assert_called_once_with(mock_tool_file.id, ".png") + + def test_save_remote_url_request_failed(self, monkeypatch): + _TEST_URL = "https://example.com/image.png" + mock_request = httpx.Request("GET", _TEST_URL) + mock_response = httpx.Response( + status_code=401, + request=mock_request, + ) + file_saver = FileSaverImpl( + user_id=_gen_id(), + tenant_id=_gen_id(), + ) + mock_get = mock.MagicMock(spec=ssrf_proxy.get, return_value=mock_response) + monkeypatch.setattr(ssrf_proxy, "get", mock_get) + + with pytest.raises(httpx.HTTPStatusError) as exc: + file_saver.save_remote_url(_TEST_URL, FileType.IMAGE) + mock_get.assert_called_once_with(_TEST_URL) + assert exc.value.response.status_code == 401 + + def test_save_remote_url_success(self, monkeypatch): + _TEST_URL = "https://example.com/image.png" + mime_type = "image/png" + user_id = _gen_id() + tenant_id = _gen_id() + + mock_request = httpx.Request("GET", _TEST_URL) + mock_response = httpx.Response( + status_code=200, + content=b"test-data", + headers={"Content-Type": mime_type}, + request=mock_request, + ) + + file_saver = FileSaverImpl(user_id=user_id, tenant_id=tenant_id) + mock_tool_file = ToolFile( + id=_gen_id(), + user_id=user_id, + tenant_id=tenant_id, + conversation_id=None, + file_key="test-file-key", + mimetype=mime_type, + original_url=None, + name=f"{_gen_id()}.png", + size=len(_PNG_DATA), + ) + mock_get = mock.MagicMock(spec=ssrf_proxy.get, return_value=mock_response) + monkeypatch.setattr(ssrf_proxy, "get", mock_get) + mock_save_binary_string = mock.MagicMock(spec=file_saver.save_binary_string, return_value=mock_tool_file) + monkeypatch.setattr(file_saver, "save_binary_string", mock_save_binary_string) + + file = file_saver.save_remote_url(_TEST_URL, FileType.IMAGE) + mock_save_binary_string.assert_called_once_with( + mock_response.content, + mime_type, + FileType.IMAGE, + extension_override=".png", + ) + assert file == mock_tool_file + + +def test_validate_extension_override(): + class TestCase(NamedTuple): + extension_override: str | None + expected: str | None + + cases = [TestCase(None, None), TestCase("", ""), ".png", ".png", ".tar.gz", ".tar.gz"] + + for valid_ext_override in [None, "", ".png", ".tar.gz"]: + assert valid_ext_override == _validate_extension_override(valid_ext_override) + + for invalid_ext_override in ["png", "tar.gz"]: + with pytest.raises(ValueError) as exc: + _validate_extension_override(invalid_ext_override) + + +class TestExtractContentTypeAndExtension: + def test_with_both_content_type_and_extension(self): + content_type, extension = _extract_content_type_and_extension("https://example.com/image.jpg", "image/png") + assert content_type == "image/png" + assert extension == ".png" + + def test_url_with_file_extension(self): + for content_type in [None, ""]: + content_type, extension = _extract_content_type_and_extension("https://example.com/image.png", content_type) + assert content_type == "image/png" + assert extension == ".png" + + def test_response_with_content_type(self): + content_type, extension = _extract_content_type_and_extension("https://example.com/image", "image/png") + assert content_type == "image/png" + assert extension == ".png" + + def test_no_content_type_and_no_extension(self): + for content_type in [None, ""]: + content_type, extension = _extract_content_type_and_extension("https://example.com/image", content_type) + assert content_type == "application/octet-stream" + assert extension == ".bin" + + +class TestGetExtension: + def test_with_extension_override(self): + mime_type = "image/png" + for override in [".jpg", ""]: + extension = _get_extension(mime_type, override) + assert extension == override + + def test_without_extension_override(self): + mime_type = "image/png" + extension = _get_extension(mime_type) + assert extension == ".png" diff --git a/api/tests/unit_tests/core/workflow/nodes/llm/test_node.py b/api/tests/unit_tests/core/workflow/nodes/llm/test_node.py index 5c3e5540c4..336c2befcc 100644 --- a/api/tests/unit_tests/core/workflow/nodes/llm/test_node.py +++ b/api/tests/unit_tests/core/workflow/nodes/llm/test_node.py @@ -1,5 +1,8 @@ +import base64 +import uuid from collections.abc import Sequence from typing import Optional +from unittest import mock import pytest @@ -22,6 +25,7 @@ from core.workflow.entities.variable_pool import VariablePool from core.workflow.graph_engine import Graph, GraphInitParams, GraphRuntimeState from core.workflow.nodes.answer import AnswerStreamGenerateRoute from core.workflow.nodes.end import EndStreamParam +from core.workflow.nodes.llm import llm_utils from core.workflow.nodes.llm.entities import ( ContextConfig, LLMNodeChatModelMessage, @@ -30,6 +34,7 @@ from core.workflow.nodes.llm.entities import ( VisionConfig, VisionConfigOptions, ) +from core.workflow.nodes.llm.file_saver import LLMFileSaver from core.workflow.nodes.llm.node import LLMNode from models.enums import UserFrom from models.provider import ProviderType @@ -49,8 +54,8 @@ class MockTokenBufferMemory: @pytest.fixture -def llm_node(): - data = LLMNodeData( +def llm_node_data() -> LLMNodeData: + return LLMNodeData( title="Test LLM", model=ModelConfig(provider="openai", name="gpt-3.5-turbo", mode="chat", completion_params={}), prompt_template=[], @@ -64,42 +69,65 @@ def llm_node(): ), ), ) + + +@pytest.fixture +def graph_init_params() -> GraphInitParams: + return GraphInitParams( + tenant_id="1", + app_id="1", + workflow_type=WorkflowType.WORKFLOW, + workflow_id="1", + graph_config={}, + user_id="1", + user_from=UserFrom.ACCOUNT, + invoke_from=InvokeFrom.SERVICE_API, + call_depth=0, + ) + + +@pytest.fixture +def graph() -> Graph: + return Graph( + root_node_id="1", + answer_stream_generate_routes=AnswerStreamGenerateRoute( + answer_dependencies={}, + answer_generate_route={}, + ), + end_stream_param=EndStreamParam( + end_dependencies={}, + end_stream_variable_selector_mapping={}, + ), + ) + + +@pytest.fixture +def graph_runtime_state() -> GraphRuntimeState: variable_pool = VariablePool( system_variables={}, user_inputs={}, ) + return GraphRuntimeState( + variable_pool=variable_pool, + start_at=0, + ) + + +@pytest.fixture +def llm_node( + llm_node_data: LLMNodeData, graph_init_params: GraphInitParams, graph: Graph, graph_runtime_state: GraphRuntimeState +) -> LLMNode: + mock_file_saver = mock.MagicMock(spec=LLMFileSaver) node = LLMNode( id="1", config={ "id": "1", - "data": data.model_dump(), + "data": llm_node_data.model_dump(), }, - graph_init_params=GraphInitParams( - tenant_id="1", - app_id="1", - workflow_type=WorkflowType.WORKFLOW, - workflow_id="1", - graph_config={}, - user_id="1", - user_from=UserFrom.ACCOUNT, - invoke_from=InvokeFrom.SERVICE_API, - call_depth=0, - ), - graph=Graph( - root_node_id="1", - answer_stream_generate_routes=AnswerStreamGenerateRoute( - answer_dependencies={}, - answer_generate_route={}, - ), - end_stream_param=EndStreamParam( - end_dependencies={}, - end_stream_variable_selector_mapping={}, - ), - ), - graph_runtime_state=GraphRuntimeState( - variable_pool=variable_pool, - start_at=0, - ), + graph_init_params=graph_init_params, + graph=graph, + graph_runtime_state=graph_runtime_state, + llm_file_saver=mock_file_saver, ) return node @@ -143,7 +171,7 @@ def model_config(): ) -def test_fetch_files_with_file_segment(llm_node): +def test_fetch_files_with_file_segment(): file = File( id="1", tenant_id="test", @@ -153,13 +181,14 @@ def test_fetch_files_with_file_segment(llm_node): related_id="1", storage_key="", ) - llm_node.graph_runtime_state.variable_pool.add(["sys", "files"], file) + variable_pool = VariablePool() + variable_pool.add(["sys", "files"], file) - result = llm_node._fetch_files(selector=["sys", "files"]) + result = llm_utils.fetch_files(variable_pool=variable_pool, selector=["sys", "files"]) assert result == [file] -def test_fetch_files_with_array_file_segment(llm_node): +def test_fetch_files_with_array_file_segment(): files = [ File( id="1", @@ -180,28 +209,32 @@ def test_fetch_files_with_array_file_segment(llm_node): storage_key="", ), ] - llm_node.graph_runtime_state.variable_pool.add(["sys", "files"], ArrayFileSegment(value=files)) + variable_pool = VariablePool() + variable_pool.add(["sys", "files"], ArrayFileSegment(value=files)) - result = llm_node._fetch_files(selector=["sys", "files"]) + result = llm_utils.fetch_files(variable_pool=variable_pool, selector=["sys", "files"]) assert result == files -def test_fetch_files_with_none_segment(llm_node): - llm_node.graph_runtime_state.variable_pool.add(["sys", "files"], NoneSegment()) +def test_fetch_files_with_none_segment(): + variable_pool = VariablePool() + variable_pool.add(["sys", "files"], NoneSegment()) - result = llm_node._fetch_files(selector=["sys", "files"]) + result = llm_utils.fetch_files(variable_pool=variable_pool, selector=["sys", "files"]) assert result == [] -def test_fetch_files_with_array_any_segment(llm_node): - llm_node.graph_runtime_state.variable_pool.add(["sys", "files"], ArrayAnySegment(value=[])) +def test_fetch_files_with_array_any_segment(): + variable_pool = VariablePool() + variable_pool.add(["sys", "files"], ArrayAnySegment(value=[])) - result = llm_node._fetch_files(selector=["sys", "files"]) + result = llm_utils.fetch_files(variable_pool=variable_pool, selector=["sys", "files"]) assert result == [] -def test_fetch_files_with_non_existent_variable(llm_node): - result = llm_node._fetch_files(selector=["sys", "files"]) +def test_fetch_files_with_non_existent_variable(): + variable_pool = VariablePool() + result = llm_utils.fetch_files(variable_pool=variable_pool, selector=["sys", "files"]) assert result == [] @@ -465,3 +498,167 @@ def test_handle_list_messages_basic(llm_node): assert len(result) == 1 assert isinstance(result[0], UserPromptMessage) assert result[0].content == [TextPromptMessageContent(data="Hello, world")] + + +@pytest.fixture +def llm_node_for_multimodal( + llm_node_data, graph_init_params, graph, graph_runtime_state +) -> tuple[LLMNode, LLMFileSaver]: + mock_file_saver: LLMFileSaver = mock.MagicMock(spec=LLMFileSaver) + node = LLMNode( + id="1", + config={ + "id": "1", + "data": llm_node_data.model_dump(), + }, + graph_init_params=graph_init_params, + graph=graph, + graph_runtime_state=graph_runtime_state, + llm_file_saver=mock_file_saver, + ) + return node, mock_file_saver + + +class TestLLMNodeSaveMultiModalImageOutput: + def test_llm_node_save_inline_output(self, llm_node_for_multimodal: tuple[LLMNode, LLMFileSaver]): + llm_node, mock_file_saver = llm_node_for_multimodal + content = ImagePromptMessageContent( + format="png", + base64_data=base64.b64encode(b"test-data").decode(), + mime_type="image/png", + ) + mock_file = File( + id=str(uuid.uuid4()), + tenant_id="1", + type=FileType.IMAGE, + transfer_method=FileTransferMethod.TOOL_FILE, + related_id=str(uuid.uuid4()), + filename="test-file.png", + extension=".png", + mime_type="image/png", + size=9, + ) + mock_file_saver.save_binary_string.return_value = mock_file + file = llm_node._save_multimodal_image_output(content=content) + assert llm_node._file_outputs == [mock_file] + assert file == mock_file + mock_file_saver.save_binary_string.assert_called_once_with( + data=b"test-data", mime_type="image/png", file_type=FileType.IMAGE + ) + + def test_llm_node_save_url_output(self, llm_node_for_multimodal: tuple[LLMNode, LLMFileSaver]): + llm_node, mock_file_saver = llm_node_for_multimodal + content = ImagePromptMessageContent( + format="png", + url="https://example.com/image.png", + mime_type="image/jpg", + ) + mock_file = File( + id=str(uuid.uuid4()), + tenant_id="1", + type=FileType.IMAGE, + transfer_method=FileTransferMethod.TOOL_FILE, + related_id=str(uuid.uuid4()), + filename="test-file.png", + extension=".png", + mime_type="image/png", + size=9, + ) + mock_file_saver.save_remote_url.return_value = mock_file + file = llm_node._save_multimodal_image_output(content=content) + assert llm_node._file_outputs == [mock_file] + assert file == mock_file + mock_file_saver.save_remote_url.assert_called_once_with(content.url, FileType.IMAGE) + + +def test_llm_node_image_file_to_markdown(llm_node: LLMNode): + mock_file = mock.MagicMock(spec=File) + mock_file.generate_url.return_value = "https://example.com/image.png" + markdown = llm_node._image_file_to_markdown(mock_file) + assert markdown == "![](https://example.com/image.png)" + + +class TestSaveMultimodalOutputAndConvertResultToMarkdown: + def test_str_content(self, llm_node_for_multimodal): + llm_node, mock_file_saver = llm_node_for_multimodal + gen = llm_node._save_multimodal_output_and_convert_result_to_markdown("hello world") + assert list(gen) == ["hello world"] + mock_file_saver.save_binary_string.assert_not_called() + mock_file_saver.save_remote_url.assert_not_called() + + def test_text_prompt_message_content(self, llm_node_for_multimodal): + llm_node, mock_file_saver = llm_node_for_multimodal + gen = llm_node._save_multimodal_output_and_convert_result_to_markdown( + [TextPromptMessageContent(data="hello world")] + ) + assert list(gen) == ["hello world"] + mock_file_saver.save_binary_string.assert_not_called() + mock_file_saver.save_remote_url.assert_not_called() + + def test_image_content_with_inline_data(self, llm_node_for_multimodal, monkeypatch): + llm_node, mock_file_saver = llm_node_for_multimodal + + image_raw_data = b"PNG_DATA" + image_b64_data = base64.b64encode(image_raw_data).decode() + + mock_saved_file = File( + id=str(uuid.uuid4()), + tenant_id="1", + type=FileType.IMAGE, + transfer_method=FileTransferMethod.TOOL_FILE, + filename="test.png", + extension=".png", + size=len(image_raw_data), + related_id=str(uuid.uuid4()), + url="https://example.com/test.png", + storage_key="test_storage_key", + ) + mock_file_saver.save_binary_string.return_value = mock_saved_file + gen = llm_node._save_multimodal_output_and_convert_result_to_markdown( + [ + ImagePromptMessageContent( + format="png", + base64_data=image_b64_data, + mime_type="image/png", + ) + ] + ) + yielded_strs = list(gen) + assert len(yielded_strs) == 1 + + # This assertion requires careful handling. + # `FILES_URL` settings can vary across environments, which might lead to fragile tests. + # + # Rather than asserting the complete URL returned by _save_multimodal_output_and_convert_result_to_markdown, + # we verify that the result includes the markdown image syntax and the expected file URL path. + expected_file_url_path = f"/files/tools/{mock_saved_file.related_id}.png" + assert yielded_strs[0].startswith("![](") + assert expected_file_url_path in yielded_strs[0] + assert yielded_strs[0].endswith(")") + mock_file_saver.save_binary_string.assert_called_once_with( + data=image_raw_data, + mime_type="image/png", + file_type=FileType.IMAGE, + ) + assert mock_saved_file in llm_node._file_outputs + + def test_unknown_content_type(self, llm_node_for_multimodal): + llm_node, mock_file_saver = llm_node_for_multimodal + gen = llm_node._save_multimodal_output_and_convert_result_to_markdown(frozenset(["hello world"])) + assert list(gen) == ["frozenset({'hello world'})"] + mock_file_saver.save_binary_string.assert_not_called() + mock_file_saver.save_remote_url.assert_not_called() + + def test_unknown_item_type(self, llm_node_for_multimodal): + llm_node, mock_file_saver = llm_node_for_multimodal + gen = llm_node._save_multimodal_output_and_convert_result_to_markdown([frozenset(["hello world"])]) + assert list(gen) == ["frozenset({'hello world'})"] + mock_file_saver.save_binary_string.assert_not_called() + mock_file_saver.save_remote_url.assert_not_called() + + def test_none_content(self, llm_node_for_multimodal): + llm_node, mock_file_saver = llm_node_for_multimodal + gen = llm_node._save_multimodal_output_and_convert_result_to_markdown(None) + assert list(gen) == [] + mock_file_saver.save_binary_string.assert_not_called() + mock_file_saver.save_remote_url.assert_not_called() diff --git a/api/tests/unit_tests/core/workflow/nodes/test_answer.py b/api/tests/unit_tests/core/workflow/nodes/test_answer.py index 2f0aa28b48..abc822e98b 100644 --- a/api/tests/unit_tests/core/workflow/nodes/test_answer.py +++ b/api/tests/unit_tests/core/workflow/nodes/test_answer.py @@ -4,6 +4,7 @@ from unittest.mock import MagicMock from core.app.entities.app_invoke_entities import InvokeFrom from core.workflow.entities.variable_pool import VariablePool +from core.workflow.entities.workflow_node_execution import WorkflowNodeExecutionStatus from core.workflow.enums import SystemVariableKey from core.workflow.graph_engine.entities.graph import Graph from core.workflow.graph_engine.entities.graph_init_params import GraphInitParams @@ -11,7 +12,7 @@ from core.workflow.graph_engine.entities.graph_runtime_state import GraphRuntime from core.workflow.nodes.answer.answer_node import AnswerNode from extensions.ext_database import db from models.enums import UserFrom -from models.workflow import WorkflowNodeExecutionStatus, WorkflowType +from models.workflow import WorkflowType def test_execute_answer(): diff --git a/api/tests/unit_tests/core/workflow/nodes/test_continue_on_error.py b/api/tests/unit_tests/core/workflow/nodes/test_continue_on_error.py index 111c647d9c..ff60d5974b 100644 --- a/api/tests/unit_tests/core/workflow/nodes/test_continue_on_error.py +++ b/api/tests/unit_tests/core/workflow/nodes/test_continue_on_error.py @@ -1,7 +1,8 @@ from unittest.mock import patch from core.app.entities.app_invoke_entities import InvokeFrom -from core.workflow.entities.node_entities import NodeRunMetadataKey, NodeRunResult +from core.workflow.entities.node_entities import NodeRunResult, WorkflowNodeExecutionMetadataKey +from core.workflow.entities.workflow_node_execution import WorkflowNodeExecutionStatus from core.workflow.enums import SystemVariableKey from core.workflow.graph_engine.entities.event import ( GraphRunPartialSucceededEvent, @@ -14,7 +15,7 @@ from core.workflow.graph_engine.graph_engine import GraphEngine from core.workflow.nodes.event.event import RunCompletedEvent, RunStreamChunkEvent from core.workflow.nodes.llm.node import LLMNode from models.enums import UserFrom -from models.workflow import WorkflowNodeExecutionStatus, WorkflowType +from models.workflow import WorkflowType class ContinueOnErrorTestHelper: @@ -542,9 +543,9 @@ def test_stream_output_with_fail_branch_continue_on_error(): process_data={}, outputs={}, metadata={ - NodeRunMetadataKey.TOTAL_TOKENS: 1, - NodeRunMetadataKey.TOTAL_PRICE: 1, - NodeRunMetadataKey.CURRENCY: "USD", + WorkflowNodeExecutionMetadataKey.TOTAL_TOKENS: 1, + WorkflowNodeExecutionMetadataKey.TOTAL_PRICE: 1, + WorkflowNodeExecutionMetadataKey.CURRENCY: "USD", }, ) ) diff --git a/api/tests/unit_tests/core/workflow/nodes/test_document_extractor_node.py b/api/tests/unit_tests/core/workflow/nodes/test_document_extractor_node.py index de739ce9f5..4cb1aa93f9 100644 --- a/api/tests/unit_tests/core/workflow/nodes/test_document_extractor_node.py +++ b/api/tests/unit_tests/core/workflow/nodes/test_document_extractor_node.py @@ -1,5 +1,7 @@ +import io from unittest.mock import Mock, patch +import pandas as pd import pytest from docx.oxml.text.paragraph import CT_P @@ -7,14 +9,15 @@ from core.file import File, FileTransferMethod from core.variables import ArrayFileSegment from core.variables.variables import StringVariable from core.workflow.entities.node_entities import NodeRunResult +from core.workflow.entities.workflow_node_execution import WorkflowNodeExecutionStatus from core.workflow.nodes.document_extractor import DocumentExtractorNode, DocumentExtractorNodeData from core.workflow.nodes.document_extractor.node import ( _extract_text_from_docx, + _extract_text_from_excel, _extract_text_from_pdf, _extract_text_from_plain_text, ) from core.workflow.nodes.enums import NodeType -from models.workflow import WorkflowNodeExecutionStatus @pytest.fixture @@ -149,7 +152,7 @@ def test_extract_text_from_plain_text_non_utf8(): temp_file.write(non_utf8_content) temp_file.seek(0) text = _extract_text_from_plain_text(temp_file.read()) - assert text == "Hello, world." + assert text == "Hello, world©." @patch("pypdfium2.PdfDocument") @@ -182,3 +185,147 @@ def test_extract_text_from_docx(mock_document): def test_node_type(document_extractor_node): assert document_extractor_node._node_type == NodeType.DOCUMENT_EXTRACTOR + + +@patch("pandas.ExcelFile") +def test_extract_text_from_excel_single_sheet(mock_excel_file): + """Test extracting text from Excel file with single sheet and multiline content.""" + + # Test multi-line cell + data = {"Name\nwith\nnewline": ["John\nDoe", "Jane\nSmith"], "Age": [25, 30]} + + df = pd.DataFrame(data) + + # Mock ExcelFile + mock_excel_instance = Mock() + mock_excel_instance.sheet_names = ["Sheet1"] + mock_excel_instance.parse.return_value = df + mock_excel_file.return_value = mock_excel_instance + + file_content = b"fake_excel_content" + result = _extract_text_from_excel(file_content) + expected_manual = "| Name with newline | Age |\n| ----------------- | --- |\n\ +| John Doe | 25 |\n| Jane Smith | 30 |\n\n" + + assert expected_manual == result + mock_excel_instance.parse.assert_called_once_with(sheet_name="Sheet1") + + +@patch("pandas.ExcelFile") +def test_extract_text_from_excel_multiple_sheets(mock_excel_file): + """Test extracting text from Excel file with multiple sheets and multiline content.""" + + # Test multi-line cell + data1 = {"Product\nName": ["Apple\nRed", "Banana\nYellow"], "Price": [1.50, 0.99]} + df1 = pd.DataFrame(data1) + + data2 = {"City\nName": ["New\nYork", "Los\nAngeles"], "Population": [8000000, 3900000]} + df2 = pd.DataFrame(data2) + + # Mock ExcelFile + mock_excel_instance = Mock() + mock_excel_instance.sheet_names = ["Products", "Cities"] + mock_excel_instance.parse.side_effect = [df1, df2] + mock_excel_file.return_value = mock_excel_instance + + file_content = b"fake_excel_content_multiple_sheets" + result = _extract_text_from_excel(file_content) + + expected_manual1 = "| Product Name | Price |\n| ------------ | ----- |\n\ +| Apple Red | 1.5 |\n| Banana Yellow | 0.99 |\n\n" + expected_manual2 = "| City Name | Population |\n| --------- | ---------- |\n\ +| New York | 8000000 |\n| Los Angeles | 3900000 |\n\n" + + assert expected_manual1 in result + assert expected_manual2 in result + + assert mock_excel_instance.parse.call_count == 2 + + +@patch("pandas.ExcelFile") +def test_extract_text_from_excel_empty_sheets(mock_excel_file): + """Test extracting text from Excel file with empty sheets.""" + + # Empty excel + df = pd.DataFrame() + + # Mock ExcelFile + mock_excel_instance = Mock() + mock_excel_instance.sheet_names = ["EmptySheet"] + mock_excel_instance.parse.return_value = df + mock_excel_file.return_value = mock_excel_instance + + file_content = b"fake_excel_empty_content" + result = _extract_text_from_excel(file_content) + + expected = "| |\n| |\n\n" + assert result == expected + + mock_excel_instance.parse.assert_called_once_with(sheet_name="EmptySheet") + + +@patch("pandas.ExcelFile") +def test_extract_text_from_excel_sheet_parse_error(mock_excel_file): + """Test handling of sheet parsing errors - should continue with other sheets.""" + + # Test error + data = {"Data": ["Test"], "Value": [123]} + df = pd.DataFrame(data) + + # Mock ExcelFile + mock_excel_instance = Mock() + mock_excel_instance.sheet_names = ["GoodSheet", "BadSheet"] + mock_excel_instance.parse.side_effect = [df, Exception("Parse error")] + mock_excel_file.return_value = mock_excel_instance + + file_content = b"fake_excel_mixed_content" + result = _extract_text_from_excel(file_content) + + expected_manual = "| Data | Value |\n| ---- | ----- |\n| Test | 123 |\n\n" + + assert expected_manual == result + + assert mock_excel_instance.parse.call_count == 2 + + +@patch("pandas.ExcelFile") +def test_extract_text_from_excel_io_bytesio_usage(mock_excel_file): + """Test that BytesIO is properly used with the file content.""" + + # Test bytesio + data = {"Test": [1], "Data": ["A"]} + df = pd.DataFrame(data) + + # Mock ExcelFile + mock_excel_instance = Mock() + mock_excel_instance.sheet_names = ["TestSheet"] + mock_excel_instance.parse.return_value = df + mock_excel_file.return_value = mock_excel_instance + + file_content = b"test_excel_bytes" + result = _extract_text_from_excel(file_content) + + mock_excel_file.assert_called_once() + call_arg = mock_excel_file.call_args[0][0] + assert isinstance(call_arg, io.BytesIO) + + expected_manual = "| Test | Data |\n| ---- | ---- |\n| 1 | A |\n\n" + assert expected_manual == result + + +@patch("pandas.ExcelFile") +def test_extract_text_from_excel_all_sheets_fail(mock_excel_file): + """Test when all sheets fail to parse - should return empty string.""" + + # Mock ExcelFile + mock_excel_instance = Mock() + mock_excel_instance.sheet_names = ["BadSheet1", "BadSheet2"] + mock_excel_instance.parse.side_effect = [Exception("Error 1"), Exception("Error 2")] + mock_excel_file.return_value = mock_excel_instance + + file_content = b"fake_excel_all_bad_sheets" + result = _extract_text_from_excel(file_content) + + assert result == "" + + assert mock_excel_instance.parse.call_count == 2 diff --git a/api/tests/unit_tests/core/workflow/nodes/test_if_else.py b/api/tests/unit_tests/core/workflow/nodes/test_if_else.py index 41e2c5d484..c4e411f9d6 100644 --- a/api/tests/unit_tests/core/workflow/nodes/test_if_else.py +++ b/api/tests/unit_tests/core/workflow/nodes/test_if_else.py @@ -6,6 +6,7 @@ from core.app.entities.app_invoke_entities import InvokeFrom from core.file import File, FileTransferMethod, FileType from core.variables import ArrayFileSegment from core.workflow.entities.variable_pool import VariablePool +from core.workflow.entities.workflow_node_execution import WorkflowNodeExecutionStatus from core.workflow.enums import SystemVariableKey from core.workflow.graph_engine.entities.graph import Graph from core.workflow.graph_engine.entities.graph_init_params import GraphInitParams @@ -15,7 +16,7 @@ from core.workflow.nodes.if_else.if_else_node import IfElseNode from core.workflow.utils.condition.entities import Condition, SubCondition, SubVariableCondition from extensions.ext_database import db from models.enums import UserFrom -from models.workflow import WorkflowNodeExecutionStatus, WorkflowType +from models.workflow import WorkflowType def test_execute_if_else_result_true(): diff --git a/api/tests/unit_tests/core/workflow/nodes/test_list_operator.py b/api/tests/unit_tests/core/workflow/nodes/test_list_operator.py index 36116d3540..77d42e2692 100644 --- a/api/tests/unit_tests/core/workflow/nodes/test_list_operator.py +++ b/api/tests/unit_tests/core/workflow/nodes/test_list_operator.py @@ -4,6 +4,7 @@ import pytest from core.file import File, FileTransferMethod, FileType from core.variables import ArrayFileSegment +from core.workflow.entities.workflow_node_execution import WorkflowNodeExecutionStatus from core.workflow.nodes.list_operator.entities import ( ExtractConfig, FilterBy, @@ -14,7 +15,6 @@ from core.workflow.nodes.list_operator.entities import ( ) from core.workflow.nodes.list_operator.exc import InvalidKeyError from core.workflow.nodes.list_operator.node import ListOperatorNode, _get_file_extract_string_func -from models.workflow import WorkflowNodeExecutionStatus @pytest.fixture diff --git a/api/tests/unit_tests/core/workflow/nodes/tool/test_tool_node.py b/api/tests/unit_tests/core/workflow/nodes/tool/test_tool_node.py index f593510830..e121f6338c 100644 --- a/api/tests/unit_tests/core/workflow/nodes/tool/test_tool_node.py +++ b/api/tests/unit_tests/core/workflow/nodes/tool/test_tool_node.py @@ -7,6 +7,7 @@ from core.tools.entities.tool_entities import ToolInvokeMessage, ToolProviderTyp from core.tools.errors import ToolInvokeError from core.workflow.entities.node_entities import NodeRunResult from core.workflow.entities.variable_pool import VariablePool +from core.workflow.entities.workflow_node_execution import WorkflowNodeExecutionStatus from core.workflow.graph_engine import Graph, GraphInitParams, GraphRuntimeState from core.workflow.nodes.answer import AnswerStreamGenerateRoute from core.workflow.nodes.end import EndStreamParam @@ -14,7 +15,7 @@ from core.workflow.nodes.enums import ErrorStrategy from core.workflow.nodes.event import RunCompletedEvent from core.workflow.nodes.tool import ToolNode from core.workflow.nodes.tool.entities import ToolNodeData -from models import UserFrom, WorkflowNodeExecutionStatus, WorkflowType +from models import UserFrom, WorkflowType def _create_tool_node(): diff --git a/api/tests/unit_tests/core/workflow/nodes/variable_assigner/v2/__init__.py b/api/tests/unit_tests/core/workflow/nodes/variable_assigner/v2/__init__.py new file mode 100644 index 0000000000..8b13789179 --- /dev/null +++ b/api/tests/unit_tests/core/workflow/nodes/variable_assigner/v2/__init__.py @@ -0,0 +1 @@ + diff --git a/api/tests/unit_tests/core/workflow/nodes/variable_assigner/v2/test_variable_assigner_v2.py b/api/tests/unit_tests/core/workflow/nodes/variable_assigner/v2/test_variable_assigner_v2.py new file mode 100644 index 0000000000..7c5597dd89 --- /dev/null +++ b/api/tests/unit_tests/core/workflow/nodes/variable_assigner/v2/test_variable_assigner_v2.py @@ -0,0 +1,390 @@ +import time +import uuid +from uuid import uuid4 + +from core.app.entities.app_invoke_entities import InvokeFrom +from core.variables import ArrayStringVariable +from core.workflow.entities.variable_pool import VariablePool +from core.workflow.enums import SystemVariableKey +from core.workflow.graph_engine.entities.graph import Graph +from core.workflow.graph_engine.entities.graph_init_params import GraphInitParams +from core.workflow.graph_engine.entities.graph_runtime_state import GraphRuntimeState +from core.workflow.nodes.variable_assigner.v2 import VariableAssignerNode +from core.workflow.nodes.variable_assigner.v2.enums import InputType, Operation +from models.enums import UserFrom +from models.workflow import WorkflowType + +DEFAULT_NODE_ID = "node_id" + + +def test_handle_item_directly(): + """Test the _handle_item method directly for remove operations.""" + # Create variables + variable1 = ArrayStringVariable( + id=str(uuid4()), + name="test_variable1", + value=["first", "second", "third"], + ) + + variable2 = ArrayStringVariable( + id=str(uuid4()), + name="test_variable2", + value=["first", "second", "third"], + ) + + # Create a mock class with just the _handle_item method + class MockNode: + def _handle_item(self, *, variable, operation, value): + match operation: + case Operation.REMOVE_FIRST: + if not variable.value: + return variable.value + return variable.value[1:] + case Operation.REMOVE_LAST: + if not variable.value: + return variable.value + return variable.value[:-1] + + node = MockNode() + + # Test remove-first + result1 = node._handle_item( + variable=variable1, + operation=Operation.REMOVE_FIRST, + value=None, + ) + + # Test remove-last + result2 = node._handle_item( + variable=variable2, + operation=Operation.REMOVE_LAST, + value=None, + ) + + # Check the results + assert result1 == ["second", "third"] + assert result2 == ["first", "second"] + + +def test_remove_first_from_array(): + """Test removing the first element from an array.""" + graph_config = { + "edges": [ + { + "id": "start-source-assigner-target", + "source": "start", + "target": "assigner", + }, + ], + "nodes": [ + {"data": {"type": "start"}, "id": "start"}, + { + "data": { + "type": "assigner", + }, + "id": "assigner", + }, + ], + } + + graph = Graph.init(graph_config=graph_config) + + init_params = GraphInitParams( + tenant_id="1", + app_id="1", + workflow_type=WorkflowType.WORKFLOW, + workflow_id="1", + graph_config=graph_config, + user_id="1", + user_from=UserFrom.ACCOUNT, + invoke_from=InvokeFrom.DEBUGGER, + call_depth=0, + ) + + conversation_variable = ArrayStringVariable( + id=str(uuid4()), + name="test_conversation_variable", + value=["first", "second", "third"], + selector=["conversation", "test_conversation_variable"], + ) + + variable_pool = VariablePool( + system_variables={SystemVariableKey.CONVERSATION_ID: "conversation_id"}, + user_inputs={}, + environment_variables=[], + conversation_variables=[conversation_variable], + ) + + node = VariableAssignerNode( + id=str(uuid.uuid4()), + graph_init_params=init_params, + graph=graph, + graph_runtime_state=GraphRuntimeState(variable_pool=variable_pool, start_at=time.perf_counter()), + config={ + "id": "node_id", + "data": { + "title": "test", + "version": "2", + "items": [ + { + "variable_selector": ["conversation", conversation_variable.name], + "input_type": InputType.VARIABLE, + "operation": Operation.REMOVE_FIRST, + "value": None, + } + ], + }, + }, + ) + + # Skip the mock assertion since we're in a test environment + # Print the variable before running + print(f"Before: {variable_pool.get(['conversation', conversation_variable.name]).to_object()}") + + # Run the node + result = list(node.run()) + + # Print the variable after running and the result + print(f"After: {variable_pool.get(['conversation', conversation_variable.name]).to_object()}") + print(f"Result: {result}") + + got = variable_pool.get(["conversation", conversation_variable.name]) + assert got is not None + assert got.to_object() == ["second", "third"] + + +def test_remove_last_from_array(): + """Test removing the last element from an array.""" + graph_config = { + "edges": [ + { + "id": "start-source-assigner-target", + "source": "start", + "target": "assigner", + }, + ], + "nodes": [ + {"data": {"type": "start"}, "id": "start"}, + { + "data": { + "type": "assigner", + }, + "id": "assigner", + }, + ], + } + + graph = Graph.init(graph_config=graph_config) + + init_params = GraphInitParams( + tenant_id="1", + app_id="1", + workflow_type=WorkflowType.WORKFLOW, + workflow_id="1", + graph_config=graph_config, + user_id="1", + user_from=UserFrom.ACCOUNT, + invoke_from=InvokeFrom.DEBUGGER, + call_depth=0, + ) + + conversation_variable = ArrayStringVariable( + id=str(uuid4()), + name="test_conversation_variable", + value=["first", "second", "third"], + selector=["conversation", "test_conversation_variable"], + ) + + variable_pool = VariablePool( + system_variables={SystemVariableKey.CONVERSATION_ID: "conversation_id"}, + user_inputs={}, + environment_variables=[], + conversation_variables=[conversation_variable], + ) + + node = VariableAssignerNode( + id=str(uuid.uuid4()), + graph_init_params=init_params, + graph=graph, + graph_runtime_state=GraphRuntimeState(variable_pool=variable_pool, start_at=time.perf_counter()), + config={ + "id": "node_id", + "data": { + "title": "test", + "version": "2", + "items": [ + { + "variable_selector": ["conversation", conversation_variable.name], + "input_type": InputType.VARIABLE, + "operation": Operation.REMOVE_LAST, + "value": None, + } + ], + }, + }, + ) + + # Skip the mock assertion since we're in a test environment + list(node.run()) + + got = variable_pool.get(["conversation", conversation_variable.name]) + assert got is not None + assert got.to_object() == ["first", "second"] + + +def test_remove_first_from_empty_array(): + """Test removing the first element from an empty array (should do nothing).""" + graph_config = { + "edges": [ + { + "id": "start-source-assigner-target", + "source": "start", + "target": "assigner", + }, + ], + "nodes": [ + {"data": {"type": "start"}, "id": "start"}, + { + "data": { + "type": "assigner", + }, + "id": "assigner", + }, + ], + } + + graph = Graph.init(graph_config=graph_config) + + init_params = GraphInitParams( + tenant_id="1", + app_id="1", + workflow_type=WorkflowType.WORKFLOW, + workflow_id="1", + graph_config=graph_config, + user_id="1", + user_from=UserFrom.ACCOUNT, + invoke_from=InvokeFrom.DEBUGGER, + call_depth=0, + ) + + conversation_variable = ArrayStringVariable( + id=str(uuid4()), + name="test_conversation_variable", + value=[], + selector=["conversation", "test_conversation_variable"], + ) + + variable_pool = VariablePool( + system_variables={SystemVariableKey.CONVERSATION_ID: "conversation_id"}, + user_inputs={}, + environment_variables=[], + conversation_variables=[conversation_variable], + ) + + node = VariableAssignerNode( + id=str(uuid.uuid4()), + graph_init_params=init_params, + graph=graph, + graph_runtime_state=GraphRuntimeState(variable_pool=variable_pool, start_at=time.perf_counter()), + config={ + "id": "node_id", + "data": { + "title": "test", + "version": "2", + "items": [ + { + "variable_selector": ["conversation", conversation_variable.name], + "input_type": InputType.VARIABLE, + "operation": Operation.REMOVE_FIRST, + "value": None, + } + ], + }, + }, + ) + + # Skip the mock assertion since we're in a test environment + list(node.run()) + + got = variable_pool.get(["conversation", conversation_variable.name]) + assert got is not None + assert got.to_object() == [] + + +def test_remove_last_from_empty_array(): + """Test removing the last element from an empty array (should do nothing).""" + graph_config = { + "edges": [ + { + "id": "start-source-assigner-target", + "source": "start", + "target": "assigner", + }, + ], + "nodes": [ + {"data": {"type": "start"}, "id": "start"}, + { + "data": { + "type": "assigner", + }, + "id": "assigner", + }, + ], + } + + graph = Graph.init(graph_config=graph_config) + + init_params = GraphInitParams( + tenant_id="1", + app_id="1", + workflow_type=WorkflowType.WORKFLOW, + workflow_id="1", + graph_config=graph_config, + user_id="1", + user_from=UserFrom.ACCOUNT, + invoke_from=InvokeFrom.DEBUGGER, + call_depth=0, + ) + + conversation_variable = ArrayStringVariable( + id=str(uuid4()), + name="test_conversation_variable", + value=[], + selector=["conversation", "test_conversation_variable"], + ) + + variable_pool = VariablePool( + system_variables={SystemVariableKey.CONVERSATION_ID: "conversation_id"}, + user_inputs={}, + environment_variables=[], + conversation_variables=[conversation_variable], + ) + + node = VariableAssignerNode( + id=str(uuid.uuid4()), + graph_init_params=init_params, + graph=graph, + graph_runtime_state=GraphRuntimeState(variable_pool=variable_pool, start_at=time.perf_counter()), + config={ + "id": "node_id", + "data": { + "title": "test", + "version": "2", + "items": [ + { + "variable_selector": ["conversation", conversation_variable.name], + "input_type": InputType.VARIABLE, + "operation": Operation.REMOVE_LAST, + "value": None, + } + ], + }, + }, + ) + + # Skip the mock assertion since we're in a test environment + list(node.run()) + + got = variable_pool.get(["conversation", conversation_variable.name]) + assert got is not None + assert got.to_object() == [] diff --git a/api/tests/unit_tests/core/workflow/test_workflow_cycle_manager.py b/api/tests/unit_tests/core/workflow/test_workflow_cycle_manager.py new file mode 100644 index 0000000000..fddc182594 --- /dev/null +++ b/api/tests/unit_tests/core/workflow/test_workflow_cycle_manager.py @@ -0,0 +1,480 @@ +import json +from datetime import UTC, datetime +from unittest.mock import MagicMock + +import pytest +from sqlalchemy.orm import Session + +from core.app.app_config.entities import AppAdditionalFeatures, WorkflowUIBasedAppConfig +from core.app.entities.app_invoke_entities import AdvancedChatAppGenerateEntity, InvokeFrom +from core.app.entities.queue_entities import ( + QueueNodeFailedEvent, + QueueNodeStartedEvent, + QueueNodeSucceededEvent, +) +from core.workflow.entities.workflow_execution import WorkflowExecution, WorkflowExecutionStatus, WorkflowType +from core.workflow.entities.workflow_node_execution import ( + WorkflowNodeExecution, + WorkflowNodeExecutionMetadataKey, + WorkflowNodeExecutionStatus, +) +from core.workflow.enums import SystemVariableKey +from core.workflow.nodes import NodeType +from core.workflow.repositories.workflow_execution_repository import WorkflowExecutionRepository +from core.workflow.repositories.workflow_node_execution_repository import WorkflowNodeExecutionRepository +from core.workflow.workflow_cycle_manager import CycleManagerWorkflowInfo, WorkflowCycleManager +from models.enums import CreatorUserRole +from models.model import AppMode +from models.workflow import Workflow, WorkflowRun + + +@pytest.fixture +def real_app_generate_entity(): + additional_features = AppAdditionalFeatures( + file_upload=None, + opening_statement=None, + suggested_questions=[], + suggested_questions_after_answer=False, + show_retrieve_source=False, + more_like_this=False, + speech_to_text=False, + text_to_speech=None, + trace_config=None, + ) + + app_config = WorkflowUIBasedAppConfig( + tenant_id="test-tenant-id", + app_id="test-app-id", + app_mode=AppMode.WORKFLOW, + additional_features=additional_features, + workflow_id="test-workflow-id", + ) + + entity = AdvancedChatAppGenerateEntity( + task_id="test-task-id", + app_config=app_config, + inputs={"query": "test query"}, + files=[], + user_id="test-user-id", + stream=False, + invoke_from=InvokeFrom.WEB_APP, + query="test query", + conversation_id="test-conversation-id", + ) + + return entity + + +@pytest.fixture +def real_workflow_system_variables(): + return { + SystemVariableKey.QUERY: "test query", + SystemVariableKey.CONVERSATION_ID: "test-conversation-id", + SystemVariableKey.USER_ID: "test-user-id", + SystemVariableKey.APP_ID: "test-app-id", + SystemVariableKey.WORKFLOW_ID: "test-workflow-id", + SystemVariableKey.WORKFLOW_EXECUTION_ID: "test-workflow-run-id", + } + + +@pytest.fixture +def mock_node_execution_repository(): + repo = MagicMock(spec=WorkflowNodeExecutionRepository) + repo.get_by_node_execution_id.return_value = None + repo.get_running_executions.return_value = [] + return repo + + +@pytest.fixture +def mock_workflow_execution_repository(): + repo = MagicMock(spec=WorkflowExecutionRepository) + repo.get.return_value = None + return repo + + +@pytest.fixture +def real_workflow_entity(): + return CycleManagerWorkflowInfo( + workflow_id="test-workflow-id", # Matches ID used in other fixtures + workflow_type=WorkflowType.CHAT, + version="1.0.0", + graph_data={ + "nodes": [ + { + "id": "node1", + "type": "chat", # NodeType is a string enum + "name": "Chat Node", + "data": {"model": "gpt-3.5-turbo", "prompt": "test prompt"}, + } + ], + "edges": [], + }, + ) + + +@pytest.fixture +def workflow_cycle_manager( + real_app_generate_entity, + real_workflow_system_variables, + mock_workflow_execution_repository, + mock_node_execution_repository, + real_workflow_entity, +): + return WorkflowCycleManager( + application_generate_entity=real_app_generate_entity, + workflow_system_variables=real_workflow_system_variables, + workflow_info=real_workflow_entity, + workflow_execution_repository=mock_workflow_execution_repository, + workflow_node_execution_repository=mock_node_execution_repository, + ) + + +@pytest.fixture +def mock_session(): + session = MagicMock(spec=Session) + return session + + +@pytest.fixture +def real_workflow(): + workflow = Workflow() + workflow.id = "test-workflow-id" + workflow.tenant_id = "test-tenant-id" + workflow.app_id = "test-app-id" + workflow.type = "chat" + workflow.version = "1.0" + + graph_data = {"nodes": [], "edges": []} + workflow.graph = json.dumps(graph_data) + workflow.features = json.dumps({"file_upload": {"enabled": False}}) + workflow.created_by = "test-user-id" + workflow.created_at = datetime.now(UTC).replace(tzinfo=None) + workflow.updated_at = datetime.now(UTC).replace(tzinfo=None) + workflow._environment_variables = "{}" + workflow._conversation_variables = "{}" + + return workflow + + +@pytest.fixture +def real_workflow_run(): + workflow_run = WorkflowRun() + workflow_run.id = "test-workflow-run-id" + workflow_run.tenant_id = "test-tenant-id" + workflow_run.app_id = "test-app-id" + workflow_run.workflow_id = "test-workflow-id" + workflow_run.sequence_number = 1 + workflow_run.type = "chat" + workflow_run.triggered_from = "app-run" + workflow_run.version = "1.0" + workflow_run.graph = json.dumps({"nodes": [], "edges": []}) + workflow_run.inputs = json.dumps({"query": "test query"}) + workflow_run.status = WorkflowExecutionStatus.RUNNING + workflow_run.outputs = json.dumps({"answer": "test answer"}) + workflow_run.created_by_role = CreatorUserRole.ACCOUNT + workflow_run.created_by = "test-user-id" + workflow_run.created_at = datetime.now(UTC).replace(tzinfo=None) + + return workflow_run + + +def test_init( + workflow_cycle_manager, + real_app_generate_entity, + real_workflow_system_variables, + mock_workflow_execution_repository, + mock_node_execution_repository, +): + """Test initialization of WorkflowCycleManager""" + assert workflow_cycle_manager._application_generate_entity == real_app_generate_entity + assert workflow_cycle_manager._workflow_system_variables == real_workflow_system_variables + assert workflow_cycle_manager._workflow_execution_repository == mock_workflow_execution_repository + assert workflow_cycle_manager._workflow_node_execution_repository == mock_node_execution_repository + + +def test_handle_workflow_run_start(workflow_cycle_manager): + """Test handle_workflow_run_start method""" + # Call the method + workflow_execution = workflow_cycle_manager.handle_workflow_run_start() + + # Verify the result + assert workflow_execution.workflow_id == "test-workflow-id" + + # Verify the workflow_execution_repository.save was called + workflow_cycle_manager._workflow_execution_repository.save.assert_called_once_with(workflow_execution) + + +def test_handle_workflow_run_success(workflow_cycle_manager, mock_workflow_execution_repository): + """Test handle_workflow_run_success method""" + # Create a real WorkflowExecution + + workflow_execution = WorkflowExecution( + id_="test-workflow-run-id", + workflow_id="test-workflow-id", + workflow_version="1.0", + workflow_type=WorkflowType.CHAT, + graph={"nodes": [], "edges": []}, + inputs={"query": "test query"}, + started_at=datetime.now(UTC).replace(tzinfo=None), + ) + + # Mock _get_workflow_execution_or_raise_error to return the real workflow_execution + workflow_cycle_manager._workflow_execution_repository.get.return_value = workflow_execution + + # Call the method + result = workflow_cycle_manager.handle_workflow_run_success( + workflow_run_id="test-workflow-run-id", + total_tokens=100, + total_steps=5, + outputs={"answer": "test answer"}, + ) + + # Verify the result + assert result == workflow_execution + assert result.status == WorkflowExecutionStatus.SUCCEEDED + assert result.outputs == {"answer": "test answer"} + assert result.total_tokens == 100 + assert result.total_steps == 5 + assert result.finished_at is not None + + +def test_handle_workflow_run_failed(workflow_cycle_manager, mock_workflow_execution_repository): + """Test handle_workflow_run_failed method""" + # Create a real WorkflowExecution + + workflow_execution = WorkflowExecution( + id_="test-workflow-run-id", + workflow_id="test-workflow-id", + workflow_version="1.0", + workflow_type=WorkflowType.CHAT, + graph={"nodes": [], "edges": []}, + inputs={"query": "test query"}, + started_at=datetime.now(UTC).replace(tzinfo=None), + ) + + # Mock _get_workflow_execution_or_raise_error to return the real workflow_execution + workflow_cycle_manager._workflow_execution_repository.get.return_value = workflow_execution + + # Mock get_running_executions to return an empty list + workflow_cycle_manager._workflow_node_execution_repository.get_running_executions.return_value = [] + + # Call the method + result = workflow_cycle_manager.handle_workflow_run_failed( + workflow_run_id="test-workflow-run-id", + total_tokens=50, + total_steps=3, + status=WorkflowExecutionStatus.FAILED, + error_message="Test error message", + ) + + # Verify the result + assert result == workflow_execution + assert result.status == WorkflowExecutionStatus.FAILED + assert result.error_message == "Test error message" + assert result.total_tokens == 50 + assert result.total_steps == 3 + assert result.finished_at is not None + + +def test_handle_node_execution_start(workflow_cycle_manager, mock_workflow_execution_repository): + """Test handle_node_execution_start method""" + # Create a real WorkflowExecution + + workflow_execution = WorkflowExecution( + id_="test-workflow-execution-id", + workflow_id="test-workflow-id", + workflow_version="1.0", + workflow_type=WorkflowType.CHAT, + graph={"nodes": [], "edges": []}, + inputs={"query": "test query"}, + started_at=datetime.now(UTC).replace(tzinfo=None), + ) + + # Mock _get_workflow_execution_or_raise_error to return the real workflow_execution + workflow_cycle_manager._workflow_execution_repository.get.return_value = workflow_execution + + # Create a mock event + event = MagicMock(spec=QueueNodeStartedEvent) + event.node_execution_id = "test-node-execution-id" + event.node_id = "test-node-id" + event.node_type = NodeType.LLM + + # Create node_data as a separate mock + node_data = MagicMock() + node_data.title = "Test Node" + event.node_data = node_data + + event.predecessor_node_id = "test-predecessor-node-id" + event.node_run_index = 1 + event.parallel_mode_run_id = "test-parallel-mode-run-id" + event.in_iteration_id = "test-iteration-id" + event.in_loop_id = "test-loop-id" + + # Call the method + result = workflow_cycle_manager.handle_node_execution_start( + workflow_execution_id=workflow_execution.id_, + event=event, + ) + + # Verify the result + assert result.workflow_id == workflow_execution.workflow_id + assert result.workflow_execution_id == workflow_execution.id_ + assert result.node_execution_id == event.node_execution_id + assert result.node_id == event.node_id + assert result.node_type == event.node_type + assert result.title == event.node_data.title + assert result.status == WorkflowNodeExecutionStatus.RUNNING + + # Verify save was called + workflow_cycle_manager._workflow_node_execution_repository.save.assert_called_once_with(result) + + +def test_get_workflow_execution_or_raise_error(workflow_cycle_manager, mock_workflow_execution_repository): + """Test _get_workflow_execution_or_raise_error method""" + # Create a real WorkflowExecution + + workflow_execution = WorkflowExecution( + id_="test-workflow-run-id", + workflow_id="test-workflow-id", + workflow_version="1.0", + workflow_type=WorkflowType.CHAT, + graph={"nodes": [], "edges": []}, + inputs={"query": "test query"}, + started_at=datetime.now(UTC).replace(tzinfo=None), + ) + + # Mock the repository get method to return the real execution + workflow_cycle_manager._workflow_execution_repository.get.return_value = workflow_execution + + # Call the method + result = workflow_cycle_manager._get_workflow_execution_or_raise_error("test-workflow-run-id") + + # Verify the result + assert result == workflow_execution + + # Test error case + workflow_cycle_manager._workflow_execution_repository.get.return_value = None + + # Expect an error when execution is not found + with pytest.raises(ValueError): + workflow_cycle_manager._get_workflow_execution_or_raise_error("non-existent-id") + + +def test_handle_workflow_node_execution_success(workflow_cycle_manager): + """Test handle_workflow_node_execution_success method""" + # Create a mock event + event = MagicMock(spec=QueueNodeSucceededEvent) + event.node_execution_id = "test-node-execution-id" + event.inputs = {"input": "test input"} + event.process_data = {"process": "test process"} + event.outputs = {"output": "test output"} + event.execution_metadata = {WorkflowNodeExecutionMetadataKey.TOTAL_TOKENS: 100} + event.start_at = datetime.now(UTC).replace(tzinfo=None) + + # Create a real node execution + + node_execution = WorkflowNodeExecution( + id="test-node-execution-record-id", + node_execution_id="test-node-execution-id", + workflow_id="test-workflow-id", + workflow_execution_id="test-workflow-run-id", + index=1, + node_id="test-node-id", + node_type=NodeType.LLM, + title="Test Node", + created_at=datetime.now(UTC).replace(tzinfo=None), + ) + + # Mock the repository to return the node execution + workflow_cycle_manager._workflow_node_execution_repository.get_by_node_execution_id.return_value = node_execution + + # Call the method + result = workflow_cycle_manager.handle_workflow_node_execution_success( + event=event, + ) + + # Verify the result + assert result == node_execution + assert result.status == WorkflowNodeExecutionStatus.SUCCEEDED + + # Verify save was called + workflow_cycle_manager._workflow_node_execution_repository.save.assert_called_once_with(node_execution) + + +def test_handle_workflow_run_partial_success(workflow_cycle_manager, mock_workflow_execution_repository): + """Test handle_workflow_run_partial_success method""" + # Create a real WorkflowExecution + + workflow_execution = WorkflowExecution( + id_="test-workflow-run-id", + workflow_id="test-workflow-id", + workflow_version="1.0", + workflow_type=WorkflowType.CHAT, + graph={"nodes": [], "edges": []}, + inputs={"query": "test query"}, + started_at=datetime.now(UTC).replace(tzinfo=None), + ) + + # Mock _get_workflow_execution_or_raise_error to return the real workflow_execution + workflow_cycle_manager._workflow_execution_repository.get.return_value = workflow_execution + + # Call the method + result = workflow_cycle_manager.handle_workflow_run_partial_success( + workflow_run_id="test-workflow-run-id", + total_tokens=75, + total_steps=4, + outputs={"partial_answer": "test partial answer"}, + exceptions_count=2, + ) + + # Verify the result + assert result == workflow_execution + assert result.status == WorkflowExecutionStatus.PARTIAL_SUCCEEDED + assert result.outputs == {"partial_answer": "test partial answer"} + assert result.total_tokens == 75 + assert result.total_steps == 4 + assert result.exceptions_count == 2 + assert result.finished_at is not None + + +def test_handle_workflow_node_execution_failed(workflow_cycle_manager): + """Test handle_workflow_node_execution_failed method""" + # Create a mock event + event = MagicMock(spec=QueueNodeFailedEvent) + event.node_execution_id = "test-node-execution-id" + event.inputs = {"input": "test input"} + event.process_data = {"process": "test process"} + event.outputs = {"output": "test output"} + event.execution_metadata = {WorkflowNodeExecutionMetadataKey.TOTAL_TOKENS: 100} + event.start_at = datetime.now(UTC).replace(tzinfo=None) + event.error = "Test error message" + + # Create a real node execution + + node_execution = WorkflowNodeExecution( + id="test-node-execution-record-id", + node_execution_id="test-node-execution-id", + workflow_id="test-workflow-id", + workflow_execution_id="test-workflow-run-id", + index=1, + node_id="test-node-id", + node_type=NodeType.LLM, + title="Test Node", + created_at=datetime.now(UTC).replace(tzinfo=None), + ) + + # Mock the repository to return the node execution + workflow_cycle_manager._workflow_node_execution_repository.get_by_node_execution_id.return_value = node_execution + + # Call the method + result = workflow_cycle_manager.handle_workflow_node_execution_failed( + event=event, + ) + + # Verify the result + assert result == node_execution + assert result.status == WorkflowNodeExecutionStatus.FAILED + assert result.error == "Test error message" + + # Verify save was called + workflow_cycle_manager._workflow_node_execution_repository.save.assert_called_once_with(node_execution) diff --git a/api/tests/unit_tests/extensions/test_ext_request_logging.py b/api/tests/unit_tests/extensions/test_ext_request_logging.py new file mode 100644 index 0000000000..4e71469bcc --- /dev/null +++ b/api/tests/unit_tests/extensions/test_ext_request_logging.py @@ -0,0 +1,265 @@ +import json +import logging +from unittest import mock + +import pytest +from flask import Flask, Response + +from configs import dify_config +from extensions import ext_request_logging +from extensions.ext_request_logging import _is_content_type_json, _log_request_finished, init_app + + +def test_is_content_type_json(): + """ + Test the _is_content_type_json function. + """ + + assert _is_content_type_json("application/json") is True + # content type header with charset option. + assert _is_content_type_json("application/json; charset=utf-8") is True + # content type header with charset option, in uppercase. + assert _is_content_type_json("APPLICATION/JSON; CHARSET=UTF-8") is True + assert _is_content_type_json("text/html") is False + assert _is_content_type_json("") is False + + +_KEY_NEEDLE = "needle" +_VALUE_NEEDLE = _KEY_NEEDLE[::-1] +_RESPONSE_NEEDLE = "response" + + +def _get_test_app(): + app = Flask(__name__) + + @app.route("/", methods=["GET", "POST"]) + def handler(): + return _RESPONSE_NEEDLE + + return app + + +# NOTE(QuantumGhost): Due to the design of Flask, we need to use monkey patch to write tests. + + +@pytest.fixture +def mock_request_receiver(monkeypatch) -> mock.Mock: + mock_log_request_started = mock.Mock() + monkeypatch.setattr(ext_request_logging, "_log_request_started", mock_log_request_started) + return mock_log_request_started + + +@pytest.fixture +def mock_response_receiver(monkeypatch) -> mock.Mock: + mock_log_request_finished = mock.Mock() + monkeypatch.setattr(ext_request_logging, "_log_request_finished", mock_log_request_finished) + return mock_log_request_finished + + +@pytest.fixture +def mock_logger(monkeypatch) -> logging.Logger: + _logger = mock.MagicMock(spec=logging.Logger) + monkeypatch.setattr(ext_request_logging, "_logger", _logger) + return _logger + + +@pytest.fixture +def enable_request_logging(monkeypatch): + monkeypatch.setattr(dify_config, "ENABLE_REQUEST_LOGGING", True) + + +class TestRequestLoggingExtension: + def test_receiver_should_not_be_invoked_if_configuration_is_disabled( + self, + monkeypatch, + mock_request_receiver, + mock_response_receiver, + ): + monkeypatch.setattr(dify_config, "ENABLE_REQUEST_LOGGING", False) + + app = _get_test_app() + init_app(app) + + with app.test_client() as client: + client.get("/") + + mock_request_receiver.assert_not_called() + mock_response_receiver.assert_not_called() + + def test_receiver_should_be_called_if_enabled( + self, + enable_request_logging, + mock_request_receiver, + mock_response_receiver, + ): + """ + Test the request logging extension with JSON data. + """ + + app = _get_test_app() + init_app(app) + + with app.test_client() as client: + client.post("/", json={_KEY_NEEDLE: _VALUE_NEEDLE}) + + mock_request_receiver.assert_called_once() + mock_response_receiver.assert_called_once() + + +class TestLoggingLevel: + @pytest.mark.usefixtures("enable_request_logging") + def test_logging_should_be_skipped_if_level_is_above_debug(self, enable_request_logging, mock_logger): + mock_logger.isEnabledFor.return_value = False + app = _get_test_app() + init_app(app) + + with app.test_client() as client: + client.post("/", json={_KEY_NEEDLE: _VALUE_NEEDLE}) + mock_logger.debug.assert_not_called() + + +class TestRequestReceiverLogging: + @pytest.mark.usefixtures("enable_request_logging") + def test_non_json_request(self, enable_request_logging, mock_logger, mock_response_receiver): + mock_logger.isEnabledFor.return_value = True + app = _get_test_app() + init_app(app) + + with app.test_client() as client: + client.post("/", data="plain text") + assert mock_logger.debug.call_count == 1 + call_args = mock_logger.debug.call_args[0] + assert "Received Request" in call_args[0] + assert call_args[1] == "POST" + assert call_args[2] == "/" + assert "Request Body" not in call_args[0] + + @pytest.mark.usefixtures("enable_request_logging") + def test_json_request(self, enable_request_logging, mock_logger, mock_response_receiver): + mock_logger.isEnabledFor.return_value = True + app = _get_test_app() + init_app(app) + + with app.test_client() as client: + client.post("/", json={_KEY_NEEDLE: _VALUE_NEEDLE}) + assert mock_logger.debug.call_count == 1 + call_args = mock_logger.debug.call_args[0] + assert "Received Request" in call_args[0] + assert "Request Body" in call_args[0] + assert call_args[1] == "POST" + assert call_args[2] == "/" + assert _KEY_NEEDLE in call_args[3] + + @pytest.mark.usefixtures("enable_request_logging") + def test_json_request_with_empty_body(self, enable_request_logging, mock_logger, mock_response_receiver): + mock_logger.isEnabledFor.return_value = True + app = _get_test_app() + init_app(app) + + with app.test_client() as client: + client.post("/", headers={"Content-Type": "application/json"}) + + assert mock_logger.debug.call_count == 1 + call_args = mock_logger.debug.call_args[0] + assert "Received Request" in call_args[0] + assert "Request Body" not in call_args[0] + assert call_args[1] == "POST" + assert call_args[2] == "/" + + @pytest.mark.usefixtures("enable_request_logging") + def test_json_request_with_invalid_json_as_body(self, enable_request_logging, mock_logger, mock_response_receiver): + mock_logger.isEnabledFor.return_value = True + app = _get_test_app() + init_app(app) + + with app.test_client() as client: + client.post( + "/", + headers={"Content-Type": "application/json"}, + data="{", + ) + assert mock_logger.debug.call_count == 0 + assert mock_logger.exception.call_count == 1 + + exception_call_args = mock_logger.exception.call_args[0] + assert exception_call_args[0] == "Failed to parse JSON request" + + +class TestResponseReceiverLogging: + @pytest.mark.usefixtures("enable_request_logging") + def test_non_json_response(self, enable_request_logging, mock_logger): + mock_logger.isEnabledFor.return_value = True + app = _get_test_app() + response = Response( + "OK", + headers={"Content-Type": "text/plain"}, + ) + _log_request_finished(app, response) + assert mock_logger.debug.call_count == 1 + call_args = mock_logger.debug.call_args[0] + assert "Response" in call_args[0] + assert "200" in call_args[1] + assert call_args[2] == "text/plain" + assert "Response Body" not in call_args[0] + + @pytest.mark.usefixtures("enable_request_logging") + def test_json_response(self, enable_request_logging, mock_logger, mock_response_receiver): + mock_logger.isEnabledFor.return_value = True + app = _get_test_app() + response = Response( + json.dumps({_KEY_NEEDLE: _VALUE_NEEDLE}), + headers={"Content-Type": "application/json"}, + ) + _log_request_finished(app, response) + assert mock_logger.debug.call_count == 1 + call_args = mock_logger.debug.call_args[0] + assert "Response" in call_args[0] + assert "Response Body" in call_args[0] + assert "200" in call_args[1] + assert call_args[2] == "application/json" + assert _KEY_NEEDLE in call_args[3] + + @pytest.mark.usefixtures("enable_request_logging") + def test_json_request_with_invalid_json_as_body(self, enable_request_logging, mock_logger, mock_response_receiver): + mock_logger.isEnabledFor.return_value = True + app = _get_test_app() + + response = Response( + "{", + headers={"Content-Type": "application/json"}, + ) + _log_request_finished(app, response) + assert mock_logger.debug.call_count == 0 + assert mock_logger.exception.call_count == 1 + + exception_call_args = mock_logger.exception.call_args[0] + assert exception_call_args[0] == "Failed to parse JSON response" + + +class TestResponseUnmodified: + def test_when_request_logging_disabled(self): + app = _get_test_app() + init_app(app) + + with app.test_client() as client: + response = client.post( + "/", + headers={"Content-Type": "application/json"}, + data="{", + ) + assert response.text == _RESPONSE_NEEDLE + assert response.status_code == 200 + + @pytest.mark.usefixtures("enable_request_logging") + def test_when_request_logging_enabled(self, enable_request_logging): + app = _get_test_app() + init_app(app) + + with app.test_client() as client: + response = client.post( + "/", + headers={"Content-Type": "application/json"}, + data="{", + ) + assert response.text == _RESPONSE_NEEDLE + assert response.status_code == 200 diff --git a/api/tests/unit_tests/models/test_types_enum_text.py b/api/tests/unit_tests/models/test_types_enum_text.py new file mode 100644 index 0000000000..3afa0f17a0 --- /dev/null +++ b/api/tests/unit_tests/models/test_types_enum_text.py @@ -0,0 +1,187 @@ +from collections.abc import Callable, Iterable +from enum import StrEnum +from typing import Any, NamedTuple, TypeVar + +import pytest +import sqlalchemy as sa +from sqlalchemy import exc as sa_exc +from sqlalchemy import insert +from sqlalchemy.orm import DeclarativeBase, Mapped, Session +from sqlalchemy.sql.sqltypes import VARCHAR + +from models.types import EnumText + +_user_type_admin = "admin" +_user_type_normal = "normal" + + +class _Base(DeclarativeBase): + pass + + +class _UserType(StrEnum): + admin = _user_type_admin + normal = _user_type_normal + + +class _EnumWithLongValue(StrEnum): + unknown = "unknown" + a_really_long_enum_values = "a_really_long_enum_values" + + +class _User(_Base): + __tablename__ = "users" + + id: Mapped[int] = sa.Column(sa.Integer, primary_key=True) + name: Mapped[str] = sa.Column(sa.String(length=255), nullable=False) + user_type: Mapped[_UserType] = sa.Column(EnumText(enum_class=_UserType), nullable=False, default=_UserType.normal) + user_type_nullable: Mapped[_UserType | None] = sa.Column(EnumText(enum_class=_UserType), nullable=True) + + +class _ColumnTest(_Base): + __tablename__ = "column_test" + + id: Mapped[int] = sa.Column(sa.Integer, primary_key=True) + + user_type: Mapped[_UserType] = sa.Column(EnumText(enum_class=_UserType), nullable=False, default=_UserType.normal) + explicit_length: Mapped[_UserType | None] = sa.Column( + EnumText(_UserType, length=50), nullable=True, default=_UserType.normal + ) + long_value: Mapped[_EnumWithLongValue] = sa.Column(EnumText(enum_class=_EnumWithLongValue), nullable=False) + + +_T = TypeVar("_T") + + +def _first(it: Iterable[_T]) -> _T: + ls = list(it) + if not ls: + raise ValueError("List is empty") + return ls[0] + + +class TestEnumText: + def test_column_impl(self): + engine = sa.create_engine("sqlite://", echo=False) + _Base.metadata.create_all(engine) + + inspector = sa.inspect(engine) + columns = inspector.get_columns(_ColumnTest.__tablename__) + + user_type_column = _first(c for c in columns if c["name"] == "user_type") + sql_type = user_type_column["type"] + assert isinstance(user_type_column["type"], VARCHAR) + assert sql_type.length == 20 + assert user_type_column["nullable"] is False + + explicit_length_column = _first(c for c in columns if c["name"] == "explicit_length") + sql_type = explicit_length_column["type"] + assert isinstance(sql_type, VARCHAR) + assert sql_type.length == 50 + assert explicit_length_column["nullable"] is True + + long_value_column = _first(c for c in columns if c["name"] == "long_value") + sql_type = long_value_column["type"] + assert isinstance(sql_type, VARCHAR) + assert sql_type.length == len(_EnumWithLongValue.a_really_long_enum_values) + + def test_insert_and_select(self): + engine = sa.create_engine("sqlite://", echo=False) + _Base.metadata.create_all(engine) + + with Session(engine) as session: + admin_user = _User( + name="admin", + user_type=_UserType.admin, + user_type_nullable=None, + ) + session.add(admin_user) + session.flush() + admin_user_id = admin_user.id + + normal_user = _User( + name="normal", + user_type=_UserType.normal.value, + user_type_nullable=_UserType.normal.value, + ) + session.add(normal_user) + session.flush() + normal_user_id = normal_user.id + session.commit() + + with Session(engine) as session: + user = session.query(_User).filter(_User.id == admin_user_id).first() + assert user.user_type == _UserType.admin + assert user.user_type_nullable is None + + with Session(engine) as session: + user = session.query(_User).filter(_User.id == normal_user_id).first() + assert user.user_type == _UserType.normal + assert user.user_type_nullable == _UserType.normal + + def test_insert_invalid_values(self): + def _session_insert_with_value(sess: Session, user_type: Any): + user = _User(name="test_user", user_type=user_type) + sess.add(user) + sess.flush() + + def _insert_with_user(sess: Session, user_type: Any): + stmt = insert(_User).values( + { + "name": "test_user", + "user_type": user_type, + } + ) + sess.execute(stmt) + + class TestCase(NamedTuple): + name: str + action: Callable[[Session], None] + exc_type: type[Exception] + + engine = sa.create_engine("sqlite://", echo=False) + _Base.metadata.create_all(engine) + cases = [ + TestCase( + name="session insert with invalid value", + action=lambda s: _session_insert_with_value(s, "invalid"), + exc_type=ValueError, + ), + TestCase( + name="session insert with invalid type", + action=lambda s: _session_insert_with_value(s, 1), + exc_type=TypeError, + ), + TestCase( + name="insert with invalid value", + action=lambda s: _insert_with_user(s, "invalid"), + exc_type=ValueError, + ), + TestCase( + name="insert with invalid type", + action=lambda s: _insert_with_user(s, 1), + exc_type=TypeError, + ), + ] + for idx, c in enumerate(cases, 1): + with pytest.raises(sa_exc.StatementError) as exc: + with Session(engine) as session: + c.action(session) + + assert isinstance(exc.value.orig, c.exc_type), f"test case {idx} failed, name={c.name}" + + def test_select_invalid_values(self): + engine = sa.create_engine("sqlite://", echo=False) + _Base.metadata.create_all(engine) + + insertion_sql = """ + INSERT INTO users (id, name, user_type) VALUES + (1, 'invalid_value', 'invalid'); + """ + with Session(engine) as session: + session.execute(sa.text(insertion_sql)) + session.commit() + + with pytest.raises(ValueError) as exc: + with Session(engine) as session: + _user = session.query(_User).filter(_User.id == 1).first() diff --git a/api/tests/unit_tests/models/test_workflow.py b/api/tests/unit_tests/models/test_workflow.py index fe56f18f1b..b79e95c7ed 100644 --- a/api/tests/unit_tests/models/test_workflow.py +++ b/api/tests/unit_tests/models/test_workflow.py @@ -1,14 +1,14 @@ +import json from unittest import mock from uuid import uuid4 -import contexts from constants import HIDDEN_VALUE from core.variables import FloatVariable, IntegerVariable, SecretVariable, StringVariable -from models.workflow import Workflow +from models.workflow import Workflow, WorkflowNodeExecutionModel def test_environment_variables(): - contexts.tenant_id.set("tenant_id") + # tenant_id context variable removed - using current_user.current_tenant_id directly # Create a Workflow instance workflow = Workflow( @@ -37,9 +37,14 @@ def test_environment_variables(): {"name": "var4", "value": 3.14, "id": str(uuid4()), "selector": ["env", "var4"]} ) + # Mock current_user as an EndUser + mock_user = mock.Mock() + mock_user.tenant_id = "tenant_id" + with ( mock.patch("core.helper.encrypter.encrypt_token", return_value="encrypted_token"), mock.patch("core.helper.encrypter.decrypt_token", return_value="secret"), + mock.patch("models.workflow.current_user", mock_user), ): # Set the environment_variables property of the Workflow instance variables = [variable1, variable2, variable3, variable4] @@ -50,7 +55,7 @@ def test_environment_variables(): def test_update_environment_variables(): - contexts.tenant_id.set("tenant_id") + # tenant_id context variable removed - using current_user.current_tenant_id directly # Create a Workflow instance workflow = Workflow( @@ -79,9 +84,14 @@ def test_update_environment_variables(): {"name": "var4", "value": 3.14, "id": str(uuid4()), "selector": ["env", "var4"]} ) + # Mock current_user as an EndUser + mock_user = mock.Mock() + mock_user.tenant_id = "tenant_id" + with ( mock.patch("core.helper.encrypter.encrypt_token", return_value="encrypted_token"), mock.patch("core.helper.encrypter.decrypt_token", return_value="secret"), + mock.patch("models.workflow.current_user", mock_user), ): variables = [variable1, variable2, variable3, variable4] @@ -103,7 +113,7 @@ def test_update_environment_variables(): def test_to_dict(): - contexts.tenant_id.set("tenant_id") + # tenant_id context variable removed - using current_user.current_tenant_id directly # Create a Workflow instance workflow = Workflow( @@ -120,9 +130,14 @@ def test_to_dict(): # Create some EnvironmentVariable instances + # Mock current_user as an EndUser + mock_user = mock.Mock() + mock_user.tenant_id = "tenant_id" + with ( mock.patch("core.helper.encrypter.encrypt_token", return_value="encrypted_token"), mock.patch("core.helper.encrypter.decrypt_token", return_value="secret"), + mock.patch("models.workflow.current_user", mock_user), ): # Set the environment_variables property of the Workflow instance workflow.environment_variables = [ @@ -137,3 +152,14 @@ def test_to_dict(): workflow_dict = workflow.to_dict(include_secret=True) assert workflow_dict["environment_variables"][0]["value"] == "secret" assert workflow_dict["environment_variables"][1]["value"] == "text" + + +class TestWorkflowNodeExecution: + def test_execution_metadata_dict(self): + node_exec = WorkflowNodeExecutionModel() + node_exec.execution_metadata = None + assert node_exec.execution_metadata_dict == {} + + original = {"a": 1, "b": ["2"]} + node_exec.execution_metadata = json.dumps(original) + assert node_exec.execution_metadata_dict == original diff --git a/api/tests/unit_tests/repositories/workflow_node_execution/test_sqlalchemy_repository.py b/api/tests/unit_tests/repositories/workflow_node_execution/test_sqlalchemy_repository.py index 36847f8a13..643efb0a0c 100644 --- a/api/tests/unit_tests/repositories/workflow_node_execution/test_sqlalchemy_repository.py +++ b/api/tests/unit_tests/repositories/workflow_node_execution/test_sqlalchemy_repository.py @@ -2,15 +2,41 @@ Unit tests for the SQLAlchemy implementation of WorkflowNodeExecutionRepository. """ -from unittest.mock import MagicMock +import json +from datetime import datetime +from decimal import Decimal +from unittest.mock import MagicMock, PropertyMock import pytest from pytest_mock import MockerFixture from sqlalchemy.orm import Session, sessionmaker -from core.repository.workflow_node_execution_repository import OrderConfig -from models.workflow import WorkflowNodeExecution -from repositories.workflow_node_execution.sqlalchemy_repository import SQLAlchemyWorkflowNodeExecutionRepository +from core.model_runtime.utils.encoders import jsonable_encoder +from core.repositories import SQLAlchemyWorkflowNodeExecutionRepository +from core.workflow.entities.workflow_node_execution import ( + WorkflowNodeExecution, + WorkflowNodeExecutionMetadataKey, + WorkflowNodeExecutionStatus, +) +from core.workflow.nodes.enums import NodeType +from core.workflow.repositories.workflow_node_execution_repository import OrderConfig +from models.account import Account, Tenant +from models.workflow import WorkflowNodeExecutionModel, WorkflowNodeExecutionTriggeredFrom + + +def configure_mock_execution(mock_execution): + """Configure a mock execution with proper JSON serializable values.""" + # Configure inputs, outputs, process_data, and execution_metadata to return JSON serializable values + type(mock_execution).inputs = PropertyMock(return_value='{"key": "value"}') + type(mock_execution).outputs = PropertyMock(return_value='{"result": "success"}') + type(mock_execution).process_data = PropertyMock(return_value='{"process": "data"}') + type(mock_execution).execution_metadata = PropertyMock(return_value='{"metadata": "info"}') + + # Configure status and triggered_from to be valid enum values + mock_execution.status = "running" + mock_execution.triggered_from = "workflow-run" + + return mock_execution @pytest.fixture @@ -28,13 +54,30 @@ def session(): @pytest.fixture -def repository(session): +def mock_user(): + """Create a user instance for testing.""" + user = Account() + user.id = "test-user-id" + + tenant = Tenant() + tenant.id = "test-tenant" + tenant.name = "Test Workspace" + user._current_tenant = MagicMock() + user._current_tenant.id = "test-tenant" + + return user + + +@pytest.fixture +def repository(session, mock_user): """Create a repository instance with test data.""" _, session_factory = session - tenant_id = "test-tenant" app_id = "test-app" return SQLAlchemyWorkflowNodeExecutionRepository( - session_factory=session_factory, tenant_id=tenant_id, app_id=app_id + session_factory=session_factory, + user=mock_user, + app_id=app_id, + triggered_from=WorkflowNodeExecutionTriggeredFrom.WORKFLOW_RUN, ) @@ -42,49 +85,76 @@ def test_save(repository, session): """Test save method.""" session_obj, _ = session # Create a mock execution - execution = MagicMock(spec=WorkflowNodeExecution) + execution = MagicMock(spec=WorkflowNodeExecutionModel) execution.tenant_id = None execution.app_id = None + execution.inputs = None + execution.process_data = None + execution.outputs = None + execution.metadata = None + + # Mock the to_db_model method to return the execution itself + # This simulates the behavior of setting tenant_id and app_id + repository.to_db_model = MagicMock(return_value=execution) # Call save method repository.save(execution) - # Assert tenant_id and app_id are set - assert execution.tenant_id == repository._tenant_id - assert execution.app_id == repository._app_id + # Assert to_db_model was called with the execution + repository.to_db_model.assert_called_once_with(execution) - # Assert session.add was called - session_obj.add.assert_called_once_with(execution) + # Assert session.merge was called (now using merge for both save and update) + session_obj.merge.assert_called_once_with(execution) def test_save_with_existing_tenant_id(repository, session): """Test save method with existing tenant_id.""" session_obj, _ = session # Create a mock execution with existing tenant_id - execution = MagicMock(spec=WorkflowNodeExecution) + execution = MagicMock(spec=WorkflowNodeExecutionModel) execution.tenant_id = "existing-tenant" execution.app_id = None + execution.inputs = None + execution.process_data = None + execution.outputs = None + execution.metadata = None + + # Create a modified execution that will be returned by _to_db_model + modified_execution = MagicMock(spec=WorkflowNodeExecutionModel) + modified_execution.tenant_id = "existing-tenant" # Tenant ID should not change + modified_execution.app_id = repository._app_id # App ID should be set + + # Mock the to_db_model method to return the modified execution + repository.to_db_model = MagicMock(return_value=modified_execution) # Call save method repository.save(execution) - # Assert tenant_id is not changed and app_id is set - assert execution.tenant_id == "existing-tenant" - assert execution.app_id == repository._app_id + # Assert to_db_model was called with the execution + repository.to_db_model.assert_called_once_with(execution) - # Assert session.add was called - session_obj.add.assert_called_once_with(execution) + # Assert session.merge was called with the modified execution (now using merge for both save and update) + session_obj.merge.assert_called_once_with(modified_execution) def test_get_by_node_execution_id(repository, session, mocker: MockerFixture): """Test get_by_node_execution_id method.""" session_obj, _ = session # Set up mock - mock_select = mocker.patch("repositories.workflow_node_execution.sqlalchemy_repository.select") + mock_select = mocker.patch("core.repositories.sqlalchemy_workflow_node_execution_repository.select") mock_stmt = mocker.MagicMock() mock_select.return_value = mock_stmt mock_stmt.where.return_value = mock_stmt - session_obj.scalar.return_value = mocker.MagicMock(spec=WorkflowNodeExecution) + + # Create a properly configured mock execution + mock_execution = mocker.MagicMock(spec=WorkflowNodeExecutionModel) + configure_mock_execution(mock_execution) + session_obj.scalar.return_value = mock_execution + + # Create a mock domain model to be returned by _to_domain_model + mock_domain_model = mocker.MagicMock() + # Mock the _to_domain_model method to return our mock domain model + repository._to_domain_model = mocker.MagicMock(return_value=mock_domain_model) # Call method result = repository.get_by_node_execution_id("test-node-execution-id") @@ -92,19 +162,31 @@ def test_get_by_node_execution_id(repository, session, mocker: MockerFixture): # Assert select was called with correct parameters mock_select.assert_called_once() session_obj.scalar.assert_called_once_with(mock_stmt) - assert result is not None + # Assert _to_domain_model was called with the mock execution + repository._to_domain_model.assert_called_once_with(mock_execution) + # Assert the result is our mock domain model + assert result is mock_domain_model def test_get_by_workflow_run(repository, session, mocker: MockerFixture): """Test get_by_workflow_run method.""" session_obj, _ = session # Set up mock - mock_select = mocker.patch("repositories.workflow_node_execution.sqlalchemy_repository.select") + mock_select = mocker.patch("core.repositories.sqlalchemy_workflow_node_execution_repository.select") mock_stmt = mocker.MagicMock() mock_select.return_value = mock_stmt mock_stmt.where.return_value = mock_stmt mock_stmt.order_by.return_value = mock_stmt - session_obj.scalars.return_value.all.return_value = [mocker.MagicMock(spec=WorkflowNodeExecution)] + + # Create a properly configured mock execution + mock_execution = mocker.MagicMock(spec=WorkflowNodeExecutionModel) + configure_mock_execution(mock_execution) + session_obj.scalars.return_value.all.return_value = [mock_execution] + + # Create a mock domain model to be returned by _to_domain_model + mock_domain_model = mocker.MagicMock() + # Mock the _to_domain_model method to return our mock domain model + repository._to_domain_model = mocker.MagicMock(return_value=mock_domain_model) # Call method order_config = OrderConfig(order_by=["index"], order_direction="desc") @@ -113,18 +195,31 @@ def test_get_by_workflow_run(repository, session, mocker: MockerFixture): # Assert select was called with correct parameters mock_select.assert_called_once() session_obj.scalars.assert_called_once_with(mock_stmt) + # Assert _to_domain_model was called with the mock execution + repository._to_domain_model.assert_called_once_with(mock_execution) + # Assert the result contains our mock domain model assert len(result) == 1 + assert result[0] is mock_domain_model def test_get_running_executions(repository, session, mocker: MockerFixture): """Test get_running_executions method.""" session_obj, _ = session # Set up mock - mock_select = mocker.patch("repositories.workflow_node_execution.sqlalchemy_repository.select") + mock_select = mocker.patch("core.repositories.sqlalchemy_workflow_node_execution_repository.select") mock_stmt = mocker.MagicMock() mock_select.return_value = mock_stmt mock_stmt.where.return_value = mock_stmt - session_obj.scalars.return_value.all.return_value = [mocker.MagicMock(spec=WorkflowNodeExecution)] + + # Create a properly configured mock execution + mock_execution = mocker.MagicMock(spec=WorkflowNodeExecutionModel) + configure_mock_execution(mock_execution) + session_obj.scalars.return_value.all.return_value = [mock_execution] + + # Create a mock domain model to be returned by _to_domain_model + mock_domain_model = mocker.MagicMock() + # Mock the _to_domain_model method to return our mock domain model + repository._to_domain_model = mocker.MagicMock(return_value=mock_domain_model) # Call method result = repository.get_running_executions("test-workflow-run-id") @@ -132,25 +227,36 @@ def test_get_running_executions(repository, session, mocker: MockerFixture): # Assert select was called with correct parameters mock_select.assert_called_once() session_obj.scalars.assert_called_once_with(mock_stmt) + # Assert _to_domain_model was called with the mock execution + repository._to_domain_model.assert_called_once_with(mock_execution) + # Assert the result contains our mock domain model assert len(result) == 1 + assert result[0] is mock_domain_model -def test_update(repository, session): - """Test update method.""" +def test_update_via_save(repository, session): + """Test updating an existing record via save method.""" session_obj, _ = session # Create a mock execution - execution = MagicMock(spec=WorkflowNodeExecution) + execution = MagicMock(spec=WorkflowNodeExecutionModel) execution.tenant_id = None execution.app_id = None + execution.inputs = None + execution.process_data = None + execution.outputs = None + execution.metadata = None - # Call update method - repository.update(execution) + # Mock the to_db_model method to return the execution itself + # This simulates the behavior of setting tenant_id and app_id + repository.to_db_model = MagicMock(return_value=execution) - # Assert tenant_id and app_id are set - assert execution.tenant_id == repository._tenant_id - assert execution.app_id == repository._app_id + # Call save method to update an existing record + repository.save(execution) + + # Assert to_db_model was called with the execution + repository.to_db_model.assert_called_once_with(execution) - # Assert session.merge was called + # Assert session.merge was called (for updates) session_obj.merge.assert_called_once_with(execution) @@ -158,7 +264,7 @@ def test_clear(repository, session, mocker: MockerFixture): """Test clear method.""" session_obj, _ = session # Set up mock - mock_delete = mocker.patch("repositories.workflow_node_execution.sqlalchemy_repository.delete") + mock_delete = mocker.patch("core.repositories.sqlalchemy_workflow_node_execution_repository.delete") mock_stmt = mocker.MagicMock() mock_delete.return_value = mock_stmt mock_stmt.where.return_value = mock_stmt @@ -172,7 +278,125 @@ def test_clear(repository, session, mocker: MockerFixture): repository.clear() # Assert delete was called with correct parameters - mock_delete.assert_called_once_with(WorkflowNodeExecution) + mock_delete.assert_called_once_with(WorkflowNodeExecutionModel) mock_stmt.where.assert_called() session_obj.execute.assert_called_once_with(mock_stmt) session_obj.commit.assert_called_once() + + +def test_to_db_model(repository): + """Test to_db_model method.""" + # Create a domain model + domain_model = WorkflowNodeExecution( + id="test-id", + workflow_id="test-workflow-id", + node_execution_id="test-node-execution-id", + workflow_execution_id="test-workflow-run-id", + index=1, + predecessor_node_id="test-predecessor-id", + node_id="test-node-id", + node_type=NodeType.START, + title="Test Node", + inputs={"input_key": "input_value"}, + process_data={"process_key": "process_value"}, + outputs={"output_key": "output_value"}, + status=WorkflowNodeExecutionStatus.RUNNING, + error=None, + elapsed_time=1.5, + metadata={ + WorkflowNodeExecutionMetadataKey.TOTAL_TOKENS: 100, + WorkflowNodeExecutionMetadataKey.TOTAL_PRICE: Decimal("0.0"), + }, + created_at=datetime.now(), + finished_at=None, + ) + + # Convert to DB model + db_model = repository.to_db_model(domain_model) + + # Assert DB model has correct values + assert isinstance(db_model, WorkflowNodeExecutionModel) + assert db_model.id == domain_model.id + assert db_model.tenant_id == repository._tenant_id + assert db_model.app_id == repository._app_id + assert db_model.workflow_id == domain_model.workflow_id + assert db_model.triggered_from == repository._triggered_from + assert db_model.workflow_run_id == domain_model.workflow_execution_id + assert db_model.index == domain_model.index + assert db_model.predecessor_node_id == domain_model.predecessor_node_id + assert db_model.node_execution_id == domain_model.node_execution_id + assert db_model.node_id == domain_model.node_id + assert db_model.node_type == domain_model.node_type + assert db_model.title == domain_model.title + + assert db_model.inputs_dict == domain_model.inputs + assert db_model.process_data_dict == domain_model.process_data + assert db_model.outputs_dict == domain_model.outputs + assert db_model.execution_metadata_dict == jsonable_encoder(domain_model.metadata) + + assert db_model.status == domain_model.status + assert db_model.error == domain_model.error + assert db_model.elapsed_time == domain_model.elapsed_time + assert db_model.created_at == domain_model.created_at + assert db_model.created_by_role == repository._creator_user_role + assert db_model.created_by == repository._creator_user_id + assert db_model.finished_at == domain_model.finished_at + + +def test_to_domain_model(repository): + """Test _to_domain_model method.""" + # Create input dictionaries + inputs_dict = {"input_key": "input_value"} + process_data_dict = {"process_key": "process_value"} + outputs_dict = {"output_key": "output_value"} + metadata_dict = {str(WorkflowNodeExecutionMetadataKey.TOTAL_TOKENS): 100} + + # Create a DB model using our custom subclass + db_model = WorkflowNodeExecutionModel() + db_model.id = "test-id" + db_model.tenant_id = "test-tenant-id" + db_model.app_id = "test-app-id" + db_model.workflow_id = "test-workflow-id" + db_model.triggered_from = "workflow-run" + db_model.workflow_run_id = "test-workflow-run-id" + db_model.index = 1 + db_model.predecessor_node_id = "test-predecessor-id" + db_model.node_execution_id = "test-node-execution-id" + db_model.node_id = "test-node-id" + db_model.node_type = NodeType.START.value + db_model.title = "Test Node" + db_model.inputs = json.dumps(inputs_dict) + db_model.process_data = json.dumps(process_data_dict) + db_model.outputs = json.dumps(outputs_dict) + db_model.status = WorkflowNodeExecutionStatus.RUNNING + db_model.error = None + db_model.elapsed_time = 1.5 + db_model.execution_metadata = json.dumps(metadata_dict) + db_model.created_at = datetime.now() + db_model.created_by_role = "account" + db_model.created_by = "test-user-id" + db_model.finished_at = None + + # Convert to domain model + domain_model = repository._to_domain_model(db_model) + + # Assert domain model has correct values + assert isinstance(domain_model, WorkflowNodeExecution) + assert domain_model.id == db_model.id + assert domain_model.workflow_id == db_model.workflow_id + assert domain_model.workflow_execution_id == db_model.workflow_run_id + assert domain_model.index == db_model.index + assert domain_model.predecessor_node_id == db_model.predecessor_node_id + assert domain_model.node_execution_id == db_model.node_execution_id + assert domain_model.node_id == db_model.node_id + assert domain_model.node_type == NodeType(db_model.node_type) + assert domain_model.title == db_model.title + assert domain_model.inputs == inputs_dict + assert domain_model.process_data == process_data_dict + assert domain_model.outputs == outputs_dict + assert domain_model.status == WorkflowNodeExecutionStatus(db_model.status) + assert domain_model.error == db_model.error + assert domain_model.elapsed_time == db_model.elapsed_time + assert domain_model.metadata == metadata_dict + assert domain_model.created_at == db_model.created_at + assert domain_model.finished_at == db_model.finished_at diff --git a/api/tests/unit_tests/services/test_dataset_permission.py b/api/tests/unit_tests/services/test_dataset_permission.py new file mode 100644 index 0000000000..066f541c1b --- /dev/null +++ b/api/tests/unit_tests/services/test_dataset_permission.py @@ -0,0 +1,158 @@ +from unittest.mock import Mock, patch + +import pytest + +from models.account import Account, TenantAccountRole +from models.dataset import Dataset, DatasetPermission, DatasetPermissionEnum +from services.dataset_service import DatasetService +from services.errors.account import NoPermissionError + + +class TestDatasetPermissionService: + """Test cases for dataset permission checking functionality""" + + def setup_method(self): + """Set up test fixtures""" + # Mock tenant and user + self.tenant_id = "test-tenant-123" + self.creator_id = "creator-456" + self.normal_user_id = "normal-789" + self.owner_user_id = "owner-999" + + # Mock dataset + self.dataset = Mock(spec=Dataset) + self.dataset.id = "dataset-123" + self.dataset.tenant_id = self.tenant_id + self.dataset.created_by = self.creator_id + + # Mock users + self.creator_user = Mock(spec=Account) + self.creator_user.id = self.creator_id + self.creator_user.current_tenant_id = self.tenant_id + self.creator_user.current_role = TenantAccountRole.EDITOR + + self.normal_user = Mock(spec=Account) + self.normal_user.id = self.normal_user_id + self.normal_user.current_tenant_id = self.tenant_id + self.normal_user.current_role = TenantAccountRole.NORMAL + + self.owner_user = Mock(spec=Account) + self.owner_user.id = self.owner_user_id + self.owner_user.current_tenant_id = self.tenant_id + self.owner_user.current_role = TenantAccountRole.OWNER + + def test_permission_check_different_tenant_should_fail(self): + """Test that users from different tenants cannot access dataset""" + self.normal_user.current_tenant_id = "different-tenant" + + with pytest.raises(NoPermissionError, match="You do not have permission to access this dataset."): + DatasetService.check_dataset_permission(self.dataset, self.normal_user) + + def test_owner_can_access_any_dataset(self): + """Test that tenant owners can access any dataset regardless of permission""" + self.dataset.permission = DatasetPermissionEnum.ONLY_ME + + # Should not raise any exception + DatasetService.check_dataset_permission(self.dataset, self.owner_user) + + def test_only_me_permission_creator_can_access(self): + """Test ONLY_ME permission allows only creator to access""" + self.dataset.permission = DatasetPermissionEnum.ONLY_ME + + # Creator should be able to access + DatasetService.check_dataset_permission(self.dataset, self.creator_user) + + def test_only_me_permission_others_cannot_access(self): + """Test ONLY_ME permission denies access to non-creators""" + self.dataset.permission = DatasetPermissionEnum.ONLY_ME + + with pytest.raises(NoPermissionError, match="You do not have permission to access this dataset."): + DatasetService.check_dataset_permission(self.dataset, self.normal_user) + + def test_all_team_permission_allows_access(self): + """Test ALL_TEAM permission allows any team member to access""" + self.dataset.permission = DatasetPermissionEnum.ALL_TEAM + + # Should not raise any exception for team members + DatasetService.check_dataset_permission(self.dataset, self.normal_user) + DatasetService.check_dataset_permission(self.dataset, self.creator_user) + + @patch("services.dataset_service.db.session") + def test_partial_team_permission_creator_can_access(self, mock_session): + """Test PARTIAL_TEAM permission allows creator to access""" + self.dataset.permission = DatasetPermissionEnum.PARTIAL_TEAM + + # Should not raise any exception for creator + DatasetService.check_dataset_permission(self.dataset, self.creator_user) + + # Should not query database for creator + mock_session.query.assert_not_called() + + @patch("services.dataset_service.db.session") + def test_partial_team_permission_with_explicit_permission(self, mock_session): + """Test PARTIAL_TEAM permission allows users with explicit permission""" + self.dataset.permission = DatasetPermissionEnum.PARTIAL_TEAM + + # Mock database query to return a permission record + mock_permission = Mock(spec=DatasetPermission) + mock_session.query().filter_by().first.return_value = mock_permission + + # Should not raise any exception + DatasetService.check_dataset_permission(self.dataset, self.normal_user) + + # Verify database was queried correctly + mock_session.query().filter_by.assert_called_with(dataset_id=self.dataset.id, account_id=self.normal_user.id) + + @patch("services.dataset_service.db.session") + def test_partial_team_permission_without_explicit_permission(self, mock_session): + """Test PARTIAL_TEAM permission denies users without explicit permission""" + self.dataset.permission = DatasetPermissionEnum.PARTIAL_TEAM + + # Mock database query to return None (no permission record) + mock_session.query().filter_by().first.return_value = None + + with pytest.raises(NoPermissionError, match="You do not have permission to access this dataset."): + DatasetService.check_dataset_permission(self.dataset, self.normal_user) + + # Verify database was queried correctly + mock_session.query().filter_by.assert_called_with(dataset_id=self.dataset.id, account_id=self.normal_user.id) + + @patch("services.dataset_service.db.session") + def test_partial_team_permission_non_creator_without_permission_fails(self, mock_session): + """Test that non-creators without explicit permission are denied access""" + self.dataset.permission = DatasetPermissionEnum.PARTIAL_TEAM + + # Create a different user (not the creator) + other_user = Mock(spec=Account) + other_user.id = "other-user-123" + other_user.current_tenant_id = self.tenant_id + other_user.current_role = TenantAccountRole.NORMAL + + # Mock database query to return None (no permission record) + mock_session.query().filter_by().first.return_value = None + + with pytest.raises(NoPermissionError, match="You do not have permission to access this dataset."): + DatasetService.check_dataset_permission(self.dataset, other_user) + + def test_partial_team_permission_uses_correct_enum(self): + """Test that the method correctly uses DatasetPermissionEnum.PARTIAL_TEAM""" + # This test ensures we're using the enum instead of string literals + self.dataset.permission = DatasetPermissionEnum.PARTIAL_TEAM + + # Creator should always have access + DatasetService.check_dataset_permission(self.dataset, self.creator_user) + + @patch("services.dataset_service.logging") + @patch("services.dataset_service.db.session") + def test_permission_denied_logs_debug_message(self, mock_session, mock_logging): + """Test that permission denied events are logged""" + self.dataset.permission = DatasetPermissionEnum.PARTIAL_TEAM + mock_session.query().filter_by().first.return_value = None + + with pytest.raises(NoPermissionError): + DatasetService.check_dataset_permission(self.dataset, self.normal_user) + + # Verify debug message was logged + mock_logging.debug.assert_called_with( + f"User {self.normal_user.id} does not have permission to access dataset {self.dataset.id}" + ) diff --git a/api/tests/unit_tests/utils/http_parser/test_oauth_convert_request_to_raw_data.py b/api/tests/unit_tests/utils/http_parser/test_oauth_convert_request_to_raw_data.py new file mode 100644 index 0000000000..f788a9756b --- /dev/null +++ b/api/tests/unit_tests/utils/http_parser/test_oauth_convert_request_to_raw_data.py @@ -0,0 +1,20 @@ +from werkzeug import Request +from werkzeug.datastructures import Headers +from werkzeug.test import EnvironBuilder + +from core.plugin.impl.oauth import OAuthHandler + + +def test_oauth_convert_request_to_raw_data(): + oauth_handler = OAuthHandler() + builder = EnvironBuilder( + method="GET", + path="/test", + headers=Headers({"Content-Type": "application/json"}), + ) + request = Request(builder.get_environ()) + raw_request_bytes = oauth_handler._convert_request_to_raw_data(request) + + assert b"GET /test HTTP/1.1" in raw_request_bytes + assert b"Content-Type: application/json" in raw_request_bytes + assert b"\r\n\r\n" in raw_request_bytes diff --git a/api/uv.lock b/api/uv.lock index 6c8699dd7c..c900191ceb 100644 --- a/api/uv.lock +++ b/api/uv.lock @@ -1,35 +1,42 @@ version = 1 +revision = 2 requires-python = ">=3.11, <3.13" resolution-markers = [ - "python_full_version >= '3.12.4' and platform_python_implementation != 'PyPy'", - "python_full_version >= '3.12' and python_full_version < '3.12.4' and platform_python_implementation != 'PyPy'", - "python_full_version >= '3.12.4' and platform_python_implementation == 'PyPy'", - "python_full_version >= '3.12' and python_full_version < '3.12.4' and platform_python_implementation == 'PyPy'", - "python_full_version < '3.12' and platform_python_implementation != 'PyPy'", - "python_full_version < '3.12' and platform_python_implementation == 'PyPy'", + "python_full_version >= '3.12.4' and platform_python_implementation != 'PyPy' and sys_platform == 'linux'", + "python_full_version >= '3.12.4' and platform_python_implementation != 'PyPy' and sys_platform != 'linux'", + "python_full_version >= '3.12' and python_full_version < '3.12.4' and platform_python_implementation != 'PyPy' and sys_platform == 'linux'", + "python_full_version >= '3.12' and python_full_version < '3.12.4' and platform_python_implementation != 'PyPy' and sys_platform != 'linux'", + "python_full_version >= '3.12.4' and platform_python_implementation == 'PyPy' and sys_platform == 'linux'", + "python_full_version >= '3.12.4' and platform_python_implementation == 'PyPy' and sys_platform != 'linux'", + "python_full_version >= '3.12' and python_full_version < '3.12.4' and platform_python_implementation == 'PyPy' and sys_platform == 'linux'", + "python_full_version >= '3.12' and python_full_version < '3.12.4' and platform_python_implementation == 'PyPy' and sys_platform != 'linux'", + "python_full_version < '3.12' and platform_python_implementation != 'PyPy' and sys_platform == 'linux'", + "python_full_version < '3.12' and platform_python_implementation != 'PyPy' and sys_platform != 'linux'", + "python_full_version < '3.12' and platform_python_implementation == 'PyPy' and sys_platform == 'linux'", + "python_full_version < '3.12' and platform_python_implementation == 'PyPy' and sys_platform != 'linux'", ] [[package]] name = "aiofiles" version = "24.1.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/0b/03/a88171e277e8caa88a4c77808c20ebb04ba74cc4681bf1e9416c862de237/aiofiles-24.1.0.tar.gz", hash = "sha256:22a075c9e5a3810f0c2e48f3008c94d68c65d763b9b03857924c99e57355166c", size = 30247 } +sdist = { url = "https://files.pythonhosted.org/packages/0b/03/a88171e277e8caa88a4c77808c20ebb04ba74cc4681bf1e9416c862de237/aiofiles-24.1.0.tar.gz", hash = "sha256:22a075c9e5a3810f0c2e48f3008c94d68c65d763b9b03857924c99e57355166c", size = 30247, upload-time = "2024-06-24T11:02:03.584Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a5/45/30bb92d442636f570cb5651bc661f52b610e2eec3f891a5dc3a4c3667db0/aiofiles-24.1.0-py3-none-any.whl", hash = "sha256:b4ec55f4195e3eb5d7abd1bf7e061763e864dd4954231fb8539a0ef8bb8260e5", size = 15896 }, + { url = "https://files.pythonhosted.org/packages/a5/45/30bb92d442636f570cb5651bc661f52b610e2eec3f891a5dc3a4c3667db0/aiofiles-24.1.0-py3-none-any.whl", hash = "sha256:b4ec55f4195e3eb5d7abd1bf7e061763e864dd4954231fb8539a0ef8bb8260e5", size = 15896, upload-time = "2024-06-24T11:02:01.529Z" }, ] [[package]] name = "aiohappyeyeballs" version = "2.6.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/26/30/f84a107a9c4331c14b2b586036f40965c128aa4fee4dda5d3d51cb14ad54/aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558", size = 22760 } +sdist = { url = "https://files.pythonhosted.org/packages/26/30/f84a107a9c4331c14b2b586036f40965c128aa4fee4dda5d3d51cb14ad54/aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558", size = 22760, upload-time = "2025-03-12T01:42:48.764Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/0f/15/5bf3b99495fb160b63f95972b81750f18f7f4e02ad051373b669d17d44f2/aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8", size = 15265 }, + { url = "https://files.pythonhosted.org/packages/0f/15/5bf3b99495fb160b63f95972b81750f18f7f4e02ad051373b669d17d44f2/aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8", size = 15265, upload-time = "2025-03-12T01:42:47.083Z" }, ] [[package]] name = "aiohttp" -version = "3.11.16" +version = "3.12.7" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "aiohappyeyeballs" }, @@ -40,40 +47,42 @@ dependencies = [ { name = "propcache" }, { name = "yarl" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/f1/d9/1c4721d143e14af753f2bf5e3b681883e1f24b592c0482df6fa6e33597fa/aiohttp-3.11.16.tar.gz", hash = "sha256:16f8a2c9538c14a557b4d309ed4d0a7c60f0253e8ed7b6c9a2859a7582f8b1b8", size = 7676826 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b1/98/be30539cd84260d9f3ea1936d50445e25aa6029a4cb9707f3b64cfd710f7/aiohttp-3.11.16-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8cb0688a8d81c63d716e867d59a9ccc389e97ac7037ebef904c2b89334407180", size = 708664 }, - { url = "https://files.pythonhosted.org/packages/e6/27/d51116ce18bdfdea7a2244b55ad38d7b01a4298af55765eed7e8431f013d/aiohttp-3.11.16-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0ad1fb47da60ae1ddfb316f0ff16d1f3b8e844d1a1e154641928ea0583d486ed", size = 468953 }, - { url = "https://files.pythonhosted.org/packages/34/23/eedf80ec42865ea5355b46265a2433134138eff9a4fea17e1348530fa4ae/aiohttp-3.11.16-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:df7db76400bf46ec6a0a73192b14c8295bdb9812053f4fe53f4e789f3ea66bbb", size = 456065 }, - { url = "https://files.pythonhosted.org/packages/36/23/4a5b1ef6cff994936bf96d981dd817b487d9db755457a0d1c2939920d620/aiohttp-3.11.16-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cc3a145479a76ad0ed646434d09216d33d08eef0d8c9a11f5ae5cdc37caa3540", size = 1687976 }, - { url = "https://files.pythonhosted.org/packages/d0/5d/c7474b4c3069bb35276d54c82997dff4f7575e4b73f0a7b1b08a39ece1eb/aiohttp-3.11.16-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d007aa39a52d62373bd23428ba4a2546eed0e7643d7bf2e41ddcefd54519842c", size = 1752711 }, - { url = "https://files.pythonhosted.org/packages/64/4c/ee416987b6729558f2eb1b727c60196580aafdb141e83bd78bb031d1c000/aiohttp-3.11.16-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f6ddd90d9fb4b501c97a4458f1c1720e42432c26cb76d28177c5b5ad4e332601", size = 1791305 }, - { url = "https://files.pythonhosted.org/packages/58/28/3e1e1884070b95f1f69c473a1995852a6f8516670bb1c29d6cb2dbb73e1c/aiohttp-3.11.16-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0a2f451849e6b39e5c226803dcacfa9c7133e9825dcefd2f4e837a2ec5a3bb98", size = 1674499 }, - { url = "https://files.pythonhosted.org/packages/ad/55/a032b32fa80a662d25d9eb170ed1e2c2be239304ca114ec66c89dc40f37f/aiohttp-3.11.16-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8df6612df74409080575dca38a5237282865408016e65636a76a2eb9348c2567", size = 1622313 }, - { url = "https://files.pythonhosted.org/packages/b1/df/ca775605f72abbda4e4746e793c408c84373ca2c6ce7a106a09f853f1e89/aiohttp-3.11.16-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:78e6e23b954644737e385befa0deb20233e2dfddf95dd11e9db752bdd2a294d3", size = 1658274 }, - { url = "https://files.pythonhosted.org/packages/cc/6c/21c45b66124df5b4b0ab638271ecd8c6402b702977120cb4d5be6408e15d/aiohttp-3.11.16-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:696ef00e8a1f0cec5e30640e64eca75d8e777933d1438f4facc9c0cdf288a810", size = 1666704 }, - { url = "https://files.pythonhosted.org/packages/1d/e2/7d92adc03e3458edd18a21da2575ab84e58f16b1672ae98529e4eeee45ab/aiohttp-3.11.16-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e3538bc9fe1b902bef51372462e3d7c96fce2b566642512138a480b7adc9d508", size = 1652815 }, - { url = "https://files.pythonhosted.org/packages/3a/52/7549573cd654ad651e3c5786ec3946d8f0ee379023e22deb503ff856b16c/aiohttp-3.11.16-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:3ab3367bb7f61ad18793fea2ef71f2d181c528c87948638366bf1de26e239183", size = 1735669 }, - { url = "https://files.pythonhosted.org/packages/d5/54/dcd24a23c7a5a2922123e07a296a5f79ea87ce605f531be068415c326de6/aiohttp-3.11.16-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:56a3443aca82abda0e07be2e1ecb76a050714faf2be84256dae291182ba59049", size = 1760422 }, - { url = "https://files.pythonhosted.org/packages/a7/53/87327fe982fa310944e1450e97bf7b2a28015263771931372a1dfe682c58/aiohttp-3.11.16-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:61c721764e41af907c9d16b6daa05a458f066015abd35923051be8705108ed17", size = 1694457 }, - { url = "https://files.pythonhosted.org/packages/ce/6d/c5ccf41059267bcf89853d3db9d8d217dacf0a04f4086cb6bf278323011f/aiohttp-3.11.16-cp311-cp311-win32.whl", hash = "sha256:3e061b09f6fa42997cf627307f220315e313ece74907d35776ec4373ed718b86", size = 416817 }, - { url = "https://files.pythonhosted.org/packages/e7/dd/01f6fe028e054ef4f909c9d63e3a2399e77021bb2e1bb51d56ca8b543989/aiohttp-3.11.16-cp311-cp311-win_amd64.whl", hash = "sha256:745f1ed5e2c687baefc3c5e7b4304e91bf3e2f32834d07baaee243e349624b24", size = 442986 }, - { url = "https://files.pythonhosted.org/packages/db/38/100d01cbc60553743baf0fba658cb125f8ad674a8a771f765cdc155a890d/aiohttp-3.11.16-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:911a6e91d08bb2c72938bc17f0a2d97864c531536b7832abee6429d5296e5b27", size = 704881 }, - { url = "https://files.pythonhosted.org/packages/21/ed/b4102bb6245e36591209e29f03fe87e7956e54cb604ee12e20f7eb47f994/aiohttp-3.11.16-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6ac13b71761e49d5f9e4d05d33683bbafef753e876e8e5a7ef26e937dd766713", size = 464564 }, - { url = "https://files.pythonhosted.org/packages/3b/e1/a9ab6c47b62ecee080eeb33acd5352b40ecad08fb2d0779bcc6739271745/aiohttp-3.11.16-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fd36c119c5d6551bce374fcb5c19269638f8d09862445f85a5a48596fd59f4bb", size = 456548 }, - { url = "https://files.pythonhosted.org/packages/80/ad/216c6f71bdff2becce6c8776f0aa32cb0fa5d83008d13b49c3208d2e4016/aiohttp-3.11.16-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d489d9778522fbd0f8d6a5c6e48e3514f11be81cb0a5954bdda06f7e1594b321", size = 1691749 }, - { url = "https://files.pythonhosted.org/packages/bd/ea/7df7bcd3f4e734301605f686ffc87993f2d51b7acb6bcc9b980af223f297/aiohttp-3.11.16-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:69a2cbd61788d26f8f1e626e188044834f37f6ae3f937bd9f08b65fc9d7e514e", size = 1736874 }, - { url = "https://files.pythonhosted.org/packages/51/41/c7724b9c87a29b7cfd1202ec6446bae8524a751473d25e2ff438bc9a02bf/aiohttp-3.11.16-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd464ba806e27ee24a91362ba3621bfc39dbbb8b79f2e1340201615197370f7c", size = 1786885 }, - { url = "https://files.pythonhosted.org/packages/86/b3/f61f8492fa6569fa87927ad35a40c159408862f7e8e70deaaead349e2fba/aiohttp-3.11.16-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ce63ae04719513dd2651202352a2beb9f67f55cb8490c40f056cea3c5c355ce", size = 1698059 }, - { url = "https://files.pythonhosted.org/packages/ce/be/7097cf860a9ce8bbb0e8960704e12869e111abcd3fbd245153373079ccec/aiohttp-3.11.16-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:09b00dd520d88eac9d1768439a59ab3d145065c91a8fab97f900d1b5f802895e", size = 1626527 }, - { url = "https://files.pythonhosted.org/packages/1d/1d/aaa841c340e8c143a8d53a1f644c2a2961c58cfa26e7b398d6bf75cf5d23/aiohttp-3.11.16-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7f6428fee52d2bcf96a8aa7b62095b190ee341ab0e6b1bcf50c615d7966fd45b", size = 1644036 }, - { url = "https://files.pythonhosted.org/packages/2c/88/59d870f76e9345e2b149f158074e78db457985c2b4da713038d9da3020a8/aiohttp-3.11.16-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:13ceac2c5cdcc3f64b9015710221ddf81c900c5febc505dbd8f810e770011540", size = 1685270 }, - { url = "https://files.pythonhosted.org/packages/2b/b1/c6686948d4c79c3745595efc469a9f8a43cab3c7efc0b5991be65d9e8cb8/aiohttp-3.11.16-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:fadbb8f1d4140825069db3fedbbb843290fd5f5bc0a5dbd7eaf81d91bf1b003b", size = 1650852 }, - { url = "https://files.pythonhosted.org/packages/fe/94/3e42a6916fd3441721941e0f1b8438e1ce2a4c49af0e28e0d3c950c9b3c9/aiohttp-3.11.16-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:6a792ce34b999fbe04a7a71a90c74f10c57ae4c51f65461a411faa70e154154e", size = 1704481 }, - { url = "https://files.pythonhosted.org/packages/b1/6d/6ab5854ff59b27075c7a8c610597d2b6c38945f9a1284ee8758bc3720ff6/aiohttp-3.11.16-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:f4065145bf69de124accdd17ea5f4dc770da0a6a6e440c53f6e0a8c27b3e635c", size = 1735370 }, - { url = "https://files.pythonhosted.org/packages/73/2a/08a68eec3c99a6659067d271d7553e4d490a0828d588e1daa3970dc2b771/aiohttp-3.11.16-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fa73e8c2656a3653ae6c307b3f4e878a21f87859a9afab228280ddccd7369d71", size = 1697619 }, - { url = "https://files.pythonhosted.org/packages/61/d5/fea8dbbfb0cd68fbb56f0ae913270a79422d9a41da442a624febf72d2aaf/aiohttp-3.11.16-cp312-cp312-win32.whl", hash = "sha256:f244b8e541f414664889e2c87cac11a07b918cb4b540c36f7ada7bfa76571ea2", size = 411710 }, - { url = "https://files.pythonhosted.org/packages/33/fb/41cde15fbe51365024550bf77b95a4fc84ef41365705c946da0421f0e1e0/aiohttp-3.11.16-cp312-cp312-win_amd64.whl", hash = "sha256:23a15727fbfccab973343b6d1b7181bfb0b4aa7ae280f36fd2f90f5476805682", size = 438012 }, +sdist = { url = "https://files.pythonhosted.org/packages/eb/62/95588e933dfea06a3af0332990bd19f6768f8f37fa4c0fe33fe4c55cf9d0/aiohttp-3.12.7.tar.gz", hash = "sha256:08bf55b216c779eddb6e41c1841c17d7ddd12776c7d7b36051c0a292a9ca828e", size = 7806530, upload-time = "2025-06-02T16:34:10.399Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/af/19/37560cc111d6fd95ff6c4bd14445e3c629269fce406c89cc7a69a2865ecf/aiohttp-3.12.7-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:388b5947aa6931ef4ce3ed4edde6853e84980677886992cfadcf733dd06eed63", size = 707169, upload-time = "2025-06-02T16:31:39.107Z" }, + { url = "https://files.pythonhosted.org/packages/b9/18/29bbefb094f81a687473c1d31391bf8a4c48c7b5b8559c3679fc14e67597/aiohttp-3.12.7-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9ed5af1cce257cca27a3e920b003b3b397f63418a203064b7d804ea3b45782af", size = 479443, upload-time = "2025-06-02T16:31:41.185Z" }, + { url = "https://files.pythonhosted.org/packages/cf/7d/119f3e012c75a3fe38f86ac1d77f1452779e0e940770d5827d4e62aa5655/aiohttp-3.12.7-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f466ae8f9c02993b7d167be685bdbeb527cf254a3cfcc757697e0e336399d0a2", size = 467706, upload-time = "2025-06-02T16:31:43.401Z" }, + { url = "https://files.pythonhosted.org/packages/83/f1/f61d8573d648e17347ab9a112063e4363664b5b6100792467fbb26028bde/aiohttp-3.12.7-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2be095a420a9f9a12eff343d877ae180dd919238b539431af08cef929e874759", size = 1737902, upload-time = "2025-06-02T16:31:45.948Z" }, + { url = "https://files.pythonhosted.org/packages/4b/f8/7a8a000bc63de3c79aaa8f03b0784e29e9982276f4579c5e2e56d560e403/aiohttp-3.12.7-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:b058cf2ba6adba699960d7bc403411c8a99ab5d3e5ea3eb01473638ae7d1a30e", size = 1686569, upload-time = "2025-06-02T16:31:47.749Z" }, + { url = "https://files.pythonhosted.org/packages/5c/4e/29a5b35ca9a598f51dc7deff4e8403bf813988f30a8b250e25a8442641b7/aiohttp-3.12.7-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9b6a660163b055686dbb0acc961978fd14537eba5d9da6cbdb4dced7a8d3be1a", size = 1785359, upload-time = "2025-06-02T16:31:49.687Z" }, + { url = "https://files.pythonhosted.org/packages/f9/36/0521398a69c40ac24c659b130597e2544cde1d7dd00291b8a6206bb552d0/aiohttp-3.12.7-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d741923905f267ad5d5c8f86a56f9d2beac9f32a36c217c5d9ef65cd74fd8ca0", size = 1824408, upload-time = "2025-06-02T16:31:51.518Z" }, + { url = "https://files.pythonhosted.org/packages/c1/41/79506d76da96399b6b700acbe10b14291547a3b49a1cc7ed2c5edaa199ce/aiohttp-3.12.7-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:519f5454b6018158ae0e789b8f6a88726c47dd680982eb318ef3ca4dee727314", size = 1726867, upload-time = "2025-06-02T16:31:53.277Z" }, + { url = "https://files.pythonhosted.org/packages/32/d1/d59ed16962934b46c7569d04af2dc9638a38ae5812680b9d6c7ee42d770e/aiohttp-3.12.7-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e4eebfe470e22cc4b374d7e32c07e96d777a5c0fa51f3824de68e697da037ec", size = 1663943, upload-time = "2025-06-02T16:31:55.094Z" }, + { url = "https://files.pythonhosted.org/packages/15/d5/971d1b277e6a3d5b679f0c9ba076c343a5125ea2eacc51c23ea7d875d43a/aiohttp-3.12.7-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:74ff39445f94923cf595e9e6dd602ecbe66b12364e2207e61342b8834417f8da", size = 1712217, upload-time = "2025-06-02T16:31:57.336Z" }, + { url = "https://files.pythonhosted.org/packages/e1/9c/c21fd0ba87772f3d1d43cdbfcfd40fe29f37d36a5d73997a8a4d4d1485c3/aiohttp-3.12.7-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:77cb9dba16486ecfeac8076763600b9714941e0ff696e53a30e8d408d9a196ca", size = 1707375, upload-time = "2025-06-02T16:31:59.12Z" }, + { url = "https://files.pythonhosted.org/packages/85/48/bb97ef3a694df852b70e6f5c1aaf3621a3a26b35f0a0d90481f3fdb1ce8b/aiohttp-3.12.7-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:a7b3b9cbe83e3918a1918b0de274884f17b64224c1c9210a6fb0f7c10d246636", size = 1687561, upload-time = "2025-06-02T16:32:01.385Z" }, + { url = "https://files.pythonhosted.org/packages/d5/75/0b85f30ba9eb1dbdb5d3a53d3a0db29990220f69187acb24d06903686c5d/aiohttp-3.12.7-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:6055f53c70938498884e71ca966abe8e9e7558489e13a7e40b6384dee7230d1d", size = 1781163, upload-time = "2025-06-02T16:32:03.176Z" }, + { url = "https://files.pythonhosted.org/packages/92/51/6350a4c485c7d2fb794101d46085c3830485ec1c37738d8af6c9c5ed8e1a/aiohttp-3.12.7-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:8493a42d5b2a736c6804239b985feebeea1c60f8fcb46a3607d6dce3c1a42b12", size = 1801624, upload-time = "2025-06-02T16:32:05.027Z" }, + { url = "https://files.pythonhosted.org/packages/4f/dd/6a75eaaac93b5552090e42c38a576062028ce4af50f0b50ac550332d332c/aiohttp-3.12.7-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:372f2237cade45f563d973c2a913895f2699a892c0eb11c55c6880b6f0acf219", size = 1714679, upload-time = "2025-06-02T16:32:06.837Z" }, + { url = "https://files.pythonhosted.org/packages/de/ad/0574964387d8eed7fcd0c2fe6ef20c4affe0b265c710938d5fffdb3776b5/aiohttp-3.12.7-cp311-cp311-win32.whl", hash = "sha256:41f686749a099b507563a5c0cb4fd77367b05448a2c1758784ad506a28e9e579", size = 424709, upload-time = "2025-06-02T16:32:08.671Z" }, + { url = "https://files.pythonhosted.org/packages/4f/ab/6b82b43abb0990e4452aaef509cf1403ab50c04b5c090f92fb1b255fb319/aiohttp-3.12.7-cp311-cp311-win_amd64.whl", hash = "sha256:7a3691583470d4397aca70fbf8e0f0778b63a2c2a6a23263bdeeb68395972f29", size = 449100, upload-time = "2025-06-02T16:32:10.373Z" }, + { url = "https://files.pythonhosted.org/packages/5d/65/0bd8ccbffa33ee69db9f5c43f3f62fb8b600b607388e9a8deab8962d0523/aiohttp-3.12.7-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:9b9345918f5b5156a5712c37d1d331baf320df67547ea032a49a609b773c3606", size = 698263, upload-time = "2025-06-02T16:32:12.107Z" }, + { url = "https://files.pythonhosted.org/packages/99/64/a48a8abc4e684fb447d1f7b61e7adcb19865b91e20b50595f49b2942fbb3/aiohttp-3.12.7-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:3091b4883f405dbabeb9ea821a25dec16d03a51c3e0d2752fc3ab48b652bf196", size = 472877, upload-time = "2025-06-02T16:32:14.386Z" }, + { url = "https://files.pythonhosted.org/packages/7d/e4/994bc56a7d7733e9cd1f45db8b656e78d51d7a61cefff8043ec4f7d4a23f/aiohttp-3.12.7-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:97fd97abd4cf199eff4041d0346a7dc68b60deab177f01de87283be513ffc3ab", size = 465716, upload-time = "2025-06-02T16:32:16.108Z" }, + { url = "https://files.pythonhosted.org/packages/39/b0/bddc489288a0e3b05fa05387db9caebc38577204a17db0d5428abae524ba/aiohttp-3.12.7-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3a5938973105cd5ff17176e8cb36bc19cac7c82ae7c58c0dbd7e023972d0c708", size = 1712513, upload-time = "2025-06-02T16:32:17.898Z" }, + { url = "https://files.pythonhosted.org/packages/4d/4a/c06d3ce0dc5f96338cc8d18da57d74608585a3751234eeef5952e4f48ade/aiohttp-3.12.7-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e506ae5c4c05d1a1e87edd64b994cea2d49385d41d32e1c6be8764f31cf2245c", size = 1695167, upload-time = "2025-06-02T16:32:20.131Z" }, + { url = "https://files.pythonhosted.org/packages/79/ec/e847fdfe2b1c1f1a2b0ba5343a9b2bd033a0545f8eaf1f7894a6614473ae/aiohttp-3.12.7-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b780b402e6361c4cfcec252580f5ecdd86cb68376520ac34748d3f8b262dd598", size = 1750261, upload-time = "2025-06-02T16:32:22.717Z" }, + { url = "https://files.pythonhosted.org/packages/2c/5e/b832ff59737d99cc5ae51b737c52976d19990ccee922ba6fe811f615e7f9/aiohttp-3.12.7-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cf981bbfb7ff2ebc1b3bfae49d2efe2c51ca1cf3d90867f47c310df65398e85e", size = 1796416, upload-time = "2025-06-02T16:32:25.15Z" }, + { url = "https://files.pythonhosted.org/packages/e0/ff/51ae87efce9b53aafd384179f58923bf178f561897cf80054a440fdf8363/aiohttp-3.12.7-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:94f98e0e5a49f89b252e115844f756c04fc8050f38252a32a3dd994ce8121f10", size = 1715855, upload-time = "2025-06-02T16:32:27.236Z" }, + { url = "https://files.pythonhosted.org/packages/b1/54/5a77116498f84d2503f5588e687eccfa43a85aa2450bc195ee6e5bb75695/aiohttp-3.12.7-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:410e96cc6824fc4ced9703fb2ac2d06c6190d21fc6f5b588f62b1918628449c1", size = 1631656, upload-time = "2025-06-02T16:32:29.15Z" }, + { url = "https://files.pythonhosted.org/packages/46/34/554220592f8ade7f3cabebfb9325e95078f842140f293ced3ab977fd13ec/aiohttp-3.12.7-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:43e93987fe9df4349db8deae7c391695538c35e4ba893133c7e823234f6e4537", size = 1692718, upload-time = "2025-06-02T16:32:31.295Z" }, + { url = "https://files.pythonhosted.org/packages/ff/9d/ae7103bb8c73c3521e38ae8cde301ddc937024b1681ce134bb1ef01be7d0/aiohttp-3.12.7-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:cb3f3dcb59f3e16819a1c7d3fa32e7b87255b661c1e139a1b5940bde270704ab", size = 1714171, upload-time = "2025-06-02T16:32:33.767Z" }, + { url = "https://files.pythonhosted.org/packages/5d/4d/9b8b8f362e36392939019340321f7efcc1807bb2c4cdea8eb1019d3398ff/aiohttp-3.12.7-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:4a46fe4a4c66b2712059e48a8384eb93565fbe3251af4844860fed846ef4ca75", size = 1654822, upload-time = "2025-06-02T16:32:36.23Z" }, + { url = "https://files.pythonhosted.org/packages/48/30/0ca82df423ee346206bc167852c825cd210c11d2f1fa0064a2a55d7f60d5/aiohttp-3.12.7-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:ad01793164661af70918490ef8efc2c09df7a3c686b6c84ca90a2d69cdbc3911", size = 1734385, upload-time = "2025-06-02T16:32:38.171Z" }, + { url = "https://files.pythonhosted.org/packages/43/bd/96d12318c0f82ac8323bd4459ee26291ad220f688988077a21e538b0872c/aiohttp-3.12.7-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:e85c6833be3f49cead2e7bc79080e5c18d6dab9af32226ab5a01dc20c523e7d9", size = 1762356, upload-time = "2025-06-02T16:32:40.142Z" }, + { url = "https://files.pythonhosted.org/packages/6c/39/7a9b706bf42f293415584d60cf35e80d0558929ab70e72cb40b747f0dfc7/aiohttp-3.12.7-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3c9f52149d8249566e72c50c7985c2345521b3b78f84aa86f6f492cd50b14793", size = 1721970, upload-time = "2025-06-02T16:32:42.187Z" }, + { url = "https://files.pythonhosted.org/packages/19/f2/8899367a52dec8100f43036e5a792cfdbae317bf3a80549da90290083ff4/aiohttp-3.12.7-cp312-cp312-win32.whl", hash = "sha256:0e1c33ac0f6a396bcefe9c1d52c9d38a051861885a5c102ca5c8298aba0108fa", size = 419443, upload-time = "2025-06-02T16:32:44.335Z" }, + { url = "https://files.pythonhosted.org/packages/e8/34/ad5225b4edbcc23496537011d67ef1a147c03205c07340f4a50993b219b9/aiohttp-3.12.7-cp312-cp312-win_amd64.whl", hash = "sha256:b4aed5233a9d13e34e8624ecb798533aa2da97e7048cc69671b7a6d7a2efe7e8", size = 445544, upload-time = "2025-06-02T16:32:46.631Z" }, ] [[package]] @@ -83,9 +92,9 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pymysql" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/67/76/2c5b55e4406a1957ffdfd933a94c2517455291c97d2b81cec6813754791a/aiomysql-0.2.0.tar.gz", hash = "sha256:558b9c26d580d08b8c5fd1be23c5231ce3aeff2dadad989540fee740253deb67", size = 114706 } +sdist = { url = "https://files.pythonhosted.org/packages/67/76/2c5b55e4406a1957ffdfd933a94c2517455291c97d2b81cec6813754791a/aiomysql-0.2.0.tar.gz", hash = "sha256:558b9c26d580d08b8c5fd1be23c5231ce3aeff2dadad989540fee740253deb67", size = 114706, upload-time = "2023-06-11T19:57:53.608Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/42/87/c982ee8b333c85b8ae16306387d703a1fcdfc81a2f3f15a24820ab1a512d/aiomysql-0.2.0-py3-none-any.whl", hash = "sha256:b7c26da0daf23a5ec5e0b133c03d20657276e4eae9b73e040b72787f6f6ade0a", size = 44215 }, + { url = "https://files.pythonhosted.org/packages/42/87/c982ee8b333c85b8ae16306387d703a1fcdfc81a2f3f15a24820ab1a512d/aiomysql-0.2.0-py3-none-any.whl", hash = "sha256:b7c26da0daf23a5ec5e0b133c03d20657276e4eae9b73e040b72787f6f6ade0a", size = 44215, upload-time = "2023-06-11T19:57:51.09Z" }, ] [[package]] @@ -95,33 +104,42 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "frozenlist" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ba/b5/6d55e80f6d8a08ce22b982eafa278d823b541c925f11ee774b0b9c43473d/aiosignal-1.3.2.tar.gz", hash = "sha256:a8c255c66fafb1e499c9351d0bf32ff2d8a0321595ebac3b93713656d2436f54", size = 19424 } +sdist = { url = "https://files.pythonhosted.org/packages/ba/b5/6d55e80f6d8a08ce22b982eafa278d823b541c925f11ee774b0b9c43473d/aiosignal-1.3.2.tar.gz", hash = "sha256:a8c255c66fafb1e499c9351d0bf32ff2d8a0321595ebac3b93713656d2436f54", size = 19424, upload-time = "2024-12-13T17:10:40.86Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ec/6a/bc7e17a3e87a2985d3e8f4da4cd0f481060eb78fb08596c42be62c90a4d9/aiosignal-1.3.2-py2.py3-none-any.whl", hash = "sha256:45cde58e409a301715980c2b01d0c28bdde3770d8290b5eb2173759d9acb31a5", size = 7597 }, + { url = "https://files.pythonhosted.org/packages/ec/6a/bc7e17a3e87a2985d3e8f4da4cd0f481060eb78fb08596c42be62c90a4d9/aiosignal-1.3.2-py2.py3-none-any.whl", hash = "sha256:45cde58e409a301715980c2b01d0c28bdde3770d8290b5eb2173759d9acb31a5", size = 7597, upload-time = "2024-12-13T17:10:38.469Z" }, ] [[package]] name = "alembic" -version = "1.15.2" +version = "1.16.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "mako" }, { name = "sqlalchemy" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/e6/57/e314c31b261d1e8a5a5f1908065b4ff98270a778ce7579bd4254477209a7/alembic-1.15.2.tar.gz", hash = "sha256:1c72391bbdeffccfe317eefba686cb9a3c078005478885413b95c3b26c57a8a7", size = 1925573 } +sdist = { url = "https://files.pythonhosted.org/packages/20/89/bfb4fe86e3fc3972d35431af7bedbc60fa606e8b17196704a1747f7aa4c3/alembic-1.16.1.tar.gz", hash = "sha256:43d37ba24b3d17bc1eb1024fe0f51cd1dc95aeb5464594a02c6bb9ca9864bfa4", size = 1955006, upload-time = "2025-05-21T23:11:05.991Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/41/18/d89a443ed1ab9bcda16264716f809c663866d4ca8de218aa78fd50b38ead/alembic-1.15.2-py3-none-any.whl", hash = "sha256:2e76bd916d547f6900ec4bb5a90aeac1485d2c92536923d0b138c02b126edc53", size = 231911 }, + { url = "https://files.pythonhosted.org/packages/31/59/565286efff3692c5716c212202af61466480f6357c4ae3089d4453bff1f3/alembic-1.16.1-py3-none-any.whl", hash = "sha256:0cdd48acada30d93aa1035767d67dff25702f8de74d7c3919f2e8492c8db2e67", size = 242488, upload-time = "2025-05-21T23:11:07.783Z" }, ] [[package]] name = "alibabacloud-credentials" -version = "0.3.6" +version = "1.0.2" source = { registry = "https://pypi.org/simple" } dependencies = [ + { name = "aiofiles" }, + { name = "alibabacloud-credentials-api" }, { name = "alibabacloud-tea" }, + { name = "apscheduler" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/fc/92/7cb0807d6d380fa09cbad6d4fe983781e657dcc16d60fc559d6575bf98be/alibabacloud_credentials-0.3.6.tar.gz", hash = "sha256:caa82cf258648dcbe1ca14aeba50ba21845567d6ac3cd48d318e0a445fff7f96", size = 18771 } +sdist = { url = "https://files.pythonhosted.org/packages/b7/0c/1b0c5f4c2170165719b336616ac0a88f1666fd8690fda41e2e8ae3139fd9/alibabacloud-credentials-1.0.2.tar.gz", hash = "sha256:d2368eb70bd02db9143b2bf531a27a6fecd2cde9601db6e5b48cd6dbe25720ce", size = 30804, upload-time = "2025-05-06T12:30:35.46Z" } + +[[package]] +name = "alibabacloud-credentials-api" +version = "1.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a0/87/1d7019d23891897cb076b2f7e3c81ab3c2ba91de3bb067196f675d60d34c/alibabacloud-credentials-api-1.0.0.tar.gz", hash = "sha256:8c340038d904f0218d7214a8f4088c31912bfcf279af2cbc7d9be4897a97dd2f", size = 2330, upload-time = "2025-01-13T05:53:04.931Z" } [[package]] name = "alibabacloud-endpoint-util" @@ -130,7 +148,7 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "alibabacloud-tea" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/24/54/56736a0f224b3f2b65bb9c9ff9e20fa390351a7587c99f19ca1f8d596ae1/alibabacloud_endpoint_util-0.0.3.tar.gz", hash = "sha256:8c0efb76fdcc3af4ca716ef24bbce770201a3f83f98c0afcf81655f684b9c7d2", size = 2756 } +sdist = { url = "https://files.pythonhosted.org/packages/24/54/56736a0f224b3f2b65bb9c9ff9e20fa390351a7587c99f19ca1f8d596ae1/alibabacloud_endpoint_util-0.0.3.tar.gz", hash = "sha256:8c0efb76fdcc3af4ca716ef24bbce770201a3f83f98c0afcf81655f684b9c7d2", size = 2756, upload-time = "2020-09-16T07:27:42.19Z" } [[package]] name = "alibabacloud-gateway-spi" @@ -139,7 +157,7 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "alibabacloud-credentials" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ab/98/d7111245f17935bf72ee9bea60bbbeff2bc42cdfe24d2544db52bc517e1a/alibabacloud_gateway_spi-0.0.3.tar.gz", hash = "sha256:10d1c53a3fc5f87915fbd6b4985b98338a776e9b44a0263f56643c5048223b8b", size = 4249 } +sdist = { url = "https://files.pythonhosted.org/packages/ab/98/d7111245f17935bf72ee9bea60bbbeff2bc42cdfe24d2544db52bc517e1a/alibabacloud_gateway_spi-0.0.3.tar.gz", hash = "sha256:10d1c53a3fc5f87915fbd6b4985b98338a776e9b44a0263f56643c5048223b8b", size = 4249, upload-time = "2025-02-23T16:29:54.222Z" } [[package]] name = "alibabacloud-gpdb20160503" @@ -155,9 +173,9 @@ dependencies = [ { name = "alibabacloud-tea-openapi" }, { name = "alibabacloud-tea-util" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/15/6a/cc72e744e95c8f37fa6a84e66ae0b9b57a13ee97a0ef03d94c7127c31d75/alibabacloud_gpdb20160503-3.8.3.tar.gz", hash = "sha256:4dfcc0d9cff5a921d529d76f4bf97e2ceb9dc2fa53f00ab055f08509423d8e30", size = 155092 } +sdist = { url = "https://files.pythonhosted.org/packages/15/6a/cc72e744e95c8f37fa6a84e66ae0b9b57a13ee97a0ef03d94c7127c31d75/alibabacloud_gpdb20160503-3.8.3.tar.gz", hash = "sha256:4dfcc0d9cff5a921d529d76f4bf97e2ceb9dc2fa53f00ab055f08509423d8e30", size = 155092, upload-time = "2024-07-18T17:09:42.438Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ab/36/bce41704b3bf59d607590ec73a42a254c5dea27c0f707aee11d20512a200/alibabacloud_gpdb20160503-3.8.3-py3-none-any.whl", hash = "sha256:06e1c46ce5e4e9d1bcae76e76e51034196c625799d06b2efec8d46a7df323fe8", size = 156097 }, + { url = "https://files.pythonhosted.org/packages/ab/36/bce41704b3bf59d607590ec73a42a254c5dea27c0f707aee11d20512a200/alibabacloud_gpdb20160503-3.8.3-py3-none-any.whl", hash = "sha256:06e1c46ce5e4e9d1bcae76e76e51034196c625799d06b2efec8d46a7df323fe8", size = 156097, upload-time = "2024-07-18T17:09:40.414Z" }, ] [[package]] @@ -168,7 +186,7 @@ dependencies = [ { name = "alibabacloud-tea-util" }, { name = "cryptography" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/f6/50/5f41ab550d7874c623f6e992758429802c4b52a6804db437017e5387de33/alibabacloud_openapi_util-0.2.2.tar.gz", hash = "sha256:ebbc3906f554cb4bf8f513e43e8a33e8b6a3d4a0ef13617a0e14c3dda8ef52a8", size = 7201 } +sdist = { url = "https://files.pythonhosted.org/packages/f6/50/5f41ab550d7874c623f6e992758429802c4b52a6804db437017e5387de33/alibabacloud_openapi_util-0.2.2.tar.gz", hash = "sha256:ebbc3906f554cb4bf8f513e43e8a33e8b6a3d4a0ef13617a0e14c3dda8ef52a8", size = 7201, upload-time = "2023-10-23T07:44:18.523Z" } [[package]] name = "alibabacloud-openplatform20191219" @@ -180,14 +198,14 @@ dependencies = [ { name = "alibabacloud-tea-openapi" }, { name = "alibabacloud-tea-util" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/4f/bf/f7fa2f3657ed352870f442434cb2f27b7f70dcd52a544a1f3998eeaf6d71/alibabacloud_openplatform20191219-2.0.0.tar.gz", hash = "sha256:e67f4c337b7542538746592c6a474bd4ae3a9edccdf62e11a32ca61fad3c9020", size = 5038 } +sdist = { url = "https://files.pythonhosted.org/packages/4f/bf/f7fa2f3657ed352870f442434cb2f27b7f70dcd52a544a1f3998eeaf6d71/alibabacloud_openplatform20191219-2.0.0.tar.gz", hash = "sha256:e67f4c337b7542538746592c6a474bd4ae3a9edccdf62e11a32ca61fad3c9020", size = 5038, upload-time = "2022-09-21T06:16:10.683Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/94/e5/18c75213551eeca9db1f6b41ddcc0bd87b5b6508c75a67f05cd8671847b4/alibabacloud_openplatform20191219-2.0.0-py3-none-any.whl", hash = "sha256:873821c45bca72a6c6ec7a906c9cb21554c122e88893bbac3986934dab30dd36", size = 5204 }, + { url = "https://files.pythonhosted.org/packages/94/e5/18c75213551eeca9db1f6b41ddcc0bd87b5b6508c75a67f05cd8671847b4/alibabacloud_openplatform20191219-2.0.0-py3-none-any.whl", hash = "sha256:873821c45bca72a6c6ec7a906c9cb21554c122e88893bbac3986934dab30dd36", size = 5204, upload-time = "2022-09-21T06:16:07.844Z" }, ] [[package]] name = "alibabacloud-oss-sdk" -version = "0.1.0" +version = "0.1.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "alibabacloud-credentials" }, @@ -196,7 +214,7 @@ dependencies = [ { name = "alibabacloud-tea-util" }, { name = "alibabacloud-tea-xml" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d8/e7/13d266ae5bf5636b9881477c60eded1b8688ec56f00048aa85b36b78a971/alibabacloud_oss_sdk-0.1.0.tar.gz", hash = "sha256:cc5ce36044bae758047fccb56c0cb6204cbc362d18cc3dd4ceac54c8c0897b8b", size = 46109 } +sdist = { url = "https://files.pythonhosted.org/packages/7e/d1/f442dd026908fcf55340ca694bb1d027aa91e119e76ae2fbea62f2bde4f4/alibabacloud_oss_sdk-0.1.1.tar.gz", hash = "sha256:f51a368020d0964fcc0978f96736006f49f5ab6a4a4bf4f0b8549e2c659e7358", size = 46434, upload-time = "2025-04-22T12:40:41.717Z" } [[package]] name = "alibabacloud-oss-util" @@ -205,7 +223,7 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "alibabacloud-tea" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/02/7c/d7e812b9968247a302573daebcfef95d0f9a718f7b4bfcca8d3d83e266be/alibabacloud_oss_util-0.0.6.tar.gz", hash = "sha256:d3ecec36632434bd509a113e8cf327dc23e830ac8d9dd6949926f4e334c8b5d6", size = 10008 } +sdist = { url = "https://files.pythonhosted.org/packages/02/7c/d7e812b9968247a302573daebcfef95d0f9a718f7b4bfcca8d3d83e266be/alibabacloud_oss_util-0.0.6.tar.gz", hash = "sha256:d3ecec36632434bd509a113e8cf327dc23e830ac8d9dd6949926f4e334c8b5d6", size = 10008, upload-time = "2021-04-28T09:25:04.056Z" } [[package]] name = "alibabacloud-tea" @@ -215,7 +233,7 @@ dependencies = [ { name = "aiohttp" }, { name = "requests" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/9a/7d/b22cb9a0d4f396ee0f3f9d7f26b76b9ed93d4101add7867a2c87ed2534f5/alibabacloud-tea-0.4.3.tar.gz", hash = "sha256:ec8053d0aa8d43ebe1deb632d5c5404339b39ec9a18a0707d57765838418504a", size = 8785 } +sdist = { url = "https://files.pythonhosted.org/packages/9a/7d/b22cb9a0d4f396ee0f3f9d7f26b76b9ed93d4101add7867a2c87ed2534f5/alibabacloud-tea-0.4.3.tar.gz", hash = "sha256:ec8053d0aa8d43ebe1deb632d5c5404339b39ec9a18a0707d57765838418504a", size = 8785, upload-time = "2025-03-24T07:34:42.958Z" } [[package]] name = "alibabacloud-tea-fileform" @@ -224,11 +242,11 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "alibabacloud-tea" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/22/8a/ef8ddf5ee0350984cad2749414b420369fe943e15e6d96b79be45367630e/alibabacloud_tea_fileform-0.0.5.tar.gz", hash = "sha256:fd00a8c9d85e785a7655059e9651f9e91784678881831f60589172387b968ee8", size = 3961 } +sdist = { url = "https://files.pythonhosted.org/packages/22/8a/ef8ddf5ee0350984cad2749414b420369fe943e15e6d96b79be45367630e/alibabacloud_tea_fileform-0.0.5.tar.gz", hash = "sha256:fd00a8c9d85e785a7655059e9651f9e91784678881831f60589172387b968ee8", size = 3961, upload-time = "2021-04-28T09:22:54.56Z" } [[package]] name = "alibabacloud-tea-openapi" -version = "0.3.13" +version = "0.3.15" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "alibabacloud-credentials" }, @@ -237,7 +255,7 @@ dependencies = [ { name = "alibabacloud-tea-util" }, { name = "alibabacloud-tea-xml" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/21/02/f5a6519efee96141a6e5af4e79b768bdae77dd62992bca02a710e06c36b1/alibabacloud_tea_openapi-0.3.13.tar.gz", hash = "sha256:77034911dbed41de440e9b6de38cb24646723aa1d0059cefeb3906f8c0a4523e", size = 12918 } +sdist = { url = "https://files.pythonhosted.org/packages/be/cb/f1b10b1da37e4c0de2aa9ca1e7153a6960a7f2dc496664e85fdc8b621f84/alibabacloud_tea_openapi-0.3.15.tar.gz", hash = "sha256:56a0aa6d51d8cf18c0cf3d219d861f4697f59d3e17fa6726b1101826d93988a2", size = 13021, upload-time = "2025-05-06T12:56:29.402Z" } [[package]] name = "alibabacloud-tea-util" @@ -246,7 +264,7 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "alibabacloud-tea" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/23/18/35be17103c8f40f9eebec3b1567f51b3eec09c3a47a5dd62bcb413f4e619/alibabacloud_tea_util-0.3.13.tar.gz", hash = "sha256:8cbdfd2a03fbbf622f901439fa08643898290dd40e1d928347f6346e43f63c90", size = 6535 } +sdist = { url = "https://files.pythonhosted.org/packages/23/18/35be17103c8f40f9eebec3b1567f51b3eec09c3a47a5dd62bcb413f4e619/alibabacloud_tea_util-0.3.13.tar.gz", hash = "sha256:8cbdfd2a03fbbf622f901439fa08643898290dd40e1d928347f6346e43f63c90", size = 6535, upload-time = "2024-07-15T12:25:12.07Z" } [[package]] name = "alibabacloud-tea-xml" @@ -255,7 +273,7 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "alibabacloud-tea" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/3b/30/f934051b1f65525d450cb225e4ac81dc3b77d808b0f79d51059d2a7ad3d3/alibabacloud_tea_xml-0.0.2.tar.gz", hash = "sha256:f0135e8148fd7d9c1f029db161863f37f144f837c280cba16c2edeb2f9c549d8", size = 3378 } +sdist = { url = "https://files.pythonhosted.org/packages/3b/30/f934051b1f65525d450cb225e4ac81dc3b77d808b0f79d51059d2a7ad3d3/alibabacloud_tea_xml-0.0.2.tar.gz", hash = "sha256:f0135e8148fd7d9c1f029db161863f37f144f837c280cba16c2edeb2f9c549d8", size = 3378, upload-time = "2020-07-02T09:03:55.866Z" } [[package]] name = "aliyun-python-sdk-core" @@ -265,7 +283,7 @@ dependencies = [ { name = "cryptography" }, { name = "jmespath" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/3e/09/da9f58eb38b4fdb97ba6523274fbf445ef6a06be64b433693da8307b4bec/aliyun-python-sdk-core-2.16.0.tar.gz", hash = "sha256:651caad597eb39d4fad6cf85133dffe92837d53bdf62db9d8f37dab6508bb8f9", size = 449555 } +sdist = { url = "https://files.pythonhosted.org/packages/3e/09/da9f58eb38b4fdb97ba6523274fbf445ef6a06be64b433693da8307b4bec/aliyun-python-sdk-core-2.16.0.tar.gz", hash = "sha256:651caad597eb39d4fad6cf85133dffe92837d53bdf62db9d8f37dab6508bb8f9", size = 449555, upload-time = "2024-10-09T06:01:01.762Z" } [[package]] name = "aliyun-python-sdk-kms" @@ -274,9 +292,9 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "aliyun-python-sdk-core" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a8/2c/9877d0e6b18ecf246df671ac65a5d1d9fecbf85bdcb5d43efbde0d4662eb/aliyun-python-sdk-kms-2.16.5.tar.gz", hash = "sha256:f328a8a19d83ecbb965ffce0ec1e9930755216d104638cd95ecd362753b813b3", size = 12018 } +sdist = { url = "https://files.pythonhosted.org/packages/a8/2c/9877d0e6b18ecf246df671ac65a5d1d9fecbf85bdcb5d43efbde0d4662eb/aliyun-python-sdk-kms-2.16.5.tar.gz", hash = "sha256:f328a8a19d83ecbb965ffce0ec1e9930755216d104638cd95ecd362753b813b3", size = 12018, upload-time = "2024-08-30T09:01:20.104Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/11/5c/0132193d7da2c735669a1ed103b142fd63c9455984d48c5a88a1a516efaa/aliyun_python_sdk_kms-2.16.5-py2.py3-none-any.whl", hash = "sha256:24b6cdc4fd161d2942619479c8d050c63ea9cd22b044fe33b60bbb60153786f0", size = 99495 }, + { url = "https://files.pythonhosted.org/packages/11/5c/0132193d7da2c735669a1ed103b142fd63c9455984d48c5a88a1a516efaa/aliyun_python_sdk_kms-2.16.5-py2.py3-none-any.whl", hash = "sha256:24b6cdc4fd161d2942619479c8d050c63ea9cd22b044fe33b60bbb60153786f0", size = 99495, upload-time = "2024-08-30T09:01:18.462Z" }, ] [[package]] @@ -286,27 +304,27 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "vine" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/79/fc/ec94a357dfc6683d8c86f8b4cfa5416a4c36b28052ec8260c77aca96a443/amqp-5.3.1.tar.gz", hash = "sha256:cddc00c725449522023bad949f70fff7b48f0b1ade74d170a6f10ab044739432", size = 129013 } +sdist = { url = "https://files.pythonhosted.org/packages/79/fc/ec94a357dfc6683d8c86f8b4cfa5416a4c36b28052ec8260c77aca96a443/amqp-5.3.1.tar.gz", hash = "sha256:cddc00c725449522023bad949f70fff7b48f0b1ade74d170a6f10ab044739432", size = 129013, upload-time = "2024-11-12T19:55:44.051Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/26/99/fc813cd978842c26c82534010ea849eee9ab3a13ea2b74e95cb9c99e747b/amqp-5.3.1-py3-none-any.whl", hash = "sha256:43b3319e1b4e7d1251833a93d672b4af1e40f3d632d479b98661a95f117880a2", size = 50944 }, + { url = "https://files.pythonhosted.org/packages/26/99/fc813cd978842c26c82534010ea849eee9ab3a13ea2b74e95cb9c99e747b/amqp-5.3.1-py3-none-any.whl", hash = "sha256:43b3319e1b4e7d1251833a93d672b4af1e40f3d632d479b98661a95f117880a2", size = 50944, upload-time = "2024-11-12T19:55:41.782Z" }, ] [[package]] name = "aniso8601" -version = "10.0.0" +version = "10.0.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f3/3f/dc8a28fa6dc72c13d8c158b01f8975f240e9e72c336cc1ae00f424e2d7ce/aniso8601-10.0.0.tar.gz", hash = "sha256:ff1d0fc2346688c62c0151547136ac30e322896ed8af316ef7602c47da9426cf", size = 47008 } +sdist = { url = "https://files.pythonhosted.org/packages/8b/8d/52179c4e3f1978d3d9a285f98c706642522750ef343e9738286130423730/aniso8601-10.0.1.tar.gz", hash = "sha256:25488f8663dd1528ae1f54f94ac1ea51ae25b4d531539b8bc707fed184d16845", size = 47190, upload-time = "2025-04-18T17:29:42.995Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/72/bf/d5cde2cb7cdc2cb1770d974418d169a79c3187bd962cb752b9fd617848ca/aniso8601-10.0.0-py2.py3-none-any.whl", hash = "sha256:3c943422efaa0229ebd2b0d7d223effb5e7c89e24d2267ebe76c61a2d8e290cb", size = 52767 }, + { url = "https://files.pythonhosted.org/packages/59/75/e0e10dc7ed1408c28e03a6cb2d7a407f99320eb953f229d008a7a6d05546/aniso8601-10.0.1-py2.py3-none-any.whl", hash = "sha256:eb19717fd4e0db6de1aab06f12450ab92144246b257423fe020af5748c0cb89e", size = 52848, upload-time = "2025-04-18T17:29:41.492Z" }, ] [[package]] name = "annotated-types" version = "0.7.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081 } +sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081, upload-time = "2024-05-20T21:33:25.928Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643 }, + { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload-time = "2024-05-20T21:33:24.1Z" }, ] [[package]] @@ -318,36 +336,48 @@ dependencies = [ { name = "sniffio" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/95/7d/4c1bd541d4dffa1b52bd83fb8527089e097a106fc90b467a7313b105f840/anyio-4.9.0.tar.gz", hash = "sha256:673c0c244e15788651a4ff38710fea9675823028a6f08a5eda409e0c9840a028", size = 190949 } +sdist = { url = "https://files.pythonhosted.org/packages/95/7d/4c1bd541d4dffa1b52bd83fb8527089e097a106fc90b467a7313b105f840/anyio-4.9.0.tar.gz", hash = "sha256:673c0c244e15788651a4ff38710fea9675823028a6f08a5eda409e0c9840a028", size = 190949, upload-time = "2025-03-17T00:02:54.77Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a1/ee/48ca1a7c89ffec8b6a0c5d02b89c305671d5ffd8d3c94acf8b8c408575bb/anyio-4.9.0-py3-none-any.whl", hash = "sha256:9f76d541cad6e36af7beb62e978876f3b41e3e04f2c1fbf0884604c0a9c4d93c", size = 100916, upload-time = "2025-03-17T00:02:52.713Z" }, +] + +[[package]] +name = "apscheduler" +version = "3.11.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "tzlocal" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/4e/00/6d6814ddc19be2df62c8c898c4df6b5b1914f3bd024b780028caa392d186/apscheduler-3.11.0.tar.gz", hash = "sha256:4c622d250b0955a65d5d0eb91c33e6d43fd879834bf541e0a18661ae60460133", size = 107347, upload-time = "2024-11-24T19:39:26.463Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a1/ee/48ca1a7c89ffec8b6a0c5d02b89c305671d5ffd8d3c94acf8b8c408575bb/anyio-4.9.0-py3-none-any.whl", hash = "sha256:9f76d541cad6e36af7beb62e978876f3b41e3e04f2c1fbf0884604c0a9c4d93c", size = 100916 }, + { url = "https://files.pythonhosted.org/packages/d0/ae/9a053dd9229c0fde6b1f1f33f609ccff1ee79ddda364c756a924c6d8563b/APScheduler-3.11.0-py3-none-any.whl", hash = "sha256:fc134ca32e50f5eadcc4938e3a4545ab19131435e851abb40b34d63d5141c6da", size = 64004, upload-time = "2024-11-24T19:39:24.442Z" }, ] [[package]] name = "asgiref" version = "3.8.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/29/38/b3395cc9ad1b56d2ddac9970bc8f4141312dbaec28bc7c218b0dfafd0f42/asgiref-3.8.1.tar.gz", hash = "sha256:c343bd80a0bec947a9860adb4c432ffa7db769836c64238fc34bdc3fec84d590", size = 35186 } +sdist = { url = "https://files.pythonhosted.org/packages/29/38/b3395cc9ad1b56d2ddac9970bc8f4141312dbaec28bc7c218b0dfafd0f42/asgiref-3.8.1.tar.gz", hash = "sha256:c343bd80a0bec947a9860adb4c432ffa7db769836c64238fc34bdc3fec84d590", size = 35186, upload-time = "2024-03-22T14:39:36.863Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/39/e3/893e8757be2612e6c266d9bb58ad2e3651524b5b40cf56761e985a28b13e/asgiref-3.8.1-py3-none-any.whl", hash = "sha256:3e1e3ecc849832fe52ccf2cb6686b7a55f82bb1d6aee72a58826471390335e47", size = 23828 }, + { url = "https://files.pythonhosted.org/packages/39/e3/893e8757be2612e6c266d9bb58ad2e3651524b5b40cf56761e985a28b13e/asgiref-3.8.1-py3-none-any.whl", hash = "sha256:3e1e3ecc849832fe52ccf2cb6686b7a55f82bb1d6aee72a58826471390335e47", size = 23828, upload-time = "2024-03-22T14:39:34.521Z" }, ] [[package]] name = "async-timeout" version = "5.0.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a5/ae/136395dfbfe00dfc94da3f3e136d0b13f394cba8f4841120e34226265780/async_timeout-5.0.1.tar.gz", hash = "sha256:d9321a7a3d5a6a5e187e824d2fa0793ce379a202935782d555d6e9d2735677d3", size = 9274 } +sdist = { url = "https://files.pythonhosted.org/packages/a5/ae/136395dfbfe00dfc94da3f3e136d0b13f394cba8f4841120e34226265780/async_timeout-5.0.1.tar.gz", hash = "sha256:d9321a7a3d5a6a5e187e824d2fa0793ce379a202935782d555d6e9d2735677d3", size = 9274, upload-time = "2024-11-06T16:41:39.6Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/fe/ba/e2081de779ca30d473f21f5b30e0e737c438205440784c7dfc81efc2b029/async_timeout-5.0.1-py3-none-any.whl", hash = "sha256:39e3809566ff85354557ec2398b55e096c8364bacac9405a7a1fa429e77fe76c", size = 6233 }, + { url = "https://files.pythonhosted.org/packages/fe/ba/e2081de779ca30d473f21f5b30e0e737c438205440784c7dfc81efc2b029/async_timeout-5.0.1-py3-none-any.whl", hash = "sha256:39e3809566ff85354557ec2398b55e096c8364bacac9405a7a1fa429e77fe76c", size = 6233, upload-time = "2024-11-06T16:41:37.9Z" }, ] [[package]] name = "attrs" version = "25.3.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/5a/b0/1367933a8532ee6ff8d63537de4f1177af4bff9f3e829baf7331f595bb24/attrs-25.3.0.tar.gz", hash = "sha256:75d7cefc7fb576747b2c81b4442d4d4a1ce0900973527c011d1030fd3bf4af1b", size = 812032 } +sdist = { url = "https://files.pythonhosted.org/packages/5a/b0/1367933a8532ee6ff8d63537de4f1177af4bff9f3e829baf7331f595bb24/attrs-25.3.0.tar.gz", hash = "sha256:75d7cefc7fb576747b2c81b4442d4d4a1ce0900973527c011d1030fd3bf4af1b", size = 812032, upload-time = "2025-03-13T11:10:22.779Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/77/06/bb80f5f86020c4551da315d78b3ab75e8228f89f0162f2c3a819e407941a/attrs-25.3.0-py3-none-any.whl", hash = "sha256:427318ce031701fea540783410126f03899a97ffc6f61596ad581ac2e40e3bc3", size = 63815 }, + { url = "https://files.pythonhosted.org/packages/77/06/bb80f5f86020c4551da315d78b3ab75e8228f89f0162f2c3a819e407941a/attrs-25.3.0-py3-none-any.whl", hash = "sha256:427318ce031701fea540783410126f03899a97ffc6f61596ad581ac2e40e3bc3", size = 63815, upload-time = "2025-03-13T11:10:21.14Z" }, ] [[package]] @@ -357,23 +387,23 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cryptography" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/09/47/df70ecd34fbf86d69833fe4e25bb9ecbaab995c8e49df726dd416f6bb822/authlib-1.3.1.tar.gz", hash = "sha256:7ae843f03c06c5c0debd63c9db91f9fda64fa62a42a77419fa15fbb7e7a58917", size = 146074 } +sdist = { url = "https://files.pythonhosted.org/packages/09/47/df70ecd34fbf86d69833fe4e25bb9ecbaab995c8e49df726dd416f6bb822/authlib-1.3.1.tar.gz", hash = "sha256:7ae843f03c06c5c0debd63c9db91f9fda64fa62a42a77419fa15fbb7e7a58917", size = 146074, upload-time = "2024-06-04T14:15:32.06Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/87/1f/bc95e43ffb57c05b8efcc376dd55a0240bf58f47ddf5a0f92452b6457b75/Authlib-1.3.1-py2.py3-none-any.whl", hash = "sha256:d35800b973099bbadc49b42b256ecb80041ad56b7fe1216a362c7943c088f377", size = 223827 }, + { url = "https://files.pythonhosted.org/packages/87/1f/bc95e43ffb57c05b8efcc376dd55a0240bf58f47ddf5a0f92452b6457b75/Authlib-1.3.1-py2.py3-none-any.whl", hash = "sha256:d35800b973099bbadc49b42b256ecb80041ad56b7fe1216a362c7943c088f377", size = 223827, upload-time = "2024-06-04T14:15:29.218Z" }, ] [[package]] name = "azure-core" -version = "1.33.0" +version = "1.34.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "requests" }, { name = "six" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/75/aa/7c9db8edd626f1a7d99d09ef7926f6f4fb34d5f9fa00dc394afdfe8e2a80/azure_core-1.33.0.tar.gz", hash = "sha256:f367aa07b5e3005fec2c1e184b882b0b039910733907d001c20fb08ebb8c0eb9", size = 295633 } +sdist = { url = "https://files.pythonhosted.org/packages/c9/29/ff7a519a315e41c85bab92a7478c6acd1cf0b14353139a08caee4c691f77/azure_core-1.34.0.tar.gz", hash = "sha256:bdb544989f246a0ad1c85d72eeb45f2f835afdcbc5b45e43f0dbde7461c81ece", size = 297999, upload-time = "2025-05-01T23:17:27.59Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/07/b7/76b7e144aa53bd206bf1ce34fa75350472c3f69bf30e5c8c18bc9881035d/azure_core-1.33.0-py3-none-any.whl", hash = "sha256:9b5b6d0223a1d38c37500e6971118c1e0f13f54951e6893968b38910bc9cda8f", size = 207071 }, + { url = "https://files.pythonhosted.org/packages/84/9e/5c87b49f65bb16571599bc789857d0ded2f53014d3392bc88a5d1f3ad779/azure_core-1.34.0-py3-none-any.whl", hash = "sha256:0615d3b756beccdb6624d1c0ae97284f38b78fb59a2a9839bf927c66fbbdddd6", size = 207409, upload-time = "2025-05-01T23:17:29.818Z" }, ] [[package]] @@ -386,9 +416,9 @@ dependencies = [ { name = "msal" }, { name = "msal-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/bb/1c/bd704075e555046e24b069157ca25c81aedb4199c3e0b35acba9243a6ca6/azure-identity-1.16.1.tar.gz", hash = "sha256:6d93f04468f240d59246d8afde3091494a5040d4f141cad0f49fc0c399d0d91e", size = 236726 } +sdist = { url = "https://files.pythonhosted.org/packages/bb/1c/bd704075e555046e24b069157ca25c81aedb4199c3e0b35acba9243a6ca6/azure-identity-1.16.1.tar.gz", hash = "sha256:6d93f04468f240d59246d8afde3091494a5040d4f141cad0f49fc0c399d0d91e", size = 236726, upload-time = "2024-06-10T22:23:27.46Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ef/c5/ca55106564d2044ab90614381368b3756690fb7e3ab04552e17f308e4e4f/azure_identity-1.16.1-py3-none-any.whl", hash = "sha256:8fb07c25642cd4ac422559a8b50d3e77f73dcc2bbfaba419d06d6c9d7cff6726", size = 166741 }, + { url = "https://files.pythonhosted.org/packages/ef/c5/ca55106564d2044ab90614381368b3756690fb7e3ab04552e17f308e4e4f/azure_identity-1.16.1-py3-none-any.whl", hash = "sha256:8fb07c25642cd4ac422559a8b50d3e77f73dcc2bbfaba419d06d6c9d7cff6726", size = 166741, upload-time = "2024-06-10T22:23:30.906Z" }, ] [[package]] @@ -400,72 +430,72 @@ dependencies = [ { name = "cryptography" }, { name = "msrest" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/b1/93/b13bf390e940a79a399981f75ac8d2e05a70112a95ebb7b41e9b752d2921/azure-storage-blob-12.13.0.zip", hash = "sha256:53f0d4cd32970ac9ff9b9753f83dd2fb3f9ac30e1d01e71638c436c509bfd884", size = 684838 } +sdist = { url = "https://files.pythonhosted.org/packages/b1/93/b13bf390e940a79a399981f75ac8d2e05a70112a95ebb7b41e9b752d2921/azure-storage-blob-12.13.0.zip", hash = "sha256:53f0d4cd32970ac9ff9b9753f83dd2fb3f9ac30e1d01e71638c436c509bfd884", size = 684838, upload-time = "2022-07-07T22:35:44.543Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/0e/2a/b8246df35af68d64fb7292c93dbbde63cd25036f2f669a9d9ae59e518c76/azure_storage_blob-12.13.0-py3-none-any.whl", hash = "sha256:280a6ab032845bab9627582bee78a50497ca2f14772929b5c5ee8b4605af0cb3", size = 377309 }, + { url = "https://files.pythonhosted.org/packages/0e/2a/b8246df35af68d64fb7292c93dbbde63cd25036f2f669a9d9ae59e518c76/azure_storage_blob-12.13.0-py3-none-any.whl", hash = "sha256:280a6ab032845bab9627582bee78a50497ca2f14772929b5c5ee8b4605af0cb3", size = 377309, upload-time = "2022-07-07T22:35:41.905Z" }, ] [[package]] name = "backoff" version = "2.2.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/47/d7/5bbeb12c44d7c4f2fb5b56abce497eb5ed9f34d85701de869acedd602619/backoff-2.2.1.tar.gz", hash = "sha256:03f829f5bb1923180821643f8753b0502c3b682293992485b0eef2807afa5cba", size = 17001 } +sdist = { url = "https://files.pythonhosted.org/packages/47/d7/5bbeb12c44d7c4f2fb5b56abce497eb5ed9f34d85701de869acedd602619/backoff-2.2.1.tar.gz", hash = "sha256:03f829f5bb1923180821643f8753b0502c3b682293992485b0eef2807afa5cba", size = 17001, upload-time = "2022-10-05T19:19:32.061Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/df/73/b6e24bd22e6720ca8ee9a85a0c4a2971af8497d8f3193fa05390cbd46e09/backoff-2.2.1-py3-none-any.whl", hash = "sha256:63579f9a0628e06278f7e47b7d7d5b6ce20dc65c5e96a6f3ca99a6adca0396e8", size = 15148 }, + { url = "https://files.pythonhosted.org/packages/df/73/b6e24bd22e6720ca8ee9a85a0c4a2971af8497d8f3193fa05390cbd46e09/backoff-2.2.1-py3-none-any.whl", hash = "sha256:63579f9a0628e06278f7e47b7d7d5b6ce20dc65c5e96a6f3ca99a6adca0396e8", size = 15148, upload-time = "2022-10-05T19:19:30.546Z" }, ] [[package]] name = "bce-python-sdk" -version = "0.9.29" +version = "0.9.35" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "future" }, { name = "pycryptodome" }, { name = "six" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/51/f6/3237811b0a9fd79ba9fcf4d13085f1a1ea19fee43b0cbb24189661d787d5/bce_python_sdk-0.9.29.tar.gz", hash = "sha256:326fbd50d57bf6d2fc21d58f589b069e0e84fc0a8733be9575c109293ab08cc4", size = 246628 } +sdist = { url = "https://files.pythonhosted.org/packages/c6/91/c218750fd515fef10d197a2385a81a5f3504d30637fc1268bafa53cc2837/bce_python_sdk-0.9.35.tar.gz", hash = "sha256:024a2b5cd086707c866225cf8631fa126edbccfdd5bc3c8a83fe2ea9aa768bf5", size = 247844, upload-time = "2025-05-19T11:23:35.223Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/0f/60/b3247bae81d5ecde1a88430ffe8c57ea3f7212c6a6670b540f7d34d2c625/bce_python_sdk-0.9.29-py3-none-any.whl", hash = "sha256:6518dc0ada422acd1841eeabcb7f89cfc07e3bb1a4be3c75945cab953907b555", size = 343584 }, + { url = "https://files.pythonhosted.org/packages/28/81/f574f6b300927a63596fa8e5081f5c0ad66d5cc99004d70d63c523f42ff8/bce_python_sdk-0.9.35-py3-none-any.whl", hash = "sha256:08c1575a0f2ec04b2fc17063fe6e47e1aab48e3bca1f26181cb8bed5528fa5de", size = 344813, upload-time = "2025-05-19T11:23:33.68Z" }, ] [[package]] name = "bcrypt" version = "4.3.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/bb/5d/6d7433e0f3cd46ce0b43cd65e1db465ea024dbb8216fb2404e919c2ad77b/bcrypt-4.3.0.tar.gz", hash = "sha256:3a3fd2204178b6d2adcf09cb4f6426ffef54762577a7c9b54c159008cb288c18", size = 25697 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/11/22/5ada0b9af72b60cbc4c9a399fdde4af0feaa609d27eb0adc61607997a3fa/bcrypt-4.3.0-cp38-abi3-macosx_10_12_universal2.whl", hash = "sha256:f81b0ed2639568bf14749112298f9e4e2b28853dab50a8b357e31798686a036d", size = 498019 }, - { url = "https://files.pythonhosted.org/packages/b8/8c/252a1edc598dc1ce57905be173328eda073083826955ee3c97c7ff5ba584/bcrypt-4.3.0-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:864f8f19adbe13b7de11ba15d85d4a428c7e2f344bac110f667676a0ff84924b", size = 279174 }, - { url = "https://files.pythonhosted.org/packages/29/5b/4547d5c49b85f0337c13929f2ccbe08b7283069eea3550a457914fc078aa/bcrypt-4.3.0-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3e36506d001e93bffe59754397572f21bb5dc7c83f54454c990c74a468cd589e", size = 283870 }, - { url = "https://files.pythonhosted.org/packages/be/21/7dbaf3fa1745cb63f776bb046e481fbababd7d344c5324eab47f5ca92dd2/bcrypt-4.3.0-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:842d08d75d9fe9fb94b18b071090220697f9f184d4547179b60734846461ed59", size = 279601 }, - { url = "https://files.pythonhosted.org/packages/6d/64/e042fc8262e971347d9230d9abbe70d68b0a549acd8611c83cebd3eaec67/bcrypt-4.3.0-cp38-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:7c03296b85cb87db865d91da79bf63d5609284fc0cab9472fdd8367bbd830753", size = 297660 }, - { url = "https://files.pythonhosted.org/packages/50/b8/6294eb84a3fef3b67c69b4470fcdd5326676806bf2519cda79331ab3c3a9/bcrypt-4.3.0-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:62f26585e8b219cdc909b6a0069efc5e4267e25d4a3770a364ac58024f62a761", size = 284083 }, - { url = "https://files.pythonhosted.org/packages/62/e6/baff635a4f2c42e8788fe1b1633911c38551ecca9a749d1052d296329da6/bcrypt-4.3.0-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:beeefe437218a65322fbd0069eb437e7c98137e08f22c4660ac2dc795c31f8bb", size = 279237 }, - { url = "https://files.pythonhosted.org/packages/39/48/46f623f1b0c7dc2e5de0b8af5e6f5ac4cc26408ac33f3d424e5ad8da4a90/bcrypt-4.3.0-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:97eea7408db3a5bcce4a55d13245ab3fa566e23b4c67cd227062bb49e26c585d", size = 283737 }, - { url = "https://files.pythonhosted.org/packages/49/8b/70671c3ce9c0fca4a6cc3cc6ccbaa7e948875a2e62cbd146e04a4011899c/bcrypt-4.3.0-cp38-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:191354ebfe305e84f344c5964c7cd5f924a3bfc5d405c75ad07f232b6dffb49f", size = 312741 }, - { url = "https://files.pythonhosted.org/packages/27/fb/910d3a1caa2d249b6040a5caf9f9866c52114d51523ac2fb47578a27faee/bcrypt-4.3.0-cp38-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:41261d64150858eeb5ff43c753c4b216991e0ae16614a308a15d909503617732", size = 316472 }, - { url = "https://files.pythonhosted.org/packages/dc/cf/7cf3a05b66ce466cfb575dbbda39718d45a609daa78500f57fa9f36fa3c0/bcrypt-4.3.0-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:33752b1ba962ee793fa2b6321404bf20011fe45b9afd2a842139de3011898fef", size = 343606 }, - { url = "https://files.pythonhosted.org/packages/e3/b8/e970ecc6d7e355c0d892b7f733480f4aa8509f99b33e71550242cf0b7e63/bcrypt-4.3.0-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:50e6e80a4bfd23a25f5c05b90167c19030cf9f87930f7cb2eacb99f45d1c3304", size = 362867 }, - { url = "https://files.pythonhosted.org/packages/a9/97/8d3118efd8354c555a3422d544163f40d9f236be5b96c714086463f11699/bcrypt-4.3.0-cp38-abi3-win32.whl", hash = "sha256:67a561c4d9fb9465ec866177e7aebcad08fe23aaf6fbd692a6fab69088abfc51", size = 160589 }, - { url = "https://files.pythonhosted.org/packages/29/07/416f0b99f7f3997c69815365babbc2e8754181a4b1899d921b3c7d5b6f12/bcrypt-4.3.0-cp38-abi3-win_amd64.whl", hash = "sha256:584027857bc2843772114717a7490a37f68da563b3620f78a849bcb54dc11e62", size = 152794 }, - { url = "https://files.pythonhosted.org/packages/6e/c1/3fa0e9e4e0bfd3fd77eb8b52ec198fd6e1fd7e9402052e43f23483f956dd/bcrypt-4.3.0-cp39-abi3-macosx_10_12_universal2.whl", hash = "sha256:0d3efb1157edebfd9128e4e46e2ac1a64e0c1fe46fb023158a407c7892b0f8c3", size = 498969 }, - { url = "https://files.pythonhosted.org/packages/ce/d4/755ce19b6743394787fbd7dff6bf271b27ee9b5912a97242e3caf125885b/bcrypt-4.3.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:08bacc884fd302b611226c01014eca277d48f0a05187666bca23aac0dad6fe24", size = 279158 }, - { url = "https://files.pythonhosted.org/packages/9b/5d/805ef1a749c965c46b28285dfb5cd272a7ed9fa971f970435a5133250182/bcrypt-4.3.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f6746e6fec103fcd509b96bacdfdaa2fbde9a553245dbada284435173a6f1aef", size = 284285 }, - { url = "https://files.pythonhosted.org/packages/ab/2b/698580547a4a4988e415721b71eb45e80c879f0fb04a62da131f45987b96/bcrypt-4.3.0-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:afe327968aaf13fc143a56a3360cb27d4ad0345e34da12c7290f1b00b8fe9a8b", size = 279583 }, - { url = "https://files.pythonhosted.org/packages/f2/87/62e1e426418204db520f955ffd06f1efd389feca893dad7095bf35612eec/bcrypt-4.3.0-cp39-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:d9af79d322e735b1fc33404b5765108ae0ff232d4b54666d46730f8ac1a43676", size = 297896 }, - { url = "https://files.pythonhosted.org/packages/cb/c6/8fedca4c2ada1b6e889c52d2943b2f968d3427e5d65f595620ec4c06fa2f/bcrypt-4.3.0-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:f1e3ffa1365e8702dc48c8b360fef8d7afeca482809c5e45e653af82ccd088c1", size = 284492 }, - { url = "https://files.pythonhosted.org/packages/4d/4d/c43332dcaaddb7710a8ff5269fcccba97ed3c85987ddaa808db084267b9a/bcrypt-4.3.0-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:3004df1b323d10021fda07a813fd33e0fd57bef0e9a480bb143877f6cba996fe", size = 279213 }, - { url = "https://files.pythonhosted.org/packages/dc/7f/1e36379e169a7df3a14a1c160a49b7b918600a6008de43ff20d479e6f4b5/bcrypt-4.3.0-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:531457e5c839d8caea9b589a1bcfe3756b0547d7814e9ce3d437f17da75c32b0", size = 284162 }, - { url = "https://files.pythonhosted.org/packages/1c/0a/644b2731194b0d7646f3210dc4d80c7fee3ecb3a1f791a6e0ae6bb8684e3/bcrypt-4.3.0-cp39-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:17a854d9a7a476a89dcef6c8bd119ad23e0f82557afbd2c442777a16408e614f", size = 312856 }, - { url = "https://files.pythonhosted.org/packages/dc/62/2a871837c0bb6ab0c9a88bf54de0fc021a6a08832d4ea313ed92a669d437/bcrypt-4.3.0-cp39-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:6fb1fd3ab08c0cbc6826a2e0447610c6f09e983a281b919ed721ad32236b8b23", size = 316726 }, - { url = "https://files.pythonhosted.org/packages/0c/a1/9898ea3faac0b156d457fd73a3cb9c2855c6fd063e44b8522925cdd8ce46/bcrypt-4.3.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:e965a9c1e9a393b8005031ff52583cedc15b7884fce7deb8b0346388837d6cfe", size = 343664 }, - { url = "https://files.pythonhosted.org/packages/40/f2/71b4ed65ce38982ecdda0ff20c3ad1b15e71949c78b2c053df53629ce940/bcrypt-4.3.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:79e70b8342a33b52b55d93b3a59223a844962bef479f6a0ea318ebbcadf71505", size = 363128 }, - { url = "https://files.pythonhosted.org/packages/11/99/12f6a58eca6dea4be992d6c681b7ec9410a1d9f5cf368c61437e31daa879/bcrypt-4.3.0-cp39-abi3-win32.whl", hash = "sha256:b4d4e57f0a63fd0b358eb765063ff661328f69a04494427265950c71b992a39a", size = 160598 }, - { url = "https://files.pythonhosted.org/packages/a9/cf/45fb5261ece3e6b9817d3d82b2f343a505fd58674a92577923bc500bd1aa/bcrypt-4.3.0-cp39-abi3-win_amd64.whl", hash = "sha256:e53e074b120f2877a35cc6c736b8eb161377caae8925c17688bd46ba56daaa5b", size = 152799 }, - { url = "https://files.pythonhosted.org/packages/4c/b1/1289e21d710496b88340369137cc4c5f6ee036401190ea116a7b4ae6d32a/bcrypt-4.3.0-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:a839320bf27d474e52ef8cb16449bb2ce0ba03ca9f44daba6d93fa1d8828e48a", size = 275103 }, - { url = "https://files.pythonhosted.org/packages/94/41/19be9fe17e4ffc5d10b7b67f10e459fc4eee6ffe9056a88de511920cfd8d/bcrypt-4.3.0-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:bdc6a24e754a555d7316fa4774e64c6c3997d27ed2d1964d55920c7c227bc4ce", size = 280513 }, - { url = "https://files.pythonhosted.org/packages/aa/73/05687a9ef89edebdd8ad7474c16d8af685eb4591c3c38300bb6aad4f0076/bcrypt-4.3.0-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:55a935b8e9a1d2def0626c4269db3fcd26728cbff1e84f0341465c31c4ee56d8", size = 274685 }, - { url = "https://files.pythonhosted.org/packages/63/13/47bba97924ebe86a62ef83dc75b7c8a881d53c535f83e2c54c4bd701e05c/bcrypt-4.3.0-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:57967b7a28d855313a963aaea51bf6df89f833db4320da458e5b3c5ab6d4c938", size = 280110 }, +sdist = { url = "https://files.pythonhosted.org/packages/bb/5d/6d7433e0f3cd46ce0b43cd65e1db465ea024dbb8216fb2404e919c2ad77b/bcrypt-4.3.0.tar.gz", hash = "sha256:3a3fd2204178b6d2adcf09cb4f6426ffef54762577a7c9b54c159008cb288c18", size = 25697, upload-time = "2025-02-28T01:24:09.174Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/11/22/5ada0b9af72b60cbc4c9a399fdde4af0feaa609d27eb0adc61607997a3fa/bcrypt-4.3.0-cp38-abi3-macosx_10_12_universal2.whl", hash = "sha256:f81b0ed2639568bf14749112298f9e4e2b28853dab50a8b357e31798686a036d", size = 498019, upload-time = "2025-02-28T01:23:05.838Z" }, + { url = "https://files.pythonhosted.org/packages/b8/8c/252a1edc598dc1ce57905be173328eda073083826955ee3c97c7ff5ba584/bcrypt-4.3.0-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:864f8f19adbe13b7de11ba15d85d4a428c7e2f344bac110f667676a0ff84924b", size = 279174, upload-time = "2025-02-28T01:23:07.274Z" }, + { url = "https://files.pythonhosted.org/packages/29/5b/4547d5c49b85f0337c13929f2ccbe08b7283069eea3550a457914fc078aa/bcrypt-4.3.0-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3e36506d001e93bffe59754397572f21bb5dc7c83f54454c990c74a468cd589e", size = 283870, upload-time = "2025-02-28T01:23:09.151Z" }, + { url = "https://files.pythonhosted.org/packages/be/21/7dbaf3fa1745cb63f776bb046e481fbababd7d344c5324eab47f5ca92dd2/bcrypt-4.3.0-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:842d08d75d9fe9fb94b18b071090220697f9f184d4547179b60734846461ed59", size = 279601, upload-time = "2025-02-28T01:23:11.461Z" }, + { url = "https://files.pythonhosted.org/packages/6d/64/e042fc8262e971347d9230d9abbe70d68b0a549acd8611c83cebd3eaec67/bcrypt-4.3.0-cp38-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:7c03296b85cb87db865d91da79bf63d5609284fc0cab9472fdd8367bbd830753", size = 297660, upload-time = "2025-02-28T01:23:12.989Z" }, + { url = "https://files.pythonhosted.org/packages/50/b8/6294eb84a3fef3b67c69b4470fcdd5326676806bf2519cda79331ab3c3a9/bcrypt-4.3.0-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:62f26585e8b219cdc909b6a0069efc5e4267e25d4a3770a364ac58024f62a761", size = 284083, upload-time = "2025-02-28T01:23:14.5Z" }, + { url = "https://files.pythonhosted.org/packages/62/e6/baff635a4f2c42e8788fe1b1633911c38551ecca9a749d1052d296329da6/bcrypt-4.3.0-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:beeefe437218a65322fbd0069eb437e7c98137e08f22c4660ac2dc795c31f8bb", size = 279237, upload-time = "2025-02-28T01:23:16.686Z" }, + { url = "https://files.pythonhosted.org/packages/39/48/46f623f1b0c7dc2e5de0b8af5e6f5ac4cc26408ac33f3d424e5ad8da4a90/bcrypt-4.3.0-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:97eea7408db3a5bcce4a55d13245ab3fa566e23b4c67cd227062bb49e26c585d", size = 283737, upload-time = "2025-02-28T01:23:18.897Z" }, + { url = "https://files.pythonhosted.org/packages/49/8b/70671c3ce9c0fca4a6cc3cc6ccbaa7e948875a2e62cbd146e04a4011899c/bcrypt-4.3.0-cp38-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:191354ebfe305e84f344c5964c7cd5f924a3bfc5d405c75ad07f232b6dffb49f", size = 312741, upload-time = "2025-02-28T01:23:21.041Z" }, + { url = "https://files.pythonhosted.org/packages/27/fb/910d3a1caa2d249b6040a5caf9f9866c52114d51523ac2fb47578a27faee/bcrypt-4.3.0-cp38-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:41261d64150858eeb5ff43c753c4b216991e0ae16614a308a15d909503617732", size = 316472, upload-time = "2025-02-28T01:23:23.183Z" }, + { url = "https://files.pythonhosted.org/packages/dc/cf/7cf3a05b66ce466cfb575dbbda39718d45a609daa78500f57fa9f36fa3c0/bcrypt-4.3.0-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:33752b1ba962ee793fa2b6321404bf20011fe45b9afd2a842139de3011898fef", size = 343606, upload-time = "2025-02-28T01:23:25.361Z" }, + { url = "https://files.pythonhosted.org/packages/e3/b8/e970ecc6d7e355c0d892b7f733480f4aa8509f99b33e71550242cf0b7e63/bcrypt-4.3.0-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:50e6e80a4bfd23a25f5c05b90167c19030cf9f87930f7cb2eacb99f45d1c3304", size = 362867, upload-time = "2025-02-28T01:23:26.875Z" }, + { url = "https://files.pythonhosted.org/packages/a9/97/8d3118efd8354c555a3422d544163f40d9f236be5b96c714086463f11699/bcrypt-4.3.0-cp38-abi3-win32.whl", hash = "sha256:67a561c4d9fb9465ec866177e7aebcad08fe23aaf6fbd692a6fab69088abfc51", size = 160589, upload-time = "2025-02-28T01:23:28.381Z" }, + { url = "https://files.pythonhosted.org/packages/29/07/416f0b99f7f3997c69815365babbc2e8754181a4b1899d921b3c7d5b6f12/bcrypt-4.3.0-cp38-abi3-win_amd64.whl", hash = "sha256:584027857bc2843772114717a7490a37f68da563b3620f78a849bcb54dc11e62", size = 152794, upload-time = "2025-02-28T01:23:30.187Z" }, + { url = "https://files.pythonhosted.org/packages/6e/c1/3fa0e9e4e0bfd3fd77eb8b52ec198fd6e1fd7e9402052e43f23483f956dd/bcrypt-4.3.0-cp39-abi3-macosx_10_12_universal2.whl", hash = "sha256:0d3efb1157edebfd9128e4e46e2ac1a64e0c1fe46fb023158a407c7892b0f8c3", size = 498969, upload-time = "2025-02-28T01:23:31.945Z" }, + { url = "https://files.pythonhosted.org/packages/ce/d4/755ce19b6743394787fbd7dff6bf271b27ee9b5912a97242e3caf125885b/bcrypt-4.3.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:08bacc884fd302b611226c01014eca277d48f0a05187666bca23aac0dad6fe24", size = 279158, upload-time = "2025-02-28T01:23:34.161Z" }, + { url = "https://files.pythonhosted.org/packages/9b/5d/805ef1a749c965c46b28285dfb5cd272a7ed9fa971f970435a5133250182/bcrypt-4.3.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f6746e6fec103fcd509b96bacdfdaa2fbde9a553245dbada284435173a6f1aef", size = 284285, upload-time = "2025-02-28T01:23:35.765Z" }, + { url = "https://files.pythonhosted.org/packages/ab/2b/698580547a4a4988e415721b71eb45e80c879f0fb04a62da131f45987b96/bcrypt-4.3.0-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:afe327968aaf13fc143a56a3360cb27d4ad0345e34da12c7290f1b00b8fe9a8b", size = 279583, upload-time = "2025-02-28T01:23:38.021Z" }, + { url = "https://files.pythonhosted.org/packages/f2/87/62e1e426418204db520f955ffd06f1efd389feca893dad7095bf35612eec/bcrypt-4.3.0-cp39-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:d9af79d322e735b1fc33404b5765108ae0ff232d4b54666d46730f8ac1a43676", size = 297896, upload-time = "2025-02-28T01:23:39.575Z" }, + { url = "https://files.pythonhosted.org/packages/cb/c6/8fedca4c2ada1b6e889c52d2943b2f968d3427e5d65f595620ec4c06fa2f/bcrypt-4.3.0-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:f1e3ffa1365e8702dc48c8b360fef8d7afeca482809c5e45e653af82ccd088c1", size = 284492, upload-time = "2025-02-28T01:23:40.901Z" }, + { url = "https://files.pythonhosted.org/packages/4d/4d/c43332dcaaddb7710a8ff5269fcccba97ed3c85987ddaa808db084267b9a/bcrypt-4.3.0-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:3004df1b323d10021fda07a813fd33e0fd57bef0e9a480bb143877f6cba996fe", size = 279213, upload-time = "2025-02-28T01:23:42.653Z" }, + { url = "https://files.pythonhosted.org/packages/dc/7f/1e36379e169a7df3a14a1c160a49b7b918600a6008de43ff20d479e6f4b5/bcrypt-4.3.0-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:531457e5c839d8caea9b589a1bcfe3756b0547d7814e9ce3d437f17da75c32b0", size = 284162, upload-time = "2025-02-28T01:23:43.964Z" }, + { url = "https://files.pythonhosted.org/packages/1c/0a/644b2731194b0d7646f3210dc4d80c7fee3ecb3a1f791a6e0ae6bb8684e3/bcrypt-4.3.0-cp39-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:17a854d9a7a476a89dcef6c8bd119ad23e0f82557afbd2c442777a16408e614f", size = 312856, upload-time = "2025-02-28T01:23:46.011Z" }, + { url = "https://files.pythonhosted.org/packages/dc/62/2a871837c0bb6ab0c9a88bf54de0fc021a6a08832d4ea313ed92a669d437/bcrypt-4.3.0-cp39-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:6fb1fd3ab08c0cbc6826a2e0447610c6f09e983a281b919ed721ad32236b8b23", size = 316726, upload-time = "2025-02-28T01:23:47.575Z" }, + { url = "https://files.pythonhosted.org/packages/0c/a1/9898ea3faac0b156d457fd73a3cb9c2855c6fd063e44b8522925cdd8ce46/bcrypt-4.3.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:e965a9c1e9a393b8005031ff52583cedc15b7884fce7deb8b0346388837d6cfe", size = 343664, upload-time = "2025-02-28T01:23:49.059Z" }, + { url = "https://files.pythonhosted.org/packages/40/f2/71b4ed65ce38982ecdda0ff20c3ad1b15e71949c78b2c053df53629ce940/bcrypt-4.3.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:79e70b8342a33b52b55d93b3a59223a844962bef479f6a0ea318ebbcadf71505", size = 363128, upload-time = "2025-02-28T01:23:50.399Z" }, + { url = "https://files.pythonhosted.org/packages/11/99/12f6a58eca6dea4be992d6c681b7ec9410a1d9f5cf368c61437e31daa879/bcrypt-4.3.0-cp39-abi3-win32.whl", hash = "sha256:b4d4e57f0a63fd0b358eb765063ff661328f69a04494427265950c71b992a39a", size = 160598, upload-time = "2025-02-28T01:23:51.775Z" }, + { url = "https://files.pythonhosted.org/packages/a9/cf/45fb5261ece3e6b9817d3d82b2f343a505fd58674a92577923bc500bd1aa/bcrypt-4.3.0-cp39-abi3-win_amd64.whl", hash = "sha256:e53e074b120f2877a35cc6c736b8eb161377caae8925c17688bd46ba56daaa5b", size = 152799, upload-time = "2025-02-28T01:23:53.139Z" }, + { url = "https://files.pythonhosted.org/packages/4c/b1/1289e21d710496b88340369137cc4c5f6ee036401190ea116a7b4ae6d32a/bcrypt-4.3.0-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:a839320bf27d474e52ef8cb16449bb2ce0ba03ca9f44daba6d93fa1d8828e48a", size = 275103, upload-time = "2025-02-28T01:24:00.764Z" }, + { url = "https://files.pythonhosted.org/packages/94/41/19be9fe17e4ffc5d10b7b67f10e459fc4eee6ffe9056a88de511920cfd8d/bcrypt-4.3.0-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:bdc6a24e754a555d7316fa4774e64c6c3997d27ed2d1964d55920c7c227bc4ce", size = 280513, upload-time = "2025-02-28T01:24:02.243Z" }, + { url = "https://files.pythonhosted.org/packages/aa/73/05687a9ef89edebdd8ad7474c16d8af685eb4591c3c38300bb6aad4f0076/bcrypt-4.3.0-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:55a935b8e9a1d2def0626c4269db3fcd26728cbff1e84f0341465c31c4ee56d8", size = 274685, upload-time = "2025-02-28T01:24:04.512Z" }, + { url = "https://files.pythonhosted.org/packages/63/13/47bba97924ebe86a62ef83dc75b7c8a881d53c535f83e2c54c4bd701e05c/bcrypt-4.3.0-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:57967b7a28d855313a963aaea51bf6df89f833db4320da458e5b3c5ab6d4c938", size = 280110, upload-time = "2025-02-28T01:24:05.896Z" }, ] [[package]] @@ -475,27 +505,27 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "soupsieve" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/af/0b/44c39cf3b18a9280950ad63a579ce395dda4c32193ee9da7ff0aed547094/beautifulsoup4-4.12.2.tar.gz", hash = "sha256:492bbc69dca35d12daac71c4db1bfff0c876c00ef4a2ffacce226d4638eb72da", size = 505113 } +sdist = { url = "https://files.pythonhosted.org/packages/af/0b/44c39cf3b18a9280950ad63a579ce395dda4c32193ee9da7ff0aed547094/beautifulsoup4-4.12.2.tar.gz", hash = "sha256:492bbc69dca35d12daac71c4db1bfff0c876c00ef4a2ffacce226d4638eb72da", size = 505113, upload-time = "2023-04-07T15:02:49.038Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/57/f4/a69c20ee4f660081a7dedb1ac57f29be9378e04edfcb90c526b923d4bebc/beautifulsoup4-4.12.2-py3-none-any.whl", hash = "sha256:bd2520ca0d9d7d12694a53d44ac482d181b4ec1888909b035a3dbf40d0f57d4a", size = 142979 }, + { url = "https://files.pythonhosted.org/packages/57/f4/a69c20ee4f660081a7dedb1ac57f29be9378e04edfcb90c526b923d4bebc/beautifulsoup4-4.12.2-py3-none-any.whl", hash = "sha256:bd2520ca0d9d7d12694a53d44ac482d181b4ec1888909b035a3dbf40d0f57d4a", size = 142979, upload-time = "2023-04-07T15:02:50.77Z" }, ] [[package]] name = "billiard" version = "4.2.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/7c/58/1546c970afcd2a2428b1bfafecf2371d8951cc34b46701bea73f4280989e/billiard-4.2.1.tar.gz", hash = "sha256:12b641b0c539073fc8d3f5b8b7be998956665c4233c7c1fcd66a7e677c4fb36f", size = 155031 } +sdist = { url = "https://files.pythonhosted.org/packages/7c/58/1546c970afcd2a2428b1bfafecf2371d8951cc34b46701bea73f4280989e/billiard-4.2.1.tar.gz", hash = "sha256:12b641b0c539073fc8d3f5b8b7be998956665c4233c7c1fcd66a7e677c4fb36f", size = 155031, upload-time = "2024-09-21T13:40:22.491Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/30/da/43b15f28fe5f9e027b41c539abc5469052e9d48fd75f8ff094ba2a0ae767/billiard-4.2.1-py3-none-any.whl", hash = "sha256:40b59a4ac8806ba2c2369ea98d876bc6108b051c227baffd928c644d15d8f3cb", size = 86766 }, + { url = "https://files.pythonhosted.org/packages/30/da/43b15f28fe5f9e027b41c539abc5469052e9d48fd75f8ff094ba2a0ae767/billiard-4.2.1-py3-none-any.whl", hash = "sha256:40b59a4ac8806ba2c2369ea98d876bc6108b051c227baffd928c644d15d8f3cb", size = 86766, upload-time = "2024-09-21T13:40:20.188Z" }, ] [[package]] name = "blinker" version = "1.9.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/21/28/9b3f50ce0e048515135495f198351908d99540d69bfdc8c1d15b73dc55ce/blinker-1.9.0.tar.gz", hash = "sha256:b4ce2265a7abece45e7cc896e98dbebe6cead56bcf805a3d23136d145f5445bf", size = 22460 } +sdist = { url = "https://files.pythonhosted.org/packages/21/28/9b3f50ce0e048515135495f198351908d99540d69bfdc8c1d15b73dc55ce/blinker-1.9.0.tar.gz", hash = "sha256:b4ce2265a7abece45e7cc896e98dbebe6cead56bcf805a3d23136d145f5445bf", size = 22460, upload-time = "2024-11-08T17:25:47.436Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/10/cb/f2ad4230dc2eb1a74edf38f1a38b9b52277f75bef262d8908e60d957e13c/blinker-1.9.0-py3-none-any.whl", hash = "sha256:ba0efaa9080b619ff2f3459d1d500c57bddea4a6b424b60a91141db6fd2f08bc", size = 8458 }, + { url = "https://files.pythonhosted.org/packages/10/cb/f2ad4230dc2eb1a74edf38f1a38b9b52277f75bef262d8908e60d957e13c/blinker-1.9.0-py3-none-any.whl", hash = "sha256:ba0efaa9080b619ff2f3459d1d500c57bddea4a6b424b60a91141db6fd2f08bc", size = 8458, upload-time = "2024-11-08T17:25:46.184Z" }, ] [[package]] @@ -507,9 +537,28 @@ dependencies = [ { name = "jmespath" }, { name = "s3transfer" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/f7/99/3e8b48f15580672eda20f33439fc1622bd611f6238b6d05407320e1fb98c/boto3-1.35.99.tar.gz", hash = "sha256:e0abd794a7a591d90558e92e29a9f8837d25ece8e3c120e530526fe27eba5fca", size = 111028 } +sdist = { url = "https://files.pythonhosted.org/packages/f7/99/3e8b48f15580672eda20f33439fc1622bd611f6238b6d05407320e1fb98c/boto3-1.35.99.tar.gz", hash = "sha256:e0abd794a7a591d90558e92e29a9f8837d25ece8e3c120e530526fe27eba5fca", size = 111028, upload-time = "2025-01-14T20:20:28.636Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/65/77/8bbca82f70b062181cf0ae53fd43f1ac6556f3078884bfef9da2269c06a3/boto3-1.35.99-py3-none-any.whl", hash = "sha256:83e560faaec38a956dfb3d62e05e1703ee50432b45b788c09e25107c5058bd71", size = 139178, upload-time = "2025-01-14T20:20:25.48Z" }, +] + +[[package]] +name = "boto3-stubs" +version = "1.38.28" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "botocore-stubs" }, + { name = "types-s3transfer" }, + { name = "typing-extensions", marker = "python_full_version < '3.12'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7b/dc/c4b318bdf66ff4973d3cbf0f218e47bdf558b94d921c8ff27d401e8ca4f9/boto3_stubs-1.38.28.tar.gz", hash = "sha256:fae8e009ea4d810b77e49d88247183b5d8d153bf268ebde8b4abdf58a701802f", size = 99065, upload-time = "2025-06-02T19:42:51.659Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/65/77/8bbca82f70b062181cf0ae53fd43f1ac6556f3078884bfef9da2269c06a3/boto3-1.35.99-py3-none-any.whl", hash = "sha256:83e560faaec38a956dfb3d62e05e1703ee50432b45b788c09e25107c5058bd71", size = 139178 }, + { url = "https://files.pythonhosted.org/packages/8b/4a/d70ef76080e0d37e45d07d65801a6680f5bc1d156333a4e5d0ca6103d45f/boto3_stubs-1.38.28-py3-none-any.whl", hash = "sha256:accd8c0943a8d960c5c2d4a7aa661fd43e842893e76adf1b5b163c02f4f62537", size = 68668, upload-time = "2025-06-02T19:42:43.426Z" }, +] + +[package.optional-dependencies] +bedrock-runtime = [ + { name = "mypy-boto3-bedrock-runtime" }, ] [[package]] @@ -521,76 +570,88 @@ dependencies = [ { name = "python-dateutil" }, { name = "urllib3" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/7c/9c/1df6deceee17c88f7170bad8325aa91452529d683486273928eecfd946d8/botocore-1.35.99.tar.gz", hash = "sha256:1eab44e969c39c5f3d9a3104a0836c24715579a455f12b3979a31d7cde51b3c3", size = 13490969 } +sdist = { url = "https://files.pythonhosted.org/packages/7c/9c/1df6deceee17c88f7170bad8325aa91452529d683486273928eecfd946d8/botocore-1.35.99.tar.gz", hash = "sha256:1eab44e969c39c5f3d9a3104a0836c24715579a455f12b3979a31d7cde51b3c3", size = 13490969, upload-time = "2025-01-14T20:20:11.419Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fc/dd/d87e2a145fad9e08d0ec6edcf9d71f838ccc7acdd919acc4c0d4a93515f8/botocore-1.35.99-py3-none-any.whl", hash = "sha256:b22d27b6b617fc2d7342090d6129000af2efd20174215948c0d7ae2da0fab445", size = 13293216, upload-time = "2025-01-14T20:20:06.427Z" }, +] + +[[package]] +name = "botocore-stubs" +version = "1.38.28" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "types-awscrt" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/bc/0e/a47d392ee3fbd444a628f1b7257affc0a13c45e8742de4eb31fa4e2758f2/botocore_stubs-1.38.28.tar.gz", hash = "sha256:b9549050b81051bdbb91966323d6a2b6c5c78ca8dcc328e16dfc44b765be39be", size = 42312, upload-time = "2025-06-02T20:18:13.928Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/fc/dd/d87e2a145fad9e08d0ec6edcf9d71f838ccc7acdd919acc4c0d4a93515f8/botocore-1.35.99-py3-none-any.whl", hash = "sha256:b22d27b6b617fc2d7342090d6129000af2efd20174215948c0d7ae2da0fab445", size = 13293216 }, + { url = "https://files.pythonhosted.org/packages/6f/3d/26c1a62b379f0dd401798f9f8f9331ac40f3c3917c34b5eed3ed0b85e2b0/botocore_stubs-1.38.28-py3-none-any.whl", hash = "sha256:9151ced682edb44b28202d268f4dc5ef5534be8b592b87e07ce4c99650818bce", size = 65629, upload-time = "2025-06-02T20:18:11.886Z" }, ] [[package]] name = "bottleneck" -version = "1.4.2" +version = "1.5.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "numpy" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/2e/61/9fb34409d58f04e1929da41666a055c36f9495903ff669b80c893bdee65f/bottleneck-1.4.2.tar.gz", hash = "sha256:fa8e8e1799dea5483ce6669462660f9d9a95649f6f98a80d315b84ec89f449f4", size = 103563 } +sdist = { url = "https://files.pythonhosted.org/packages/80/82/dd20e69b97b9072ed2d26cc95c0a573461986bf62f7fde7ac59143490918/bottleneck-1.5.0.tar.gz", hash = "sha256:c860242cf20e69d5aab2ec3c5d6c8c2a15f19e4b25b28b8fca2c2a12cefae9d8", size = 104177, upload-time = "2025-05-13T21:11:21.158Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/88/b8/31a1cc8279bf11a60c04b844a42666927307a47bb48964cbd92ec9f40e3e/Bottleneck-1.4.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b6902ebf3e85315b481bc084f10c5770f8240275ad1e039ac69c7c8d2013b040", size = 98565 }, - { url = "https://files.pythonhosted.org/packages/16/64/09d72babae7cc29341c52f2e9381066672743d4f797c86b1e735205d5fc8/Bottleneck-1.4.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c2fd34b9b490204f95288f0dd35d37042486a95029617246c88c0f94a0ab49fe", size = 364986 }, - { url = "https://files.pythonhosted.org/packages/7e/d6/39e957e9df9ab16df9c531e8ddf71594877063d27aa036dd105b66d3b3b3/Bottleneck-1.4.2-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:122845e3106c85465551d4a9a3777841347cfedfbebb3aa985cca110e07030b1", size = 360256 }, - { url = "https://files.pythonhosted.org/packages/ff/cb/d287febe0e6504194ba94cf4a6d80df66a0031ca33a32b30f00c030238cc/Bottleneck-1.4.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:1f61658ebdf5a178298544336b65020730bf86cc092dab5f6579a99a86bd888b", size = 369507 }, - { url = "https://files.pythonhosted.org/packages/dc/1e/9310f058ddee71798a76ab15c5c1ad71f0a5c3c6348f7faab9b6da038484/Bottleneck-1.4.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:7c7d29c044a3511b36fd744503c3e697e279c273a8477a6d91a2831d04fd19e0", size = 360282 }, - { url = "https://files.pythonhosted.org/packages/96/cb/c1f2a37e86e9fa47845259f0a8f32d550f7f27b908432369de055be9f7c4/Bottleneck-1.4.2-cp311-cp311-win32.whl", hash = "sha256:c663cbba8f52011fd82ee08c6a85c93b34b19e0e7ebba322d2d67809f34e0597", size = 106936 }, - { url = "https://files.pythonhosted.org/packages/d3/eb/3fd23404bbc612cf9e4883c3c2b359bd14528e234d5c40bb29bcfd591ef8/Bottleneck-1.4.2-cp311-cp311-win_amd64.whl", hash = "sha256:89651ef18c06616850203bf8875c958c5d316ea48d8ba60d9b450199d39ae391", size = 111617 }, - { url = "https://files.pythonhosted.org/packages/d2/26/6f5124e31a67f75e2a3b9239cc382145326e91fc45e7d7bc9ebffa05fdfa/Bottleneck-1.4.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a74ddd0417f42eeaba37375f0fc065b28451e0fba45cb2f99e88880b10b3fa43", size = 98681 }, - { url = "https://files.pythonhosted.org/packages/c4/93/e100b6eda77f2aecf5f16157b8c04dd3463913ba188b582650cd77ccf42b/Bottleneck-1.4.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:070d22f2f62ab81297380a89492cca931e4d9443fa4b84c2baeb52db09c3b1b4", size = 365422 }, - { url = "https://files.pythonhosted.org/packages/82/2b/c6fea2bb048d04c13b8564052818a198d50ce58d5f439ec69c2b0c458703/Bottleneck-1.4.2-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1fc4e7645bd425c05e05acd5541e9e09cb4179e71164e862f082561bf4509eac", size = 361844 }, - { url = "https://files.pythonhosted.org/packages/8f/4c/811475885bd60cf0cb28822568d0c0c3c7d7de4fbccd2ebb66863e7dc726/Bottleneck-1.4.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:037315c56605128a39f77d19af6a6019dc8c21a63694a4bfef3c026ed963be2e", size = 370369 }, - { url = "https://files.pythonhosted.org/packages/fd/ee/0a8157e6bbd2168bf6171811534a5a73a35f54c453dd7d86a323773b5bd7/Bottleneck-1.4.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:99778329331d5fae8df19772a019e8b73ba4d9d1650f110cd995ab7657114db0", size = 361786 }, - { url = "https://files.pythonhosted.org/packages/fa/6b/e8fda0510b8fa0f3f9a3586efc941abe9d546198e95ae5690c3c83370b36/Bottleneck-1.4.2-cp312-cp312-win32.whl", hash = "sha256:7363b3c8ce6ca433779cd7e96bcb94c0e516dcacadff0011adcbf0b3ac86bc9d", size = 107149 }, - { url = "https://files.pythonhosted.org/packages/22/25/908b75a329a05b82d717661aa95a1968d9dae0e68c654d5e16bfe0d6fbb6/Bottleneck-1.4.2-cp312-cp312-win_amd64.whl", hash = "sha256:48c6b9d9287c4102b803fcb01ae66ae7ef6b310b711b4b7b7e23bf952894dc05", size = 111766 }, + { url = "https://files.pythonhosted.org/packages/fd/5e/d66b2487c12fa3343013ac87a03bcefbeacf5f13ffa4ad56bb4bce319d09/bottleneck-1.5.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9be5dfdf1a662d1d4423d7b7e8dd9a1b7046dcc2ce67b6e94a31d1cc57a8558f", size = 99536, upload-time = "2025-05-13T21:10:34.324Z" }, + { url = "https://files.pythonhosted.org/packages/28/24/e7030fe27c7a9eb9cc8c86a4d74a7422d2c3e3466aecdf658617bea40491/bottleneck-1.5.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:16fead35c0b5d307815997eef67d03c2151f255ca889e0fc3d68703f41aa5302", size = 357134, upload-time = "2025-05-13T21:10:35.764Z" }, + { url = "https://files.pythonhosted.org/packages/d0/ce/91b0514a7ac456d934ebd90f0cae2314302f33c16e9489c99a4f496b1cff/bottleneck-1.5.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:049162927cf802208cc8691fb99b108afe74656cdc96b9e2067cf56cb9d84056", size = 361243, upload-time = "2025-05-13T21:10:36.851Z" }, + { url = "https://files.pythonhosted.org/packages/be/f7/1a41889a6c0863b9f6236c14182bfb5f37c964e791b90ba721450817fc24/bottleneck-1.5.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:2f5e863a4fdaf9c85416789aeb333d1cdd3603037fd854ad58b0e2ac73be16cf", size = 361326, upload-time = "2025-05-13T21:10:37.904Z" }, + { url = "https://files.pythonhosted.org/packages/d3/e8/d4772b5321cf62b53c792253e38db1f6beee4f2de81e65bce5a6fe78df8e/bottleneck-1.5.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:8d123762f78717fc35ecf10cad45d08273fcb12ab40b3c847190b83fec236f03", size = 371849, upload-time = "2025-05-13T21:10:40.544Z" }, + { url = "https://files.pythonhosted.org/packages/29/dc/f88f6d476d7a3d6bd92f6e66f814d0bf088be20f0c6f716caa2a2ca02e82/bottleneck-1.5.0-cp311-cp311-win32.whl", hash = "sha256:07c2c1aa39917b5c9be77e85791aa598e8b2c00f8597a198b93628bbfde72a3f", size = 107710, upload-time = "2025-05-13T21:10:41.648Z" }, + { url = "https://files.pythonhosted.org/packages/17/03/f89a2eff4f919a7c98433df3be6fd9787c72966a36be289ec180f505b2d5/bottleneck-1.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:80ef9eea2a92fc5a1c04734aa1bcf317253241062c962eaa6e7f123b583d0109", size = 112055, upload-time = "2025-05-13T21:10:42.549Z" }, + { url = "https://files.pythonhosted.org/packages/8e/64/127e174cec548ab98bc0fa868b4f5d3ae5276e25c856d31d235d83d885a8/bottleneck-1.5.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:dbb0f0d38feda63050aa253cf9435e81a0ecfac954b0df84896636be9eabd9b6", size = 99640, upload-time = "2025-05-13T21:10:43.574Z" }, + { url = "https://files.pythonhosted.org/packages/59/89/6e0b6463a36fd4771a9227d22ea904f892b80d95154399dd3e89fb6001f8/bottleneck-1.5.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:613165ce39bf6bd80f5307da0f05842ba534b213a89526f1eba82ea0099592fc", size = 358009, upload-time = "2025-05-13T21:10:45.045Z" }, + { url = "https://files.pythonhosted.org/packages/f7/d6/7d1795a4a9e6383d3710a94c44010c7f2a8ba58cb5f2d9e2834a1c179afe/bottleneck-1.5.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f218e4dae6511180dcc4f06d8300e0c81e7f3df382091f464c5a919d289fab8e", size = 362875, upload-time = "2025-05-13T21:10:46.16Z" }, + { url = "https://files.pythonhosted.org/packages/2b/1b/bab35ef291b9379a97e2fb986ce75f32eda38a47fc4954177b43590ee85e/bottleneck-1.5.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3886799cceb271eb67d057f6ecb13fb4582bda17a3b13b4fa0334638c59637c6", size = 361194, upload-time = "2025-05-13T21:10:47.631Z" }, + { url = "https://files.pythonhosted.org/packages/d5/f3/a416fed726b81d2093578bc2112077f011c9f57b31e7ff3a1a9b00cce3d3/bottleneck-1.5.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:dc8d553d4bf033d3e025cd32d4c034d2daf10709e31ced3909811d1c843e451c", size = 373253, upload-time = "2025-05-13T21:10:48.634Z" }, + { url = "https://files.pythonhosted.org/packages/0a/40/c372f9e59b3ce340d170fbdc24c12df3d2b3c22c4809b149b7129044180b/bottleneck-1.5.0-cp312-cp312-win32.whl", hash = "sha256:0dca825048a3076f34c4a35409e3277b31ceeb3cbb117bbe2a13ff5c214bcabc", size = 107915, upload-time = "2025-05-13T21:10:50.639Z" }, + { url = "https://files.pythonhosted.org/packages/28/5a/57571a3cd4e356bbd636bb2225fbe916f29adc2235ba3dc77cd4085c91c8/bottleneck-1.5.0-cp312-cp312-win_amd64.whl", hash = "sha256:f26005740e6ef6013eba8a48241606a963e862a601671eab064b7835cd12ef3d", size = 112148, upload-time = "2025-05-13T21:10:51.626Z" }, ] [[package]] name = "brotli" version = "1.1.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/2f/c2/f9e977608bdf958650638c3f1e28f85a1b075f075ebbe77db8555463787b/Brotli-1.1.0.tar.gz", hash = "sha256:81de08ac11bcb85841e440c13611c00b67d3bf82698314928d0b676362546724", size = 7372270 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/96/12/ad41e7fadd5db55459c4c401842b47f7fee51068f86dd2894dd0dcfc2d2a/Brotli-1.1.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a3daabb76a78f829cafc365531c972016e4aa8d5b4bf60660ad8ecee19df7ccc", size = 873068 }, - { url = "https://files.pythonhosted.org/packages/95/4e/5afab7b2b4b61a84e9c75b17814198ce515343a44e2ed4488fac314cd0a9/Brotli-1.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c8146669223164fc87a7e3de9f81e9423c67a79d6b3447994dfb9c95da16e2d6", size = 446244 }, - { url = "https://files.pythonhosted.org/packages/9d/e6/f305eb61fb9a8580c525478a4a34c5ae1a9bcb12c3aee619114940bc513d/Brotli-1.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:30924eb4c57903d5a7526b08ef4a584acc22ab1ffa085faceb521521d2de32dd", size = 2906500 }, - { url = "https://files.pythonhosted.org/packages/3e/4f/af6846cfbc1550a3024e5d3775ede1e00474c40882c7bf5b37a43ca35e91/Brotli-1.1.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ceb64bbc6eac5a140ca649003756940f8d6a7c444a68af170b3187623b43bebf", size = 2943950 }, - { url = "https://files.pythonhosted.org/packages/b3/e7/ca2993c7682d8629b62630ebf0d1f3bb3d579e667ce8e7ca03a0a0576a2d/Brotli-1.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a469274ad18dc0e4d316eefa616d1d0c2ff9da369af19fa6f3daa4f09671fd61", size = 2918527 }, - { url = "https://files.pythonhosted.org/packages/b3/96/da98e7bedc4c51104d29cc61e5f449a502dd3dbc211944546a4cc65500d3/Brotli-1.1.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:524f35912131cc2cabb00edfd8d573b07f2d9f21fa824bd3fb19725a9cf06327", size = 2845489 }, - { url = "https://files.pythonhosted.org/packages/e8/ef/ccbc16947d6ce943a7f57e1a40596c75859eeb6d279c6994eddd69615265/Brotli-1.1.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:5b3cc074004d968722f51e550b41a27be656ec48f8afaeeb45ebf65b561481dd", size = 2914080 }, - { url = "https://files.pythonhosted.org/packages/80/d6/0bd38d758d1afa62a5524172f0b18626bb2392d717ff94806f741fcd5ee9/Brotli-1.1.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:19c116e796420b0cee3da1ccec3b764ed2952ccfcc298b55a10e5610ad7885f9", size = 2813051 }, - { url = "https://files.pythonhosted.org/packages/14/56/48859dd5d129d7519e001f06dcfbb6e2cf6db92b2702c0c2ce7d97e086c1/Brotli-1.1.0-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:510b5b1bfbe20e1a7b3baf5fed9e9451873559a976c1a78eebaa3b86c57b4265", size = 2938172 }, - { url = "https://files.pythonhosted.org/packages/3d/77/a236d5f8cd9e9f4348da5acc75ab032ab1ab2c03cc8f430d24eea2672888/Brotli-1.1.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:a1fd8a29719ccce974d523580987b7f8229aeace506952fa9ce1d53a033873c8", size = 2933023 }, - { url = "https://files.pythonhosted.org/packages/f1/87/3b283efc0f5cb35f7f84c0c240b1e1a1003a5e47141a4881bf87c86d0ce2/Brotli-1.1.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c247dd99d39e0338a604f8c2b3bc7061d5c2e9e2ac7ba9cc1be5a69cb6cd832f", size = 2935871 }, - { url = "https://files.pythonhosted.org/packages/f3/eb/2be4cc3e2141dc1a43ad4ca1875a72088229de38c68e842746b342667b2a/Brotli-1.1.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:1b2c248cd517c222d89e74669a4adfa5577e06ab68771a529060cf5a156e9757", size = 2847784 }, - { url = "https://files.pythonhosted.org/packages/66/13/b58ddebfd35edde572ccefe6890cf7c493f0c319aad2a5badee134b4d8ec/Brotli-1.1.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:2a24c50840d89ded6c9a8fdc7b6ed3692ed4e86f1c4a4a938e1e92def92933e0", size = 3034905 }, - { url = "https://files.pythonhosted.org/packages/84/9c/bc96b6c7db824998a49ed3b38e441a2cae9234da6fa11f6ed17e8cf4f147/Brotli-1.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f31859074d57b4639318523d6ffdca586ace54271a73ad23ad021acd807eb14b", size = 2929467 }, - { url = "https://files.pythonhosted.org/packages/e7/71/8f161dee223c7ff7fea9d44893fba953ce97cf2c3c33f78ba260a91bcff5/Brotli-1.1.0-cp311-cp311-win32.whl", hash = "sha256:39da8adedf6942d76dc3e46653e52df937a3c4d6d18fdc94a7c29d263b1f5b50", size = 333169 }, - { url = "https://files.pythonhosted.org/packages/02/8a/fece0ee1057643cb2a5bbf59682de13f1725f8482b2c057d4e799d7ade75/Brotli-1.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:aac0411d20e345dc0920bdec5548e438e999ff68d77564d5e9463a7ca9d3e7b1", size = 357253 }, - { url = "https://files.pythonhosted.org/packages/5c/d0/5373ae13b93fe00095a58efcbce837fd470ca39f703a235d2a999baadfbc/Brotli-1.1.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:32d95b80260d79926f5fab3c41701dbb818fde1c9da590e77e571eefd14abe28", size = 815693 }, - { url = "https://files.pythonhosted.org/packages/8e/48/f6e1cdf86751300c288c1459724bfa6917a80e30dbfc326f92cea5d3683a/Brotli-1.1.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b760c65308ff1e462f65d69c12e4ae085cff3b332d894637f6273a12a482d09f", size = 422489 }, - { url = "https://files.pythonhosted.org/packages/06/88/564958cedce636d0f1bed313381dfc4b4e3d3f6015a63dae6146e1b8c65c/Brotli-1.1.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:316cc9b17edf613ac76b1f1f305d2a748f1b976b033b049a6ecdfd5612c70409", size = 873081 }, - { url = "https://files.pythonhosted.org/packages/58/79/b7026a8bb65da9a6bb7d14329fd2bd48d2b7f86d7329d5cc8ddc6a90526f/Brotli-1.1.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:caf9ee9a5775f3111642d33b86237b05808dafcd6268faa492250e9b78046eb2", size = 446244 }, - { url = "https://files.pythonhosted.org/packages/e5/18/c18c32ecea41b6c0004e15606e274006366fe19436b6adccc1ae7b2e50c2/Brotli-1.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:70051525001750221daa10907c77830bc889cb6d865cc0b813d9db7fefc21451", size = 2906505 }, - { url = "https://files.pythonhosted.org/packages/08/c8/69ec0496b1ada7569b62d85893d928e865df29b90736558d6c98c2031208/Brotli-1.1.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7f4bf76817c14aa98cc6697ac02f3972cb8c3da93e9ef16b9c66573a68014f91", size = 2944152 }, - { url = "https://files.pythonhosted.org/packages/ab/fb/0517cea182219d6768113a38167ef6d4eb157a033178cc938033a552ed6d/Brotli-1.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d0c5516f0aed654134a2fc936325cc2e642f8a0e096d075209672eb321cff408", size = 2919252 }, - { url = "https://files.pythonhosted.org/packages/c7/53/73a3431662e33ae61a5c80b1b9d2d18f58dfa910ae8dd696e57d39f1a2f5/Brotli-1.1.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6c3020404e0b5eefd7c9485ccf8393cfb75ec38ce75586e046573c9dc29967a0", size = 2845955 }, - { url = "https://files.pythonhosted.org/packages/55/ac/bd280708d9c5ebdbf9de01459e625a3e3803cce0784f47d633562cf40e83/Brotli-1.1.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:4ed11165dd45ce798d99a136808a794a748d5dc38511303239d4e2363c0695dc", size = 2914304 }, - { url = "https://files.pythonhosted.org/packages/76/58/5c391b41ecfc4527d2cc3350719b02e87cb424ef8ba2023fb662f9bf743c/Brotli-1.1.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:4093c631e96fdd49e0377a9c167bfd75b6d0bad2ace734c6eb20b348bc3ea180", size = 2814452 }, - { url = "https://files.pythonhosted.org/packages/c7/4e/91b8256dfe99c407f174924b65a01f5305e303f486cc7a2e8a5d43c8bec3/Brotli-1.1.0-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:7e4c4629ddad63006efa0ef968c8e4751c5868ff0b1c5c40f76524e894c50248", size = 2938751 }, - { url = "https://files.pythonhosted.org/packages/5a/a6/e2a39a5d3b412938362bbbeba5af904092bf3f95b867b4a3eb856104074e/Brotli-1.1.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:861bf317735688269936f755fa136a99d1ed526883859f86e41a5d43c61d8966", size = 2933757 }, - { url = "https://files.pythonhosted.org/packages/13/f0/358354786280a509482e0e77c1a5459e439766597d280f28cb097642fc26/Brotli-1.1.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:87a3044c3a35055527ac75e419dfa9f4f3667a1e887ee80360589eb8c90aabb9", size = 2936146 }, - { url = "https://files.pythonhosted.org/packages/80/f7/daf538c1060d3a88266b80ecc1d1c98b79553b3f117a485653f17070ea2a/Brotli-1.1.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:c5529b34c1c9d937168297f2c1fde7ebe9ebdd5e121297ff9c043bdb2ae3d6fb", size = 2848055 }, - { url = "https://files.pythonhosted.org/packages/ad/cf/0eaa0585c4077d3c2d1edf322d8e97aabf317941d3a72d7b3ad8bce004b0/Brotli-1.1.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:ca63e1890ede90b2e4454f9a65135a4d387a4585ff8282bb72964fab893f2111", size = 3035102 }, - { url = "https://files.pythonhosted.org/packages/d8/63/1c1585b2aa554fe6dbce30f0c18bdbc877fa9a1bf5ff17677d9cca0ac122/Brotli-1.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e79e6520141d792237c70bcd7a3b122d00f2613769ae0cb61c52e89fd3443839", size = 2930029 }, - { url = "https://files.pythonhosted.org/packages/5f/3b/4e3fd1893eb3bbfef8e5a80d4508bec17a57bb92d586c85c12d28666bb13/Brotli-1.1.0-cp312-cp312-win32.whl", hash = "sha256:5f4d5ea15c9382135076d2fb28dde923352fe02951e66935a9efaac8f10e81b0", size = 333276 }, - { url = "https://files.pythonhosted.org/packages/3d/d5/942051b45a9e883b5b6e98c041698b1eb2012d25e5948c58d6bf85b1bb43/Brotli-1.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:906bc3a79de8c4ae5b86d3d75a8b77e44404b0f4261714306e3ad248d8ab0951", size = 357255 }, +sdist = { url = "https://files.pythonhosted.org/packages/2f/c2/f9e977608bdf958650638c3f1e28f85a1b075f075ebbe77db8555463787b/Brotli-1.1.0.tar.gz", hash = "sha256:81de08ac11bcb85841e440c13611c00b67d3bf82698314928d0b676362546724", size = 7372270, upload-time = "2023-09-07T14:05:41.643Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/96/12/ad41e7fadd5db55459c4c401842b47f7fee51068f86dd2894dd0dcfc2d2a/Brotli-1.1.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a3daabb76a78f829cafc365531c972016e4aa8d5b4bf60660ad8ecee19df7ccc", size = 873068, upload-time = "2023-09-07T14:03:37.779Z" }, + { url = "https://files.pythonhosted.org/packages/95/4e/5afab7b2b4b61a84e9c75b17814198ce515343a44e2ed4488fac314cd0a9/Brotli-1.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c8146669223164fc87a7e3de9f81e9423c67a79d6b3447994dfb9c95da16e2d6", size = 446244, upload-time = "2023-09-07T14:03:39.223Z" }, + { url = "https://files.pythonhosted.org/packages/9d/e6/f305eb61fb9a8580c525478a4a34c5ae1a9bcb12c3aee619114940bc513d/Brotli-1.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:30924eb4c57903d5a7526b08ef4a584acc22ab1ffa085faceb521521d2de32dd", size = 2906500, upload-time = "2023-09-07T14:03:40.858Z" }, + { url = "https://files.pythonhosted.org/packages/3e/4f/af6846cfbc1550a3024e5d3775ede1e00474c40882c7bf5b37a43ca35e91/Brotli-1.1.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ceb64bbc6eac5a140ca649003756940f8d6a7c444a68af170b3187623b43bebf", size = 2943950, upload-time = "2023-09-07T14:03:42.896Z" }, + { url = "https://files.pythonhosted.org/packages/b3/e7/ca2993c7682d8629b62630ebf0d1f3bb3d579e667ce8e7ca03a0a0576a2d/Brotli-1.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a469274ad18dc0e4d316eefa616d1d0c2ff9da369af19fa6f3daa4f09671fd61", size = 2918527, upload-time = "2023-09-07T14:03:44.552Z" }, + { url = "https://files.pythonhosted.org/packages/b3/96/da98e7bedc4c51104d29cc61e5f449a502dd3dbc211944546a4cc65500d3/Brotli-1.1.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:524f35912131cc2cabb00edfd8d573b07f2d9f21fa824bd3fb19725a9cf06327", size = 2845489, upload-time = "2023-09-07T14:03:46.594Z" }, + { url = "https://files.pythonhosted.org/packages/e8/ef/ccbc16947d6ce943a7f57e1a40596c75859eeb6d279c6994eddd69615265/Brotli-1.1.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:5b3cc074004d968722f51e550b41a27be656ec48f8afaeeb45ebf65b561481dd", size = 2914080, upload-time = "2023-09-07T14:03:48.204Z" }, + { url = "https://files.pythonhosted.org/packages/80/d6/0bd38d758d1afa62a5524172f0b18626bb2392d717ff94806f741fcd5ee9/Brotli-1.1.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:19c116e796420b0cee3da1ccec3b764ed2952ccfcc298b55a10e5610ad7885f9", size = 2813051, upload-time = "2023-09-07T14:03:50.348Z" }, + { url = "https://files.pythonhosted.org/packages/14/56/48859dd5d129d7519e001f06dcfbb6e2cf6db92b2702c0c2ce7d97e086c1/Brotli-1.1.0-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:510b5b1bfbe20e1a7b3baf5fed9e9451873559a976c1a78eebaa3b86c57b4265", size = 2938172, upload-time = "2023-09-07T14:03:52.395Z" }, + { url = "https://files.pythonhosted.org/packages/3d/77/a236d5f8cd9e9f4348da5acc75ab032ab1ab2c03cc8f430d24eea2672888/Brotli-1.1.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:a1fd8a29719ccce974d523580987b7f8229aeace506952fa9ce1d53a033873c8", size = 2933023, upload-time = "2023-09-07T14:03:53.96Z" }, + { url = "https://files.pythonhosted.org/packages/f1/87/3b283efc0f5cb35f7f84c0c240b1e1a1003a5e47141a4881bf87c86d0ce2/Brotli-1.1.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c247dd99d39e0338a604f8c2b3bc7061d5c2e9e2ac7ba9cc1be5a69cb6cd832f", size = 2935871, upload-time = "2024-10-18T12:32:16.688Z" }, + { url = "https://files.pythonhosted.org/packages/f3/eb/2be4cc3e2141dc1a43ad4ca1875a72088229de38c68e842746b342667b2a/Brotli-1.1.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:1b2c248cd517c222d89e74669a4adfa5577e06ab68771a529060cf5a156e9757", size = 2847784, upload-time = "2024-10-18T12:32:18.459Z" }, + { url = "https://files.pythonhosted.org/packages/66/13/b58ddebfd35edde572ccefe6890cf7c493f0c319aad2a5badee134b4d8ec/Brotli-1.1.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:2a24c50840d89ded6c9a8fdc7b6ed3692ed4e86f1c4a4a938e1e92def92933e0", size = 3034905, upload-time = "2024-10-18T12:32:20.192Z" }, + { url = "https://files.pythonhosted.org/packages/84/9c/bc96b6c7db824998a49ed3b38e441a2cae9234da6fa11f6ed17e8cf4f147/Brotli-1.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f31859074d57b4639318523d6ffdca586ace54271a73ad23ad021acd807eb14b", size = 2929467, upload-time = "2024-10-18T12:32:21.774Z" }, + { url = "https://files.pythonhosted.org/packages/e7/71/8f161dee223c7ff7fea9d44893fba953ce97cf2c3c33f78ba260a91bcff5/Brotli-1.1.0-cp311-cp311-win32.whl", hash = "sha256:39da8adedf6942d76dc3e46653e52df937a3c4d6d18fdc94a7c29d263b1f5b50", size = 333169, upload-time = "2023-09-07T14:03:55.404Z" }, + { url = "https://files.pythonhosted.org/packages/02/8a/fece0ee1057643cb2a5bbf59682de13f1725f8482b2c057d4e799d7ade75/Brotli-1.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:aac0411d20e345dc0920bdec5548e438e999ff68d77564d5e9463a7ca9d3e7b1", size = 357253, upload-time = "2023-09-07T14:03:56.643Z" }, + { url = "https://files.pythonhosted.org/packages/5c/d0/5373ae13b93fe00095a58efcbce837fd470ca39f703a235d2a999baadfbc/Brotli-1.1.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:32d95b80260d79926f5fab3c41701dbb818fde1c9da590e77e571eefd14abe28", size = 815693, upload-time = "2024-10-18T12:32:23.824Z" }, + { url = "https://files.pythonhosted.org/packages/8e/48/f6e1cdf86751300c288c1459724bfa6917a80e30dbfc326f92cea5d3683a/Brotli-1.1.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b760c65308ff1e462f65d69c12e4ae085cff3b332d894637f6273a12a482d09f", size = 422489, upload-time = "2024-10-18T12:32:25.641Z" }, + { url = "https://files.pythonhosted.org/packages/06/88/564958cedce636d0f1bed313381dfc4b4e3d3f6015a63dae6146e1b8c65c/Brotli-1.1.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:316cc9b17edf613ac76b1f1f305d2a748f1b976b033b049a6ecdfd5612c70409", size = 873081, upload-time = "2023-09-07T14:03:57.967Z" }, + { url = "https://files.pythonhosted.org/packages/58/79/b7026a8bb65da9a6bb7d14329fd2bd48d2b7f86d7329d5cc8ddc6a90526f/Brotli-1.1.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:caf9ee9a5775f3111642d33b86237b05808dafcd6268faa492250e9b78046eb2", size = 446244, upload-time = "2023-09-07T14:03:59.319Z" }, + { url = "https://files.pythonhosted.org/packages/e5/18/c18c32ecea41b6c0004e15606e274006366fe19436b6adccc1ae7b2e50c2/Brotli-1.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:70051525001750221daa10907c77830bc889cb6d865cc0b813d9db7fefc21451", size = 2906505, upload-time = "2023-09-07T14:04:01.327Z" }, + { url = "https://files.pythonhosted.org/packages/08/c8/69ec0496b1ada7569b62d85893d928e865df29b90736558d6c98c2031208/Brotli-1.1.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7f4bf76817c14aa98cc6697ac02f3972cb8c3da93e9ef16b9c66573a68014f91", size = 2944152, upload-time = "2023-09-07T14:04:03.033Z" }, + { url = "https://files.pythonhosted.org/packages/ab/fb/0517cea182219d6768113a38167ef6d4eb157a033178cc938033a552ed6d/Brotli-1.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d0c5516f0aed654134a2fc936325cc2e642f8a0e096d075209672eb321cff408", size = 2919252, upload-time = "2023-09-07T14:04:04.675Z" }, + { url = "https://files.pythonhosted.org/packages/c7/53/73a3431662e33ae61a5c80b1b9d2d18f58dfa910ae8dd696e57d39f1a2f5/Brotli-1.1.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6c3020404e0b5eefd7c9485ccf8393cfb75ec38ce75586e046573c9dc29967a0", size = 2845955, upload-time = "2023-09-07T14:04:06.585Z" }, + { url = "https://files.pythonhosted.org/packages/55/ac/bd280708d9c5ebdbf9de01459e625a3e3803cce0784f47d633562cf40e83/Brotli-1.1.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:4ed11165dd45ce798d99a136808a794a748d5dc38511303239d4e2363c0695dc", size = 2914304, upload-time = "2023-09-07T14:04:08.668Z" }, + { url = "https://files.pythonhosted.org/packages/76/58/5c391b41ecfc4527d2cc3350719b02e87cb424ef8ba2023fb662f9bf743c/Brotli-1.1.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:4093c631e96fdd49e0377a9c167bfd75b6d0bad2ace734c6eb20b348bc3ea180", size = 2814452, upload-time = "2023-09-07T14:04:10.736Z" }, + { url = "https://files.pythonhosted.org/packages/c7/4e/91b8256dfe99c407f174924b65a01f5305e303f486cc7a2e8a5d43c8bec3/Brotli-1.1.0-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:7e4c4629ddad63006efa0ef968c8e4751c5868ff0b1c5c40f76524e894c50248", size = 2938751, upload-time = "2023-09-07T14:04:12.875Z" }, + { url = "https://files.pythonhosted.org/packages/5a/a6/e2a39a5d3b412938362bbbeba5af904092bf3f95b867b4a3eb856104074e/Brotli-1.1.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:861bf317735688269936f755fa136a99d1ed526883859f86e41a5d43c61d8966", size = 2933757, upload-time = "2023-09-07T14:04:14.551Z" }, + { url = "https://files.pythonhosted.org/packages/13/f0/358354786280a509482e0e77c1a5459e439766597d280f28cb097642fc26/Brotli-1.1.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:87a3044c3a35055527ac75e419dfa9f4f3667a1e887ee80360589eb8c90aabb9", size = 2936146, upload-time = "2024-10-18T12:32:27.257Z" }, + { url = "https://files.pythonhosted.org/packages/80/f7/daf538c1060d3a88266b80ecc1d1c98b79553b3f117a485653f17070ea2a/Brotli-1.1.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:c5529b34c1c9d937168297f2c1fde7ebe9ebdd5e121297ff9c043bdb2ae3d6fb", size = 2848055, upload-time = "2024-10-18T12:32:29.376Z" }, + { url = "https://files.pythonhosted.org/packages/ad/cf/0eaa0585c4077d3c2d1edf322d8e97aabf317941d3a72d7b3ad8bce004b0/Brotli-1.1.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:ca63e1890ede90b2e4454f9a65135a4d387a4585ff8282bb72964fab893f2111", size = 3035102, upload-time = "2024-10-18T12:32:31.371Z" }, + { url = "https://files.pythonhosted.org/packages/d8/63/1c1585b2aa554fe6dbce30f0c18bdbc877fa9a1bf5ff17677d9cca0ac122/Brotli-1.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e79e6520141d792237c70bcd7a3b122d00f2613769ae0cb61c52e89fd3443839", size = 2930029, upload-time = "2024-10-18T12:32:33.293Z" }, + { url = "https://files.pythonhosted.org/packages/5f/3b/4e3fd1893eb3bbfef8e5a80d4508bec17a57bb92d586c85c12d28666bb13/Brotli-1.1.0-cp312-cp312-win32.whl", hash = "sha256:5f4d5ea15c9382135076d2fb28dde923352fe02951e66935a9efaac8f10e81b0", size = 333276, upload-time = "2023-09-07T14:04:16.49Z" }, + { url = "https://files.pythonhosted.org/packages/3d/d5/942051b45a9e883b5b6e98c041698b1eb2012d25e5948c58d6bf85b1bb43/Brotli-1.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:906bc3a79de8c4ae5b86d3d75a8b77e44404b0f4261714306e3ad248d8ab0951", size = 357255, upload-time = "2023-09-07T14:04:17.83Z" }, ] [[package]] @@ -600,14 +661,14 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cffi", marker = "platform_python_implementation == 'PyPy'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/95/9d/70caa61192f570fcf0352766331b735afa931b4c6bc9a348a0925cc13288/brotlicffi-1.1.0.0.tar.gz", hash = "sha256:b77827a689905143f87915310b93b273ab17888fd43ef350d4832c4a71083c13", size = 465192 } +sdist = { url = "https://files.pythonhosted.org/packages/95/9d/70caa61192f570fcf0352766331b735afa931b4c6bc9a348a0925cc13288/brotlicffi-1.1.0.0.tar.gz", hash = "sha256:b77827a689905143f87915310b93b273ab17888fd43ef350d4832c4a71083c13", size = 465192, upload-time = "2023-09-14T14:22:40.707Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a2/11/7b96009d3dcc2c931e828ce1e157f03824a69fb728d06bfd7b2fc6f93718/brotlicffi-1.1.0.0-cp37-abi3-macosx_10_9_x86_64.whl", hash = "sha256:9b7ae6bd1a3f0df532b6d67ff674099a96d22bc0948955cb338488c31bfb8851", size = 453786 }, - { url = "https://files.pythonhosted.org/packages/d6/e6/a8f46f4a4ee7856fbd6ac0c6fb0dc65ed181ba46cd77875b8d9bbe494d9e/brotlicffi-1.1.0.0-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:19ffc919fa4fc6ace69286e0a23b3789b4219058313cf9b45625016bf7ff996b", size = 2911165 }, - { url = "https://files.pythonhosted.org/packages/be/20/201559dff14e83ba345a5ec03335607e47467b6633c210607e693aefac40/brotlicffi-1.1.0.0-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9feb210d932ffe7798ee62e6145d3a757eb6233aa9a4e7db78dd3690d7755814", size = 2927895 }, - { url = "https://files.pythonhosted.org/packages/cd/15/695b1409264143be3c933f708a3f81d53c4a1e1ebbc06f46331decbf6563/brotlicffi-1.1.0.0-cp37-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:84763dbdef5dd5c24b75597a77e1b30c66604725707565188ba54bab4f114820", size = 2851834 }, - { url = "https://files.pythonhosted.org/packages/b4/40/b961a702463b6005baf952794c2e9e0099bde657d0d7e007f923883b907f/brotlicffi-1.1.0.0-cp37-abi3-win32.whl", hash = "sha256:1b12b50e07c3911e1efa3a8971543e7648100713d4e0971b13631cce22c587eb", size = 341731 }, - { url = "https://files.pythonhosted.org/packages/1c/fa/5408a03c041114ceab628ce21766a4ea882aa6f6f0a800e04ee3a30ec6b9/brotlicffi-1.1.0.0-cp37-abi3-win_amd64.whl", hash = "sha256:994a4f0681bb6c6c3b0925530a1926b7a189d878e6e5e38fae8efa47c5d9c613", size = 366783 }, + { url = "https://files.pythonhosted.org/packages/a2/11/7b96009d3dcc2c931e828ce1e157f03824a69fb728d06bfd7b2fc6f93718/brotlicffi-1.1.0.0-cp37-abi3-macosx_10_9_x86_64.whl", hash = "sha256:9b7ae6bd1a3f0df532b6d67ff674099a96d22bc0948955cb338488c31bfb8851", size = 453786, upload-time = "2023-09-14T14:21:57.72Z" }, + { url = "https://files.pythonhosted.org/packages/d6/e6/a8f46f4a4ee7856fbd6ac0c6fb0dc65ed181ba46cd77875b8d9bbe494d9e/brotlicffi-1.1.0.0-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:19ffc919fa4fc6ace69286e0a23b3789b4219058313cf9b45625016bf7ff996b", size = 2911165, upload-time = "2023-09-14T14:21:59.613Z" }, + { url = "https://files.pythonhosted.org/packages/be/20/201559dff14e83ba345a5ec03335607e47467b6633c210607e693aefac40/brotlicffi-1.1.0.0-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9feb210d932ffe7798ee62e6145d3a757eb6233aa9a4e7db78dd3690d7755814", size = 2927895, upload-time = "2023-09-14T14:22:01.22Z" }, + { url = "https://files.pythonhosted.org/packages/cd/15/695b1409264143be3c933f708a3f81d53c4a1e1ebbc06f46331decbf6563/brotlicffi-1.1.0.0-cp37-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:84763dbdef5dd5c24b75597a77e1b30c66604725707565188ba54bab4f114820", size = 2851834, upload-time = "2023-09-14T14:22:03.571Z" }, + { url = "https://files.pythonhosted.org/packages/b4/40/b961a702463b6005baf952794c2e9e0099bde657d0d7e007f923883b907f/brotlicffi-1.1.0.0-cp37-abi3-win32.whl", hash = "sha256:1b12b50e07c3911e1efa3a8971543e7648100713d4e0971b13631cce22c587eb", size = 341731, upload-time = "2023-09-14T14:22:05.74Z" }, + { url = "https://files.pythonhosted.org/packages/1c/fa/5408a03c041114ceab628ce21766a4ea882aa6f6f0a800e04ee3a30ec6b9/brotlicffi-1.1.0.0-cp37-abi3-win_amd64.whl", hash = "sha256:994a4f0681bb6c6c3b0925530a1926b7a189d878e6e5e38fae8efa47c5d9c613", size = 366783, upload-time = "2023-09-14T14:22:07.096Z" }, ] [[package]] @@ -617,9 +678,9 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "beautifulsoup4" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c9/aa/4acaf814ff901145da37332e05bb510452ebed97bc9602695059dd46ef39/bs4-0.0.2.tar.gz", hash = "sha256:a48685c58f50fe127722417bae83fe6badf500d54b55f7e39ffe43b798653925", size = 698 } +sdist = { url = "https://files.pythonhosted.org/packages/c9/aa/4acaf814ff901145da37332e05bb510452ebed97bc9602695059dd46ef39/bs4-0.0.2.tar.gz", hash = "sha256:a48685c58f50fe127722417bae83fe6badf500d54b55f7e39ffe43b798653925", size = 698, upload-time = "2024-01-17T18:15:47.371Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/51/bb/bf7aab772a159614954d84aa832c129624ba6c32faa559dfb200a534e50b/bs4-0.0.2-py2.py3-none-any.whl", hash = "sha256:abf8742c0805ef7f662dce4b51cca104cffe52b835238afc169142ab9b3fbccc", size = 1189 }, + { url = "https://files.pythonhosted.org/packages/51/bb/bf7aab772a159614954d84aa832c129624ba6c32faa559dfb200a534e50b/bs4-0.0.2-py2.py3-none-any.whl", hash = "sha256:abf8742c0805ef7f662dce4b51cca104cffe52b835238afc169142ab9b3fbccc", size = 1189, upload-time = "2024-01-17T18:15:48.613Z" }, ] [[package]] @@ -627,27 +688,27 @@ name = "build" version = "1.2.2.post1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "colorama", marker = "os_name == 'nt'" }, + { name = "colorama", marker = "os_name == 'nt' and sys_platform != 'linux'" }, { name = "packaging" }, { name = "pyproject-hooks" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/7d/46/aeab111f8e06793e4f0e421fcad593d547fb8313b50990f31681ee2fb1ad/build-1.2.2.post1.tar.gz", hash = "sha256:b36993e92ca9375a219c99e606a122ff365a760a2d4bba0caa09bd5278b608b7", size = 46701 } +sdist = { url = "https://files.pythonhosted.org/packages/7d/46/aeab111f8e06793e4f0e421fcad593d547fb8313b50990f31681ee2fb1ad/build-1.2.2.post1.tar.gz", hash = "sha256:b36993e92ca9375a219c99e606a122ff365a760a2d4bba0caa09bd5278b608b7", size = 46701, upload-time = "2024-10-06T17:22:25.251Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/84/c2/80633736cd183ee4a62107413def345f7e6e3c01563dbca1417363cf957e/build-1.2.2.post1-py3-none-any.whl", hash = "sha256:1d61c0887fa860c01971625baae8bdd338e517b836a2f70dd1f7aa3a6b2fc5b5", size = 22950 }, + { url = "https://files.pythonhosted.org/packages/84/c2/80633736cd183ee4a62107413def345f7e6e3c01563dbca1417363cf957e/build-1.2.2.post1-py3-none-any.whl", hash = "sha256:1d61c0887fa860c01971625baae8bdd338e517b836a2f70dd1f7aa3a6b2fc5b5", size = 22950, upload-time = "2024-10-06T17:22:23.299Z" }, ] [[package]] name = "cachetools" version = "5.3.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b3/4d/27a3e6dd09011649ad5210bdf963765bc8fa81a0827a4fc01bafd2705c5b/cachetools-5.3.3.tar.gz", hash = "sha256:ba29e2dfa0b8b556606f097407ed1aa62080ee108ab0dc5ec9d6a723a007d105", size = 26522 } +sdist = { url = "https://files.pythonhosted.org/packages/b3/4d/27a3e6dd09011649ad5210bdf963765bc8fa81a0827a4fc01bafd2705c5b/cachetools-5.3.3.tar.gz", hash = "sha256:ba29e2dfa0b8b556606f097407ed1aa62080ee108ab0dc5ec9d6a723a007d105", size = 26522, upload-time = "2024-02-26T20:33:23.386Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/fb/2b/a64c2d25a37aeb921fddb929111413049fc5f8b9a4c1aefaffaafe768d54/cachetools-5.3.3-py3-none-any.whl", hash = "sha256:0abad1021d3f8325b2fc1d2e9c8b9c9d57b04c3932657a72465447332c24d945", size = 9325 }, + { url = "https://files.pythonhosted.org/packages/fb/2b/a64c2d25a37aeb921fddb929111413049fc5f8b9a4c1aefaffaafe768d54/cachetools-5.3.3-py3-none-any.whl", hash = "sha256:0abad1021d3f8325b2fc1d2e9c8b9c9d57b04c3932657a72465447332c24d945", size = 9325, upload-time = "2024-02-26T20:33:20.308Z" }, ] [[package]] name = "celery" -version = "5.4.0" +version = "5.5.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "billiard" }, @@ -657,21 +718,20 @@ dependencies = [ { name = "click-repl" }, { name = "kombu" }, { name = "python-dateutil" }, - { name = "tzdata" }, { name = "vine" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/8a/9c/cf0bce2cc1c8971bf56629d8f180e4ca35612c7e79e6e432e785261a8be4/celery-5.4.0.tar.gz", hash = "sha256:504a19140e8d3029d5acad88330c541d4c3f64c789d85f94756762d8bca7e706", size = 1575692 } +sdist = { url = "https://files.pythonhosted.org/packages/bb/7d/6c289f407d219ba36d8b384b42489ebdd0c84ce9c413875a8aae0c85f35b/celery-5.5.3.tar.gz", hash = "sha256:6c972ae7968c2b5281227f01c3a3f984037d21c5129d07bf3550cc2afc6b10a5", size = 1667144, upload-time = "2025-06-01T11:08:12.563Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/90/c4/6a4d3772e5407622feb93dd25c86ce3c0fee746fa822a777a627d56b4f2a/celery-5.4.0-py3-none-any.whl", hash = "sha256:369631eb580cf8c51a82721ec538684994f8277637edde2dfc0dacd73ed97f64", size = 425983 }, + { url = "https://files.pythonhosted.org/packages/c9/af/0dcccc7fdcdf170f9a1585e5e96b6fb0ba1749ef6be8c89a6202284759bd/celery-5.5.3-py3-none-any.whl", hash = "sha256:0b5761a07057acee94694464ca482416b959568904c9dfa41ce8413a7d65d525", size = 438775, upload-time = "2025-06-01T11:08:09.94Z" }, ] [[package]] name = "certifi" -version = "2025.1.31" +version = "2025.4.26" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/1c/ab/c9f1e32b7b1bf505bf26f0ef697775960db7932abeb7b516de930ba2705f/certifi-2025.1.31.tar.gz", hash = "sha256:3d5da6925056f6f18f119200434a4780a94263f10d1c21d032a6f6b2baa20651", size = 167577 } +sdist = { url = "https://files.pythonhosted.org/packages/e8/9e/c05b3920a3b7d20d3d3310465f50348e5b3694f4f88c6daf736eef3024c4/certifi-2025.4.26.tar.gz", hash = "sha256:0a816057ea3cdefcef70270d2c515e4506bbc954f417fa5ade2021213bb8f0c6", size = 160705, upload-time = "2025-04-26T02:12:29.51Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/38/fc/bce832fd4fd99766c04d1ee0eead6b0ec6486fb100ae5e74c1d91292b982/certifi-2025.1.31-py3-none-any.whl", hash = "sha256:ca78db4565a652026a4db2bcdf68f2fb589ea80d0be70e03929ed730746b84fe", size = 166393 }, + { url = "https://files.pythonhosted.org/packages/4a/7e/3db2bd1b1f9e95f7cddca6d6e75e2f2bd9f51b1246e546d88addca0106bd/certifi-2025.4.26-py3-none-any.whl", hash = "sha256:30350364dfe371162649852c63336a15c70c6510c2ad5015b21c2345311805f3", size = 159618, upload-time = "2025-04-26T02:12:27.662Z" }, ] [[package]] @@ -681,75 +741,75 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pycparser" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/fc/97/c783634659c2920c3fc70419e3af40972dbaf758daa229a7d6ea6135c90d/cffi-1.17.1.tar.gz", hash = "sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824", size = 516621 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/6b/f4/927e3a8899e52a27fa57a48607ff7dc91a9ebe97399b357b85a0c7892e00/cffi-1.17.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a45e3c6913c5b87b3ff120dcdc03f6131fa0065027d0ed7ee6190736a74cd401", size = 182264 }, - { url = "https://files.pythonhosted.org/packages/6c/f5/6c3a8efe5f503175aaddcbea6ad0d2c96dad6f5abb205750d1b3df44ef29/cffi-1.17.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:30c5e0cb5ae493c04c8b42916e52ca38079f1b235c2f8ae5f4527b963c401caf", size = 178651 }, - { url = "https://files.pythonhosted.org/packages/94/dd/a3f0118e688d1b1a57553da23b16bdade96d2f9bcda4d32e7d2838047ff7/cffi-1.17.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f75c7ab1f9e4aca5414ed4d8e5c0e303a34f4421f8a0d47a4d019ceff0ab6af4", size = 445259 }, - { url = "https://files.pythonhosted.org/packages/2e/ea/70ce63780f096e16ce8588efe039d3c4f91deb1dc01e9c73a287939c79a6/cffi-1.17.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a1ed2dd2972641495a3ec98445e09766f077aee98a1c896dcb4ad0d303628e41", size = 469200 }, - { url = "https://files.pythonhosted.org/packages/1c/a0/a4fa9f4f781bda074c3ddd57a572b060fa0df7655d2a4247bbe277200146/cffi-1.17.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:46bf43160c1a35f7ec506d254e5c890f3c03648a4dbac12d624e4490a7046cd1", size = 477235 }, - { url = "https://files.pythonhosted.org/packages/62/12/ce8710b5b8affbcdd5c6e367217c242524ad17a02fe5beec3ee339f69f85/cffi-1.17.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a24ed04c8ffd54b0729c07cee15a81d964e6fee0e3d4d342a27b020d22959dc6", size = 459721 }, - { url = "https://files.pythonhosted.org/packages/ff/6b/d45873c5e0242196f042d555526f92aa9e0c32355a1be1ff8c27f077fd37/cffi-1.17.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:610faea79c43e44c71e1ec53a554553fa22321b65fae24889706c0a84d4ad86d", size = 467242 }, - { url = "https://files.pythonhosted.org/packages/1a/52/d9a0e523a572fbccf2955f5abe883cfa8bcc570d7faeee06336fbd50c9fc/cffi-1.17.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a9b15d491f3ad5d692e11f6b71f7857e7835eb677955c00cc0aefcd0669adaf6", size = 477999 }, - { url = "https://files.pythonhosted.org/packages/44/74/f2a2460684a1a2d00ca799ad880d54652841a780c4c97b87754f660c7603/cffi-1.17.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:de2ea4b5833625383e464549fec1bc395c1bdeeb5f25c4a3a82b5a8c756ec22f", size = 454242 }, - { url = "https://files.pythonhosted.org/packages/f8/4a/34599cac7dfcd888ff54e801afe06a19c17787dfd94495ab0c8d35fe99fb/cffi-1.17.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:fc48c783f9c87e60831201f2cce7f3b2e4846bf4d8728eabe54d60700b318a0b", size = 478604 }, - { url = "https://files.pythonhosted.org/packages/34/33/e1b8a1ba29025adbdcda5fb3a36f94c03d771c1b7b12f726ff7fef2ebe36/cffi-1.17.1-cp311-cp311-win32.whl", hash = "sha256:85a950a4ac9c359340d5963966e3e0a94a676bd6245a4b55bc43949eee26a655", size = 171727 }, - { url = "https://files.pythonhosted.org/packages/3d/97/50228be003bb2802627d28ec0627837ac0bf35c90cf769812056f235b2d1/cffi-1.17.1-cp311-cp311-win_amd64.whl", hash = "sha256:caaf0640ef5f5517f49bc275eca1406b0ffa6aa184892812030f04c2abf589a0", size = 181400 }, - { url = "https://files.pythonhosted.org/packages/5a/84/e94227139ee5fb4d600a7a4927f322e1d4aea6fdc50bd3fca8493caba23f/cffi-1.17.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:805b4371bf7197c329fcb3ead37e710d1bca9da5d583f5073b799d5c5bd1eee4", size = 183178 }, - { url = "https://files.pythonhosted.org/packages/da/ee/fb72c2b48656111c4ef27f0f91da355e130a923473bf5ee75c5643d00cca/cffi-1.17.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:733e99bc2df47476e3848417c5a4540522f234dfd4ef3ab7fafdf555b082ec0c", size = 178840 }, - { url = "https://files.pythonhosted.org/packages/cc/b6/db007700f67d151abadf508cbfd6a1884f57eab90b1bb985c4c8c02b0f28/cffi-1.17.1-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1257bdabf294dceb59f5e70c64a3e2f462c30c7ad68092d01bbbfb1c16b1ba36", size = 454803 }, - { url = "https://files.pythonhosted.org/packages/1a/df/f8d151540d8c200eb1c6fba8cd0dfd40904f1b0682ea705c36e6c2e97ab3/cffi-1.17.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da95af8214998d77a98cc14e3a3bd00aa191526343078b530ceb0bd710fb48a5", size = 478850 }, - { url = "https://files.pythonhosted.org/packages/28/c0/b31116332a547fd2677ae5b78a2ef662dfc8023d67f41b2a83f7c2aa78b1/cffi-1.17.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d63afe322132c194cf832bfec0dc69a99fb9bb6bbd550f161a49e9e855cc78ff", size = 485729 }, - { url = "https://files.pythonhosted.org/packages/91/2b/9a1ddfa5c7f13cab007a2c9cc295b70fbbda7cb10a286aa6810338e60ea1/cffi-1.17.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f79fc4fc25f1c8698ff97788206bb3c2598949bfe0fef03d299eb1b5356ada99", size = 471256 }, - { url = "https://files.pythonhosted.org/packages/b2/d5/da47df7004cb17e4955df6a43d14b3b4ae77737dff8bf7f8f333196717bf/cffi-1.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b62ce867176a75d03a665bad002af8e6d54644fad99a3c70905c543130e39d93", size = 479424 }, - { url = "https://files.pythonhosted.org/packages/0b/ac/2a28bcf513e93a219c8a4e8e125534f4f6db03e3179ba1c45e949b76212c/cffi-1.17.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:386c8bf53c502fff58903061338ce4f4950cbdcb23e2902d86c0f722b786bbe3", size = 484568 }, - { url = "https://files.pythonhosted.org/packages/d4/38/ca8a4f639065f14ae0f1d9751e70447a261f1a30fa7547a828ae08142465/cffi-1.17.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4ceb10419a9adf4460ea14cfd6bc43d08701f0835e979bf821052f1805850fe8", size = 488736 }, - { url = "https://files.pythonhosted.org/packages/86/c5/28b2d6f799ec0bdecf44dced2ec5ed43e0eb63097b0f58c293583b406582/cffi-1.17.1-cp312-cp312-win32.whl", hash = "sha256:a08d7e755f8ed21095a310a693525137cfe756ce62d066e53f502a83dc550f65", size = 172448 }, - { url = "https://files.pythonhosted.org/packages/50/b9/db34c4755a7bd1cb2d1603ac3863f22bcecbd1ba29e5ee841a4bc510b294/cffi-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:51392eae71afec0d0c8fb1a53b204dbb3bcabcb3c9b807eedf3e1e6ccf2de903", size = 181976 }, +sdist = { url = "https://files.pythonhosted.org/packages/fc/97/c783634659c2920c3fc70419e3af40972dbaf758daa229a7d6ea6135c90d/cffi-1.17.1.tar.gz", hash = "sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824", size = 516621, upload-time = "2024-09-04T20:45:21.852Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6b/f4/927e3a8899e52a27fa57a48607ff7dc91a9ebe97399b357b85a0c7892e00/cffi-1.17.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a45e3c6913c5b87b3ff120dcdc03f6131fa0065027d0ed7ee6190736a74cd401", size = 182264, upload-time = "2024-09-04T20:43:51.124Z" }, + { url = "https://files.pythonhosted.org/packages/6c/f5/6c3a8efe5f503175aaddcbea6ad0d2c96dad6f5abb205750d1b3df44ef29/cffi-1.17.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:30c5e0cb5ae493c04c8b42916e52ca38079f1b235c2f8ae5f4527b963c401caf", size = 178651, upload-time = "2024-09-04T20:43:52.872Z" }, + { url = "https://files.pythonhosted.org/packages/94/dd/a3f0118e688d1b1a57553da23b16bdade96d2f9bcda4d32e7d2838047ff7/cffi-1.17.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f75c7ab1f9e4aca5414ed4d8e5c0e303a34f4421f8a0d47a4d019ceff0ab6af4", size = 445259, upload-time = "2024-09-04T20:43:56.123Z" }, + { url = "https://files.pythonhosted.org/packages/2e/ea/70ce63780f096e16ce8588efe039d3c4f91deb1dc01e9c73a287939c79a6/cffi-1.17.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a1ed2dd2972641495a3ec98445e09766f077aee98a1c896dcb4ad0d303628e41", size = 469200, upload-time = "2024-09-04T20:43:57.891Z" }, + { url = "https://files.pythonhosted.org/packages/1c/a0/a4fa9f4f781bda074c3ddd57a572b060fa0df7655d2a4247bbe277200146/cffi-1.17.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:46bf43160c1a35f7ec506d254e5c890f3c03648a4dbac12d624e4490a7046cd1", size = 477235, upload-time = "2024-09-04T20:44:00.18Z" }, + { url = "https://files.pythonhosted.org/packages/62/12/ce8710b5b8affbcdd5c6e367217c242524ad17a02fe5beec3ee339f69f85/cffi-1.17.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a24ed04c8ffd54b0729c07cee15a81d964e6fee0e3d4d342a27b020d22959dc6", size = 459721, upload-time = "2024-09-04T20:44:01.585Z" }, + { url = "https://files.pythonhosted.org/packages/ff/6b/d45873c5e0242196f042d555526f92aa9e0c32355a1be1ff8c27f077fd37/cffi-1.17.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:610faea79c43e44c71e1ec53a554553fa22321b65fae24889706c0a84d4ad86d", size = 467242, upload-time = "2024-09-04T20:44:03.467Z" }, + { url = "https://files.pythonhosted.org/packages/1a/52/d9a0e523a572fbccf2955f5abe883cfa8bcc570d7faeee06336fbd50c9fc/cffi-1.17.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a9b15d491f3ad5d692e11f6b71f7857e7835eb677955c00cc0aefcd0669adaf6", size = 477999, upload-time = "2024-09-04T20:44:05.023Z" }, + { url = "https://files.pythonhosted.org/packages/44/74/f2a2460684a1a2d00ca799ad880d54652841a780c4c97b87754f660c7603/cffi-1.17.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:de2ea4b5833625383e464549fec1bc395c1bdeeb5f25c4a3a82b5a8c756ec22f", size = 454242, upload-time = "2024-09-04T20:44:06.444Z" }, + { url = "https://files.pythonhosted.org/packages/f8/4a/34599cac7dfcd888ff54e801afe06a19c17787dfd94495ab0c8d35fe99fb/cffi-1.17.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:fc48c783f9c87e60831201f2cce7f3b2e4846bf4d8728eabe54d60700b318a0b", size = 478604, upload-time = "2024-09-04T20:44:08.206Z" }, + { url = "https://files.pythonhosted.org/packages/34/33/e1b8a1ba29025adbdcda5fb3a36f94c03d771c1b7b12f726ff7fef2ebe36/cffi-1.17.1-cp311-cp311-win32.whl", hash = "sha256:85a950a4ac9c359340d5963966e3e0a94a676bd6245a4b55bc43949eee26a655", size = 171727, upload-time = "2024-09-04T20:44:09.481Z" }, + { url = "https://files.pythonhosted.org/packages/3d/97/50228be003bb2802627d28ec0627837ac0bf35c90cf769812056f235b2d1/cffi-1.17.1-cp311-cp311-win_amd64.whl", hash = "sha256:caaf0640ef5f5517f49bc275eca1406b0ffa6aa184892812030f04c2abf589a0", size = 181400, upload-time = "2024-09-04T20:44:10.873Z" }, + { url = "https://files.pythonhosted.org/packages/5a/84/e94227139ee5fb4d600a7a4927f322e1d4aea6fdc50bd3fca8493caba23f/cffi-1.17.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:805b4371bf7197c329fcb3ead37e710d1bca9da5d583f5073b799d5c5bd1eee4", size = 183178, upload-time = "2024-09-04T20:44:12.232Z" }, + { url = "https://files.pythonhosted.org/packages/da/ee/fb72c2b48656111c4ef27f0f91da355e130a923473bf5ee75c5643d00cca/cffi-1.17.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:733e99bc2df47476e3848417c5a4540522f234dfd4ef3ab7fafdf555b082ec0c", size = 178840, upload-time = "2024-09-04T20:44:13.739Z" }, + { url = "https://files.pythonhosted.org/packages/cc/b6/db007700f67d151abadf508cbfd6a1884f57eab90b1bb985c4c8c02b0f28/cffi-1.17.1-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1257bdabf294dceb59f5e70c64a3e2f462c30c7ad68092d01bbbfb1c16b1ba36", size = 454803, upload-time = "2024-09-04T20:44:15.231Z" }, + { url = "https://files.pythonhosted.org/packages/1a/df/f8d151540d8c200eb1c6fba8cd0dfd40904f1b0682ea705c36e6c2e97ab3/cffi-1.17.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da95af8214998d77a98cc14e3a3bd00aa191526343078b530ceb0bd710fb48a5", size = 478850, upload-time = "2024-09-04T20:44:17.188Z" }, + { url = "https://files.pythonhosted.org/packages/28/c0/b31116332a547fd2677ae5b78a2ef662dfc8023d67f41b2a83f7c2aa78b1/cffi-1.17.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d63afe322132c194cf832bfec0dc69a99fb9bb6bbd550f161a49e9e855cc78ff", size = 485729, upload-time = "2024-09-04T20:44:18.688Z" }, + { url = "https://files.pythonhosted.org/packages/91/2b/9a1ddfa5c7f13cab007a2c9cc295b70fbbda7cb10a286aa6810338e60ea1/cffi-1.17.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f79fc4fc25f1c8698ff97788206bb3c2598949bfe0fef03d299eb1b5356ada99", size = 471256, upload-time = "2024-09-04T20:44:20.248Z" }, + { url = "https://files.pythonhosted.org/packages/b2/d5/da47df7004cb17e4955df6a43d14b3b4ae77737dff8bf7f8f333196717bf/cffi-1.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b62ce867176a75d03a665bad002af8e6d54644fad99a3c70905c543130e39d93", size = 479424, upload-time = "2024-09-04T20:44:21.673Z" }, + { url = "https://files.pythonhosted.org/packages/0b/ac/2a28bcf513e93a219c8a4e8e125534f4f6db03e3179ba1c45e949b76212c/cffi-1.17.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:386c8bf53c502fff58903061338ce4f4950cbdcb23e2902d86c0f722b786bbe3", size = 484568, upload-time = "2024-09-04T20:44:23.245Z" }, + { url = "https://files.pythonhosted.org/packages/d4/38/ca8a4f639065f14ae0f1d9751e70447a261f1a30fa7547a828ae08142465/cffi-1.17.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4ceb10419a9adf4460ea14cfd6bc43d08701f0835e979bf821052f1805850fe8", size = 488736, upload-time = "2024-09-04T20:44:24.757Z" }, + { url = "https://files.pythonhosted.org/packages/86/c5/28b2d6f799ec0bdecf44dced2ec5ed43e0eb63097b0f58c293583b406582/cffi-1.17.1-cp312-cp312-win32.whl", hash = "sha256:a08d7e755f8ed21095a310a693525137cfe756ce62d066e53f502a83dc550f65", size = 172448, upload-time = "2024-09-04T20:44:26.208Z" }, + { url = "https://files.pythonhosted.org/packages/50/b9/db34c4755a7bd1cb2d1603ac3863f22bcecbd1ba29e5ee841a4bc510b294/cffi-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:51392eae71afec0d0c8fb1a53b204dbb3bcabcb3c9b807eedf3e1e6ccf2de903", size = 181976, upload-time = "2024-09-04T20:44:27.578Z" }, ] [[package]] name = "chardet" version = "5.1.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/41/32/cdc91dcf83849c7385bf8e2a5693d87376536ed000807fa07f5eab33430d/chardet-5.1.0.tar.gz", hash = "sha256:0d62712b956bc154f85fb0a266e2a3c5913c2967e00348701b32411d6def31e5", size = 2069617 } +sdist = { url = "https://files.pythonhosted.org/packages/41/32/cdc91dcf83849c7385bf8e2a5693d87376536ed000807fa07f5eab33430d/chardet-5.1.0.tar.gz", hash = "sha256:0d62712b956bc154f85fb0a266e2a3c5913c2967e00348701b32411d6def31e5", size = 2069617, upload-time = "2022-12-01T22:34:18.086Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/74/8f/8fc49109009e8d2169d94d72e6b1f4cd45c13d147ba7d6170fb41f22b08f/chardet-5.1.0-py3-none-any.whl", hash = "sha256:362777fb014af596ad31334fde1e8c327dfdb076e1960d1694662d46a6917ab9", size = 199124 }, + { url = "https://files.pythonhosted.org/packages/74/8f/8fc49109009e8d2169d94d72e6b1f4cd45c13d147ba7d6170fb41f22b08f/chardet-5.1.0-py3-none-any.whl", hash = "sha256:362777fb014af596ad31334fde1e8c327dfdb076e1960d1694662d46a6917ab9", size = 199124, upload-time = "2022-12-01T22:34:14.609Z" }, ] [[package]] name = "charset-normalizer" -version = "3.4.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/16/b0/572805e227f01586461c80e0fd25d65a2115599cc9dad142fee4b747c357/charset_normalizer-3.4.1.tar.gz", hash = "sha256:44251f18cd68a75b56585dd00dae26183e102cd5e0f9f1466e6df5da2ed64ea3", size = 123188 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/72/80/41ef5d5a7935d2d3a773e3eaebf0a9350542f2cab4eac59a7a4741fbbbbe/charset_normalizer-3.4.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8bfa33f4f2672964266e940dd22a195989ba31669bd84629f05fab3ef4e2d125", size = 194995 }, - { url = "https://files.pythonhosted.org/packages/7a/28/0b9fefa7b8b080ec492110af6d88aa3dea91c464b17d53474b6e9ba5d2c5/charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:28bf57629c75e810b6ae989f03c0828d64d6b26a5e205535585f96093e405ed1", size = 139471 }, - { url = "https://files.pythonhosted.org/packages/71/64/d24ab1a997efb06402e3fc07317e94da358e2585165930d9d59ad45fcae2/charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f08ff5e948271dc7e18a35641d2f11a4cd8dfd5634f55228b691e62b37125eb3", size = 149831 }, - { url = "https://files.pythonhosted.org/packages/37/ed/be39e5258e198655240db5e19e0b11379163ad7070962d6b0c87ed2c4d39/charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:234ac59ea147c59ee4da87a0c0f098e9c8d169f4dc2a159ef720f1a61bbe27cd", size = 142335 }, - { url = "https://files.pythonhosted.org/packages/88/83/489e9504711fa05d8dde1574996408026bdbdbd938f23be67deebb5eca92/charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd4ec41f914fa74ad1b8304bbc634b3de73d2a0889bd32076342a573e0779e00", size = 143862 }, - { url = "https://files.pythonhosted.org/packages/c6/c7/32da20821cf387b759ad24627a9aca289d2822de929b8a41b6241767b461/charset_normalizer-3.4.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eea6ee1db730b3483adf394ea72f808b6e18cf3cb6454b4d86e04fa8c4327a12", size = 145673 }, - { url = "https://files.pythonhosted.org/packages/68/85/f4288e96039abdd5aeb5c546fa20a37b50da71b5cf01e75e87f16cd43304/charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c96836c97b1238e9c9e3fe90844c947d5afbf4f4c92762679acfe19927d81d77", size = 140211 }, - { url = "https://files.pythonhosted.org/packages/28/a3/a42e70d03cbdabc18997baf4f0227c73591a08041c149e710045c281f97b/charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:4d86f7aff21ee58f26dcf5ae81a9addbd914115cdebcbb2217e4f0ed8982e146", size = 148039 }, - { url = "https://files.pythonhosted.org/packages/85/e4/65699e8ab3014ecbe6f5c71d1a55d810fb716bbfd74f6283d5c2aa87febf/charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:09b5e6733cbd160dcc09589227187e242a30a49ca5cefa5a7edd3f9d19ed53fd", size = 151939 }, - { url = "https://files.pythonhosted.org/packages/b1/82/8e9fe624cc5374193de6860aba3ea8070f584c8565ee77c168ec13274bd2/charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:5777ee0881f9499ed0f71cc82cf873d9a0ca8af166dfa0af8ec4e675b7df48e6", size = 149075 }, - { url = "https://files.pythonhosted.org/packages/3d/7b/82865ba54c765560c8433f65e8acb9217cb839a9e32b42af4aa8e945870f/charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:237bdbe6159cff53b4f24f397d43c6336c6b0b42affbe857970cefbb620911c8", size = 144340 }, - { url = "https://files.pythonhosted.org/packages/b5/b6/9674a4b7d4d99a0d2df9b215da766ee682718f88055751e1e5e753c82db0/charset_normalizer-3.4.1-cp311-cp311-win32.whl", hash = "sha256:8417cb1f36cc0bc7eaba8ccb0e04d55f0ee52df06df3ad55259b9a323555fc8b", size = 95205 }, - { url = "https://files.pythonhosted.org/packages/1e/ab/45b180e175de4402dcf7547e4fb617283bae54ce35c27930a6f35b6bef15/charset_normalizer-3.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:d7f50a1f8c450f3925cb367d011448c39239bb3eb4117c36a6d354794de4ce76", size = 102441 }, - { url = "https://files.pythonhosted.org/packages/0a/9a/dd1e1cdceb841925b7798369a09279bd1cf183cef0f9ddf15a3a6502ee45/charset_normalizer-3.4.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:73d94b58ec7fecbc7366247d3b0b10a21681004153238750bb67bd9012414545", size = 196105 }, - { url = "https://files.pythonhosted.org/packages/d3/8c/90bfabf8c4809ecb648f39794cf2a84ff2e7d2a6cf159fe68d9a26160467/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dad3e487649f498dd991eeb901125411559b22e8d7ab25d3aeb1af367df5efd7", size = 140404 }, - { url = "https://files.pythonhosted.org/packages/ad/8f/e410d57c721945ea3b4f1a04b74f70ce8fa800d393d72899f0a40526401f/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c30197aa96e8eed02200a83fba2657b4c3acd0f0aa4bdc9f6c1af8e8962e0757", size = 150423 }, - { url = "https://files.pythonhosted.org/packages/f0/b8/e6825e25deb691ff98cf5c9072ee0605dc2acfca98af70c2d1b1bc75190d/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2369eea1ee4a7610a860d88f268eb39b95cb588acd7235e02fd5a5601773d4fa", size = 143184 }, - { url = "https://files.pythonhosted.org/packages/3e/a2/513f6cbe752421f16d969e32f3583762bfd583848b763913ddab8d9bfd4f/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc2722592d8998c870fa4e290c2eec2c1569b87fe58618e67d38b4665dfa680d", size = 145268 }, - { url = "https://files.pythonhosted.org/packages/74/94/8a5277664f27c3c438546f3eb53b33f5b19568eb7424736bdc440a88a31f/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffc9202a29ab3920fa812879e95a9e78b2465fd10be7fcbd042899695d75e616", size = 147601 }, - { url = "https://files.pythonhosted.org/packages/7c/5f/6d352c51ee763623a98e31194823518e09bfa48be2a7e8383cf691bbb3d0/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:804a4d582ba6e5b747c625bf1255e6b1507465494a40a2130978bda7b932c90b", size = 141098 }, - { url = "https://files.pythonhosted.org/packages/78/d4/f5704cb629ba5ab16d1d3d741396aec6dc3ca2b67757c45b0599bb010478/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:0f55e69f030f7163dffe9fd0752b32f070566451afe180f99dbeeb81f511ad8d", size = 149520 }, - { url = "https://files.pythonhosted.org/packages/c5/96/64120b1d02b81785f222b976c0fb79a35875457fa9bb40827678e54d1bc8/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c4c3e6da02df6fa1410a7680bd3f63d4f710232d3139089536310d027950696a", size = 152852 }, - { url = "https://files.pythonhosted.org/packages/84/c9/98e3732278a99f47d487fd3468bc60b882920cef29d1fa6ca460a1fdf4e6/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:5df196eb874dae23dcfb968c83d4f8fdccb333330fe1fc278ac5ceeb101003a9", size = 150488 }, - { url = "https://files.pythonhosted.org/packages/13/0e/9c8d4cb99c98c1007cc11eda969ebfe837bbbd0acdb4736d228ccaabcd22/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e358e64305fe12299a08e08978f51fc21fac060dcfcddd95453eabe5b93ed0e1", size = 146192 }, - { url = "https://files.pythonhosted.org/packages/b2/21/2b6b5b860781a0b49427309cb8670785aa543fb2178de875b87b9cc97746/charset_normalizer-3.4.1-cp312-cp312-win32.whl", hash = "sha256:9b23ca7ef998bc739bf6ffc077c2116917eabcc901f88da1b9856b210ef63f35", size = 95550 }, - { url = "https://files.pythonhosted.org/packages/21/5b/1b390b03b1d16c7e382b561c5329f83cc06623916aab983e8ab9239c7d5c/charset_normalizer-3.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:6ff8a4a60c227ad87030d76e99cd1698345d4491638dfa6673027c48b3cd395f", size = 102785 }, - { url = "https://files.pythonhosted.org/packages/0e/f6/65ecc6878a89bb1c23a086ea335ad4bf21a588990c3f535a227b9eea9108/charset_normalizer-3.4.1-py3-none-any.whl", hash = "sha256:d98b1668f06378c6dbefec3b92299716b931cd4e6061f3c875a71ced1780ab85", size = 49767 }, +version = "3.4.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e4/33/89c2ced2b67d1c2a61c19c6751aa8902d46ce3dacb23600a283619f5a12d/charset_normalizer-3.4.2.tar.gz", hash = "sha256:5baececa9ecba31eff645232d59845c07aa030f0c81ee70184a90d35099a0e63", size = 126367, upload-time = "2025-05-02T08:34:42.01Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/05/85/4c40d00dcc6284a1c1ad5de5e0996b06f39d8232f1031cd23c2f5c07ee86/charset_normalizer-3.4.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:be1e352acbe3c78727a16a455126d9ff83ea2dfdcbc83148d2982305a04714c2", size = 198794, upload-time = "2025-05-02T08:32:11.945Z" }, + { url = "https://files.pythonhosted.org/packages/41/d9/7a6c0b9db952598e97e93cbdfcb91bacd89b9b88c7c983250a77c008703c/charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aa88ca0b1932e93f2d961bf3addbb2db902198dca337d88c89e1559e066e7645", size = 142846, upload-time = "2025-05-02T08:32:13.946Z" }, + { url = "https://files.pythonhosted.org/packages/66/82/a37989cda2ace7e37f36c1a8ed16c58cf48965a79c2142713244bf945c89/charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d524ba3f1581b35c03cb42beebab4a13e6cdad7b36246bd22541fa585a56cccd", size = 153350, upload-time = "2025-05-02T08:32:15.873Z" }, + { url = "https://files.pythonhosted.org/packages/df/68/a576b31b694d07b53807269d05ec3f6f1093e9545e8607121995ba7a8313/charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28a1005facc94196e1fb3e82a3d442a9d9110b8434fc1ded7a24a2983c9888d8", size = 145657, upload-time = "2025-05-02T08:32:17.283Z" }, + { url = "https://files.pythonhosted.org/packages/92/9b/ad67f03d74554bed3aefd56fe836e1623a50780f7c998d00ca128924a499/charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fdb20a30fe1175ecabed17cbf7812f7b804b8a315a25f24678bcdf120a90077f", size = 147260, upload-time = "2025-05-02T08:32:18.807Z" }, + { url = "https://files.pythonhosted.org/packages/a6/e6/8aebae25e328160b20e31a7e9929b1578bbdc7f42e66f46595a432f8539e/charset_normalizer-3.4.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0f5d9ed7f254402c9e7d35d2f5972c9bbea9040e99cd2861bd77dc68263277c7", size = 149164, upload-time = "2025-05-02T08:32:20.333Z" }, + { url = "https://files.pythonhosted.org/packages/8b/f2/b3c2f07dbcc248805f10e67a0262c93308cfa149a4cd3d1fe01f593e5fd2/charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:efd387a49825780ff861998cd959767800d54f8308936b21025326de4b5a42b9", size = 144571, upload-time = "2025-05-02T08:32:21.86Z" }, + { url = "https://files.pythonhosted.org/packages/60/5b/c3f3a94bc345bc211622ea59b4bed9ae63c00920e2e8f11824aa5708e8b7/charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:f0aa37f3c979cf2546b73e8222bbfa3dc07a641585340179d768068e3455e544", size = 151952, upload-time = "2025-05-02T08:32:23.434Z" }, + { url = "https://files.pythonhosted.org/packages/e2/4d/ff460c8b474122334c2fa394a3f99a04cf11c646da895f81402ae54f5c42/charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:e70e990b2137b29dc5564715de1e12701815dacc1d056308e2b17e9095372a82", size = 155959, upload-time = "2025-05-02T08:32:24.993Z" }, + { url = "https://files.pythonhosted.org/packages/a2/2b/b964c6a2fda88611a1fe3d4c400d39c66a42d6c169c924818c848f922415/charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:0c8c57f84ccfc871a48a47321cfa49ae1df56cd1d965a09abe84066f6853b9c0", size = 153030, upload-time = "2025-05-02T08:32:26.435Z" }, + { url = "https://files.pythonhosted.org/packages/59/2e/d3b9811db26a5ebf444bc0fa4f4be5aa6d76fc6e1c0fd537b16c14e849b6/charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:6b66f92b17849b85cad91259efc341dce9c1af48e2173bf38a85c6329f1033e5", size = 148015, upload-time = "2025-05-02T08:32:28.376Z" }, + { url = "https://files.pythonhosted.org/packages/90/07/c5fd7c11eafd561bb51220d600a788f1c8d77c5eef37ee49454cc5c35575/charset_normalizer-3.4.2-cp311-cp311-win32.whl", hash = "sha256:daac4765328a919a805fa5e2720f3e94767abd632ae410a9062dff5412bae65a", size = 98106, upload-time = "2025-05-02T08:32:30.281Z" }, + { url = "https://files.pythonhosted.org/packages/a8/05/5e33dbef7e2f773d672b6d79f10ec633d4a71cd96db6673625838a4fd532/charset_normalizer-3.4.2-cp311-cp311-win_amd64.whl", hash = "sha256:e53efc7c7cee4c1e70661e2e112ca46a575f90ed9ae3fef200f2a25e954f4b28", size = 105402, upload-time = "2025-05-02T08:32:32.191Z" }, + { url = "https://files.pythonhosted.org/packages/d7/a4/37f4d6035c89cac7930395a35cc0f1b872e652eaafb76a6075943754f095/charset_normalizer-3.4.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0c29de6a1a95f24b9a1aa7aefd27d2487263f00dfd55a77719b530788f75cff7", size = 199936, upload-time = "2025-05-02T08:32:33.712Z" }, + { url = "https://files.pythonhosted.org/packages/ee/8a/1a5e33b73e0d9287274f899d967907cd0bf9c343e651755d9307e0dbf2b3/charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cddf7bd982eaa998934a91f69d182aec997c6c468898efe6679af88283b498d3", size = 143790, upload-time = "2025-05-02T08:32:35.768Z" }, + { url = "https://files.pythonhosted.org/packages/66/52/59521f1d8e6ab1482164fa21409c5ef44da3e9f653c13ba71becdd98dec3/charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fcbe676a55d7445b22c10967bceaaf0ee69407fbe0ece4d032b6eb8d4565982a", size = 153924, upload-time = "2025-05-02T08:32:37.284Z" }, + { url = "https://files.pythonhosted.org/packages/86/2d/fb55fdf41964ec782febbf33cb64be480a6b8f16ded2dbe8db27a405c09f/charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d41c4d287cfc69060fa91cae9683eacffad989f1a10811995fa309df656ec214", size = 146626, upload-time = "2025-05-02T08:32:38.803Z" }, + { url = "https://files.pythonhosted.org/packages/8c/73/6ede2ec59bce19b3edf4209d70004253ec5f4e319f9a2e3f2f15601ed5f7/charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4e594135de17ab3866138f496755f302b72157d115086d100c3f19370839dd3a", size = 148567, upload-time = "2025-05-02T08:32:40.251Z" }, + { url = "https://files.pythonhosted.org/packages/09/14/957d03c6dc343c04904530b6bef4e5efae5ec7d7990a7cbb868e4595ee30/charset_normalizer-3.4.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cf713fe9a71ef6fd5adf7a79670135081cd4431c2943864757f0fa3a65b1fafd", size = 150957, upload-time = "2025-05-02T08:32:41.705Z" }, + { url = "https://files.pythonhosted.org/packages/0d/c8/8174d0e5c10ccebdcb1b53cc959591c4c722a3ad92461a273e86b9f5a302/charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a370b3e078e418187da8c3674eddb9d983ec09445c99a3a263c2011993522981", size = 145408, upload-time = "2025-05-02T08:32:43.709Z" }, + { url = "https://files.pythonhosted.org/packages/58/aa/8904b84bc8084ac19dc52feb4f5952c6df03ffb460a887b42615ee1382e8/charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a955b438e62efdf7e0b7b52a64dc5c3396e2634baa62471768a64bc2adb73d5c", size = 153399, upload-time = "2025-05-02T08:32:46.197Z" }, + { url = "https://files.pythonhosted.org/packages/c2/26/89ee1f0e264d201cb65cf054aca6038c03b1a0c6b4ae998070392a3ce605/charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:7222ffd5e4de8e57e03ce2cef95a4c43c98fcb72ad86909abdfc2c17d227fc1b", size = 156815, upload-time = "2025-05-02T08:32:48.105Z" }, + { url = "https://files.pythonhosted.org/packages/fd/07/68e95b4b345bad3dbbd3a8681737b4338ff2c9df29856a6d6d23ac4c73cb/charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:bee093bf902e1d8fc0ac143c88902c3dfc8941f7ea1d6a8dd2bcb786d33db03d", size = 154537, upload-time = "2025-05-02T08:32:49.719Z" }, + { url = "https://files.pythonhosted.org/packages/77/1a/5eefc0ce04affb98af07bc05f3bac9094513c0e23b0562d64af46a06aae4/charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:dedb8adb91d11846ee08bec4c8236c8549ac721c245678282dcb06b221aab59f", size = 149565, upload-time = "2025-05-02T08:32:51.404Z" }, + { url = "https://files.pythonhosted.org/packages/37/a0/2410e5e6032a174c95e0806b1a6585eb21e12f445ebe239fac441995226a/charset_normalizer-3.4.2-cp312-cp312-win32.whl", hash = "sha256:db4c7bf0e07fc3b7d89ac2a5880a6a8062056801b83ff56d8464b70f65482b6c", size = 98357, upload-time = "2025-05-02T08:32:53.079Z" }, + { url = "https://files.pythonhosted.org/packages/6c/4f/c02d5c493967af3eda9c771ad4d2bbc8df6f99ddbeb37ceea6e8716a32bc/charset_normalizer-3.4.2-cp312-cp312-win_amd64.whl", hash = "sha256:5a9979887252a82fefd3d3ed2a8e3b937a7a809f65dcb1e068b090e165bbe99e", size = 105776, upload-time = "2025-05-02T08:32:54.573Z" }, + { url = "https://files.pythonhosted.org/packages/20/94/c5790835a017658cbfabd07f3bfb549140c3ac458cfc196323996b10095a/charset_normalizer-3.4.2-py3-none-any.whl", hash = "sha256:7f56930ab0abd1c45cd15be65cc741c28b1c9a34876ce8c17a2fa107810c0af0", size = 52626, upload-time = "2025-05-02T08:34:40.053Z" }, ] [[package]] @@ -759,17 +819,17 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "numpy" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/73/09/10d57569e399ce9cbc5eee2134996581c957f63a9addfa6ca657daf006b8/chroma_hnswlib-0.7.6.tar.gz", hash = "sha256:4dce282543039681160259d29fcde6151cc9106c6461e0485f57cdccd83059b7", size = 32256 } +sdist = { url = "https://files.pythonhosted.org/packages/73/09/10d57569e399ce9cbc5eee2134996581c957f63a9addfa6ca657daf006b8/chroma_hnswlib-0.7.6.tar.gz", hash = "sha256:4dce282543039681160259d29fcde6151cc9106c6461e0485f57cdccd83059b7", size = 32256, upload-time = "2024-07-22T20:19:29.259Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f5/af/d15fdfed2a204c0f9467ad35084fbac894c755820b203e62f5dcba2d41f1/chroma_hnswlib-0.7.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:81181d54a2b1e4727369486a631f977ffc53c5533d26e3d366dda243fb0998ca", size = 196911 }, - { url = "https://files.pythonhosted.org/packages/0d/19/aa6f2139f1ff7ad23a690ebf2a511b2594ab359915d7979f76f3213e46c4/chroma_hnswlib-0.7.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4b4ab4e11f1083dd0a11ee4f0e0b183ca9f0f2ed63ededba1935b13ce2b3606f", size = 185000 }, - { url = "https://files.pythonhosted.org/packages/79/b1/1b269c750e985ec7d40b9bbe7d66d0a890e420525187786718e7f6b07913/chroma_hnswlib-0.7.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:53db45cd9173d95b4b0bdccb4dbff4c54a42b51420599c32267f3abbeb795170", size = 2377289 }, - { url = "https://files.pythonhosted.org/packages/c7/2d/d5663e134436e5933bc63516a20b5edc08b4c1b1588b9680908a5f1afd04/chroma_hnswlib-0.7.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5c093f07a010b499c00a15bc9376036ee4800d335360570b14f7fe92badcdcf9", size = 2411755 }, - { url = "https://files.pythonhosted.org/packages/3e/79/1bce519cf186112d6d5ce2985392a89528c6e1e9332d680bf752694a4cdf/chroma_hnswlib-0.7.6-cp311-cp311-win_amd64.whl", hash = "sha256:0540b0ac96e47d0aa39e88ea4714358ae05d64bbe6bf33c52f316c664190a6a3", size = 151888 }, - { url = "https://files.pythonhosted.org/packages/93/ac/782b8d72de1c57b64fdf5cb94711540db99a92768d93d973174c62d45eb8/chroma_hnswlib-0.7.6-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:e87e9b616c281bfbe748d01705817c71211613c3b063021f7ed5e47173556cb7", size = 197804 }, - { url = "https://files.pythonhosted.org/packages/32/4e/fd9ce0764228e9a98f6ff46af05e92804090b5557035968c5b4198bc7af9/chroma_hnswlib-0.7.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ec5ca25bc7b66d2ecbf14502b5729cde25f70945d22f2aaf523c2d747ea68912", size = 185421 }, - { url = "https://files.pythonhosted.org/packages/d9/3d/b59a8dedebd82545d873235ef2d06f95be244dfece7ee4a1a6044f080b18/chroma_hnswlib-0.7.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:305ae491de9d5f3c51e8bd52d84fdf2545a4a2bc7af49765cda286b7bb30b1d4", size = 2389672 }, - { url = "https://files.pythonhosted.org/packages/74/1e/80a033ea4466338824974a34f418e7b034a7748bf906f56466f5caa434b0/chroma_hnswlib-0.7.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:822ede968d25a2c88823ca078a58f92c9b5c4142e38c7c8b4c48178894a0a3c5", size = 2436986 }, + { url = "https://files.pythonhosted.org/packages/f5/af/d15fdfed2a204c0f9467ad35084fbac894c755820b203e62f5dcba2d41f1/chroma_hnswlib-0.7.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:81181d54a2b1e4727369486a631f977ffc53c5533d26e3d366dda243fb0998ca", size = 196911, upload-time = "2024-07-22T20:18:33.46Z" }, + { url = "https://files.pythonhosted.org/packages/0d/19/aa6f2139f1ff7ad23a690ebf2a511b2594ab359915d7979f76f3213e46c4/chroma_hnswlib-0.7.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4b4ab4e11f1083dd0a11ee4f0e0b183ca9f0f2ed63ededba1935b13ce2b3606f", size = 185000, upload-time = "2024-07-22T20:18:36.16Z" }, + { url = "https://files.pythonhosted.org/packages/79/b1/1b269c750e985ec7d40b9bbe7d66d0a890e420525187786718e7f6b07913/chroma_hnswlib-0.7.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:53db45cd9173d95b4b0bdccb4dbff4c54a42b51420599c32267f3abbeb795170", size = 2377289, upload-time = "2024-07-22T20:18:37.761Z" }, + { url = "https://files.pythonhosted.org/packages/c7/2d/d5663e134436e5933bc63516a20b5edc08b4c1b1588b9680908a5f1afd04/chroma_hnswlib-0.7.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5c093f07a010b499c00a15bc9376036ee4800d335360570b14f7fe92badcdcf9", size = 2411755, upload-time = "2024-07-22T20:18:39.949Z" }, + { url = "https://files.pythonhosted.org/packages/3e/79/1bce519cf186112d6d5ce2985392a89528c6e1e9332d680bf752694a4cdf/chroma_hnswlib-0.7.6-cp311-cp311-win_amd64.whl", hash = "sha256:0540b0ac96e47d0aa39e88ea4714358ae05d64bbe6bf33c52f316c664190a6a3", size = 151888, upload-time = "2024-07-22T20:18:45.003Z" }, + { url = "https://files.pythonhosted.org/packages/93/ac/782b8d72de1c57b64fdf5cb94711540db99a92768d93d973174c62d45eb8/chroma_hnswlib-0.7.6-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:e87e9b616c281bfbe748d01705817c71211613c3b063021f7ed5e47173556cb7", size = 197804, upload-time = "2024-07-22T20:18:46.442Z" }, + { url = "https://files.pythonhosted.org/packages/32/4e/fd9ce0764228e9a98f6ff46af05e92804090b5557035968c5b4198bc7af9/chroma_hnswlib-0.7.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ec5ca25bc7b66d2ecbf14502b5729cde25f70945d22f2aaf523c2d747ea68912", size = 185421, upload-time = "2024-07-22T20:18:47.72Z" }, + { url = "https://files.pythonhosted.org/packages/d9/3d/b59a8dedebd82545d873235ef2d06f95be244dfece7ee4a1a6044f080b18/chroma_hnswlib-0.7.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:305ae491de9d5f3c51e8bd52d84fdf2545a4a2bc7af49765cda286b7bb30b1d4", size = 2389672, upload-time = "2024-07-22T20:18:49.583Z" }, + { url = "https://files.pythonhosted.org/packages/74/1e/80a033ea4466338824974a34f418e7b034a7748bf906f56466f5caa434b0/chroma_hnswlib-0.7.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:822ede968d25a2c88823ca078a58f92c9b5c4142e38c7c8b4c48178894a0a3c5", size = 2436986, upload-time = "2024-07-22T20:18:51.872Z" }, ] [[package]] @@ -806,30 +866,21 @@ dependencies = [ { name = "typing-extensions" }, { name = "uvicorn", extra = ["standard"] }, ] -sdist = { url = "https://files.pythonhosted.org/packages/03/31/6c8e05405bb02b4a1f71f9aa3eef242415565dabf6afc1bde7f64f726963/chromadb-0.5.20.tar.gz", hash = "sha256:19513a23b2d20059866216bfd80195d1d4a160ffba234b8899f5e80978160ca7", size = 33664540 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/5f/7a/10bf5dc92d13cc03230190fcc5016a0b138d99e5b36b8b89ee0fe1680e10/chromadb-0.5.20-py3-none-any.whl", hash = "sha256:9550ba1b6dce911e35cac2568b301badf4b42f457b99a432bdeec2b6b9dd3680", size = 617884 }, -] - -[[package]] -name = "circuitbreaker" -version = "2.1.3" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/df/ac/de7a92c4ed39cba31fe5ad9203b76a25ca67c530797f6bb420fff5f65ccb/circuitbreaker-2.1.3.tar.gz", hash = "sha256:1a4baee510f7bea3c91b194dcce7c07805fe96c4423ed5594b75af438531d084", size = 10787 } +sdist = { url = "https://files.pythonhosted.org/packages/03/31/6c8e05405bb02b4a1f71f9aa3eef242415565dabf6afc1bde7f64f726963/chromadb-0.5.20.tar.gz", hash = "sha256:19513a23b2d20059866216bfd80195d1d4a160ffba234b8899f5e80978160ca7", size = 33664540, upload-time = "2024-11-19T05:13:58.678Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ae/34/15f08edd4628f65217de1fc3c1a27c82e46fe357d60c217fc9881e12ebcc/circuitbreaker-2.1.3-py3-none-any.whl", hash = "sha256:87ba6a3ed03fdc7032bc175561c2b04d52ade9d5faf94ca2b035fbdc5e6b1dd1", size = 7737 }, + { url = "https://files.pythonhosted.org/packages/5f/7a/10bf5dc92d13cc03230190fcc5016a0b138d99e5b36b8b89ee0fe1680e10/chromadb-0.5.20-py3-none-any.whl", hash = "sha256:9550ba1b6dce911e35cac2568b301badf4b42f457b99a432bdeec2b6b9dd3680", size = 617884, upload-time = "2024-11-19T05:13:56.29Z" }, ] [[package]] name = "click" -version = "8.1.8" +version = "8.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/b9/2e/0090cbf739cee7d23781ad4b89a9894a41538e4fcf4c31dcdd705b78eb8b/click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a", size = 226593 } +sdist = { url = "https://files.pythonhosted.org/packages/60/6c/8ca2efa64cf75a977a0d7fac081354553ebe483345c734fb6b6515d96bbc/click-8.2.1.tar.gz", hash = "sha256:27c491cc05d968d271d5a1db13e3b5a184636d9d930f148c50b038f0d0646202", size = 286342, upload-time = "2025-05-20T23:19:49.832Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/7e/d4/7ebdbd03970677812aac39c869717059dbb71a4cfc033ca6e5221787892c/click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2", size = 98188 }, + { url = "https://files.pythonhosted.org/packages/85/32/10bb5764d90a8eee674e9dc6f4db6a0ab47c8c4d0d83c27f7c39ac415a4d/click-8.2.1-py3-none-any.whl", hash = "sha256:61a3265b914e850b85317d0b3109c7f8cd35a670f963866005d6ef1d5175a12b", size = 102215, upload-time = "2025-05-20T23:19:47.796Z" }, ] [[package]] @@ -839,9 +890,9 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "click" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/1d/ce/edb087fb53de63dad3b36408ca30368f438738098e668b78c87f93cd41df/click_default_group-1.2.4.tar.gz", hash = "sha256:eb3f3c99ec0d456ca6cd2a7f08f7d4e91771bef51b01bdd9580cc6450fe1251e", size = 3505 } +sdist = { url = "https://files.pythonhosted.org/packages/1d/ce/edb087fb53de63dad3b36408ca30368f438738098e668b78c87f93cd41df/click_default_group-1.2.4.tar.gz", hash = "sha256:eb3f3c99ec0d456ca6cd2a7f08f7d4e91771bef51b01bdd9580cc6450fe1251e", size = 3505, upload-time = "2023-08-04T07:54:58.425Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/2c/1a/aff8bb287a4b1400f69e09a53bd65de96aa5cee5691925b38731c67fc695/click_default_group-1.2.4-py2.py3-none-any.whl", hash = "sha256:9b60486923720e7fc61731bdb32b617039aba820e22e1c88766b1125592eaa5f", size = 4123 }, + { url = "https://files.pythonhosted.org/packages/2c/1a/aff8bb287a4b1400f69e09a53bd65de96aa5cee5691925b38731c67fc695/click_default_group-1.2.4-py2.py3-none-any.whl", hash = "sha256:9b60486923720e7fc61731bdb32b617039aba820e22e1c88766b1125592eaa5f", size = 4123, upload-time = "2023-08-04T07:54:56.875Z" }, ] [[package]] @@ -851,9 +902,9 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "click" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/30/ce/217289b77c590ea1e7c24242d9ddd6e249e52c795ff10fac2c50062c48cb/click_didyoumean-0.3.1.tar.gz", hash = "sha256:4f82fdff0dbe64ef8ab2279bd6aa3f6a99c3b28c05aa09cbfc07c9d7fbb5a463", size = 3089 } +sdist = { url = "https://files.pythonhosted.org/packages/30/ce/217289b77c590ea1e7c24242d9ddd6e249e52c795ff10fac2c50062c48cb/click_didyoumean-0.3.1.tar.gz", hash = "sha256:4f82fdff0dbe64ef8ab2279bd6aa3f6a99c3b28c05aa09cbfc07c9d7fbb5a463", size = 3089, upload-time = "2024-03-24T08:22:07.499Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/1b/5b/974430b5ffdb7a4f1941d13d83c64a0395114503cc357c6b9ae4ce5047ed/click_didyoumean-0.3.1-py3-none-any.whl", hash = "sha256:5c4bb6007cfea5f2fd6583a2fb6701a22a41eb98957e63d0fac41c10e7c3117c", size = 3631 }, + { url = "https://files.pythonhosted.org/packages/1b/5b/974430b5ffdb7a4f1941d13d83c64a0395114503cc357c6b9ae4ce5047ed/click_didyoumean-0.3.1-py3-none-any.whl", hash = "sha256:5c4bb6007cfea5f2fd6583a2fb6701a22a41eb98957e63d0fac41c10e7c3117c", size = 3631, upload-time = "2024-03-24T08:22:06.356Z" }, ] [[package]] @@ -863,9 +914,9 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "click" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/5f/1d/45434f64ed749540af821fd7e42b8e4d23ac04b1eda7c26613288d6cd8a8/click-plugins-1.1.1.tar.gz", hash = "sha256:46ab999744a9d831159c3411bb0c79346d94a444df9a3a3742e9ed63645f264b", size = 8164 } +sdist = { url = "https://files.pythonhosted.org/packages/5f/1d/45434f64ed749540af821fd7e42b8e4d23ac04b1eda7c26613288d6cd8a8/click-plugins-1.1.1.tar.gz", hash = "sha256:46ab999744a9d831159c3411bb0c79346d94a444df9a3a3742e9ed63645f264b", size = 8164, upload-time = "2019-04-04T04:27:04.82Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e9/da/824b92d9942f4e472702488857914bdd50f73021efea15b4cad9aca8ecef/click_plugins-1.1.1-py2.py3-none-any.whl", hash = "sha256:5d262006d3222f5057fd81e1623d4443e41dcda5dc815c06b442aa3c02889fc8", size = 7497 }, + { url = "https://files.pythonhosted.org/packages/e9/da/824b92d9942f4e472702488857914bdd50f73021efea15b4cad9aca8ecef/click_plugins-1.1.1-py2.py3-none-any.whl", hash = "sha256:5d262006d3222f5057fd81e1623d4443e41dcda5dc815c06b442aa3c02889fc8", size = 7497, upload-time = "2019-04-04T04:27:03.36Z" }, ] [[package]] @@ -876,9 +927,9 @@ dependencies = [ { name = "click" }, { name = "prompt-toolkit" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/cb/a2/57f4ac79838cfae6912f997b4d1a64a858fb0c86d7fcaae6f7b58d267fca/click-repl-0.3.0.tar.gz", hash = "sha256:17849c23dba3d667247dc4defe1757fff98694e90fe37474f3feebb69ced26a9", size = 10449 } +sdist = { url = "https://files.pythonhosted.org/packages/cb/a2/57f4ac79838cfae6912f997b4d1a64a858fb0c86d7fcaae6f7b58d267fca/click-repl-0.3.0.tar.gz", hash = "sha256:17849c23dba3d667247dc4defe1757fff98694e90fe37474f3feebb69ced26a9", size = 10449, upload-time = "2023-06-15T12:43:51.141Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/52/40/9d857001228658f0d59e97ebd4c346fe73e138c6de1bce61dc568a57c7f8/click_repl-0.3.0-py3-none-any.whl", hash = "sha256:fb7e06deb8da8de86180a33a9da97ac316751c094c6899382da7feeeeb51b812", size = 10289 }, + { url = "https://files.pythonhosted.org/packages/52/40/9d857001228658f0d59e97ebd4c346fe73e138c6de1bce61dc568a57c7f8/click_repl-0.3.0-py3-none-any.whl", hash = "sha256:fb7e06deb8da8de86180a33a9da97ac316751c094c6899382da7feeeeb51b812", size = 10289, upload-time = "2023-06-15T12:43:48.626Z" }, ] [[package]] @@ -892,28 +943,28 @@ dependencies = [ { name = "urllib3" }, { name = "zstandard" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/f4/8e/bf6012f7b45dbb74e19ad5c881a7bbcd1e7dd2b990f12cc434294d917800/clickhouse-connect-0.7.19.tar.gz", hash = "sha256:ce8f21f035781c5ef6ff57dc162e8150779c009b59f14030ba61f8c9c10c06d0", size = 84918 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/68/6f/a78cad40dc0f1fee19094c40abd7d23ff04bb491732c3a65b3661d426c89/clickhouse_connect-0.7.19-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ee47af8926a7ec3a970e0ebf29a82cbbe3b1b7eae43336a81b3a0ca18091de5f", size = 253530 }, - { url = "https://files.pythonhosted.org/packages/40/82/419d110149900ace5eb0787c668d11e1657ac0eabb65c1404f039746f4ed/clickhouse_connect-0.7.19-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ce429233b2d21a8a149c8cd836a2555393cbcf23d61233520db332942ffb8964", size = 245691 }, - { url = "https://files.pythonhosted.org/packages/e3/9c/ad6708ced6cf9418334d2bf19bbba3c223511ed852eb85f79b1e7c20cdbd/clickhouse_connect-0.7.19-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:617c04f5c46eed3344a7861cd96fb05293e70d3b40d21541b1e459e7574efa96", size = 1055273 }, - { url = "https://files.pythonhosted.org/packages/ea/99/88c24542d6218100793cfb13af54d7ad4143d6515b0b3d621ba3b5a2d8af/clickhouse_connect-0.7.19-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f08e33b8cc2dc1873edc5ee4088d4fc3c0dbb69b00e057547bcdc7e9680b43e5", size = 1067030 }, - { url = "https://files.pythonhosted.org/packages/c8/84/19eb776b4e760317c21214c811f04f612cba7eee0f2818a7d6806898a994/clickhouse_connect-0.7.19-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:921886b887f762e5cc3eef57ef784d419a3f66df85fd86fa2e7fbbf464c4c54a", size = 1027207 }, - { url = "https://files.pythonhosted.org/packages/22/81/c2982a33b088b6c9af5d0bdc46413adc5fedceae063b1f8b56570bb28887/clickhouse_connect-0.7.19-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:6ad0cf8552a9e985cfa6524b674ae7c8f5ba51df5bd3ecddbd86c82cdbef41a7", size = 1054850 }, - { url = "https://files.pythonhosted.org/packages/7b/a4/4a84ed3e92323d12700011cc8c4039f00a8c888079d65e75a4d4758ba288/clickhouse_connect-0.7.19-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:70f838ef0861cdf0e2e198171a1f3fd2ee05cf58e93495eeb9b17dfafb278186", size = 1022784 }, - { url = "https://files.pythonhosted.org/packages/5e/67/3f5cc6f78c9adbbd6a3183a3f9f3196a116be19e958d7eaa6e307b391fed/clickhouse_connect-0.7.19-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c5f0d207cb0dcc1adb28ced63f872d080924b7562b263a9d54d4693b670eb066", size = 1071084 }, - { url = "https://files.pythonhosted.org/packages/01/8d/a294e1cc752e22bc6ee08aa421ea31ed9559b09d46d35499449140a5c374/clickhouse_connect-0.7.19-cp311-cp311-win32.whl", hash = "sha256:8c96c4c242b98fcf8005e678a26dbd4361748721b6fa158c1fe84ad15c7edbbe", size = 221156 }, - { url = "https://files.pythonhosted.org/packages/68/69/09b3a4e53f5d3d770e9fa70f6f04642cdb37cc76d37279c55fd4e868f845/clickhouse_connect-0.7.19-cp311-cp311-win_amd64.whl", hash = "sha256:bda092bab224875ed7c7683707d63f8a2322df654c4716e6611893a18d83e908", size = 238826 }, - { url = "https://files.pythonhosted.org/packages/af/f8/1d48719728bac33c1a9815e0a7230940e078fd985b09af2371715de78a3c/clickhouse_connect-0.7.19-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:8f170d08166438d29f0dcfc8a91b672c783dc751945559e65eefff55096f9274", size = 256687 }, - { url = "https://files.pythonhosted.org/packages/ed/0d/3cbbbd204be045c4727f9007679ad97d3d1d559b43ba844373a79af54d16/clickhouse_connect-0.7.19-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:26b80cb8f66bde9149a9a2180e2cc4895c1b7d34f9dceba81630a9b9a9ae66b2", size = 247631 }, - { url = "https://files.pythonhosted.org/packages/b6/44/adb55285226d60e9c46331a9980c88dad8c8de12abb895c4e3149a088092/clickhouse_connect-0.7.19-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9ba80e3598acf916c4d1b2515671f65d9efee612a783c17c56a5a646f4db59b9", size = 1053767 }, - { url = "https://files.pythonhosted.org/packages/6c/f3/a109c26a41153768be57374cb823cac5daf74c9098a5c61081ffabeb4e59/clickhouse_connect-0.7.19-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0d38c30bd847af0ce7ff738152478f913854db356af4d5824096394d0eab873d", size = 1072014 }, - { url = "https://files.pythonhosted.org/packages/51/80/9c200e5e392a538f2444c9a6a93e1cf0e36588c7e8720882ac001e23b246/clickhouse_connect-0.7.19-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d41d4b159071c0e4f607563932d4fa5c2a8fc27d3ba1200d0929b361e5191864", size = 1027423 }, - { url = "https://files.pythonhosted.org/packages/33/a3/219fcd1572f1ce198dcef86da8c6c526b04f56e8b7a82e21119677f89379/clickhouse_connect-0.7.19-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:3682c2426f5dbda574611210e3c7c951b9557293a49eb60a7438552435873889", size = 1053683 }, - { url = "https://files.pythonhosted.org/packages/5d/df/687d90fbc0fd8ce586c46400f3791deac120e4c080aa8b343c0f676dfb08/clickhouse_connect-0.7.19-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:6d492064dca278eb61be3a2d70a5f082e2ebc8ceebd4f33752ae234116192020", size = 1021120 }, - { url = "https://files.pythonhosted.org/packages/c8/3b/39ba71b103275df8ec90d424dbaca2dba82b28398c3d2aeac5a0141b6aae/clickhouse_connect-0.7.19-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:62612da163b934c1ff35df6155a47cf17ac0e2d2f9f0f8f913641e5c02cdf39f", size = 1073652 }, - { url = "https://files.pythonhosted.org/packages/b3/92/06df8790a7d93d5d5f1098604fc7d79682784818030091966a3ce3f766a8/clickhouse_connect-0.7.19-cp312-cp312-win32.whl", hash = "sha256:196e48c977affc045794ec7281b4d711e169def00535ecab5f9fdeb8c177f149", size = 221589 }, - { url = "https://files.pythonhosted.org/packages/42/1f/935d0810b73184a1d306f92458cb0a2e9b0de2377f536da874e063b8e422/clickhouse_connect-0.7.19-cp312-cp312-win_amd64.whl", hash = "sha256:b771ca6a473d65103dcae82810d3a62475c5372fc38d8f211513c72b954fb020", size = 239584 }, +sdist = { url = "https://files.pythonhosted.org/packages/f4/8e/bf6012f7b45dbb74e19ad5c881a7bbcd1e7dd2b990f12cc434294d917800/clickhouse-connect-0.7.19.tar.gz", hash = "sha256:ce8f21f035781c5ef6ff57dc162e8150779c009b59f14030ba61f8c9c10c06d0", size = 84918, upload-time = "2024-08-21T21:37:16.639Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/68/6f/a78cad40dc0f1fee19094c40abd7d23ff04bb491732c3a65b3661d426c89/clickhouse_connect-0.7.19-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ee47af8926a7ec3a970e0ebf29a82cbbe3b1b7eae43336a81b3a0ca18091de5f", size = 253530, upload-time = "2024-08-21T21:35:53.372Z" }, + { url = "https://files.pythonhosted.org/packages/40/82/419d110149900ace5eb0787c668d11e1657ac0eabb65c1404f039746f4ed/clickhouse_connect-0.7.19-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ce429233b2d21a8a149c8cd836a2555393cbcf23d61233520db332942ffb8964", size = 245691, upload-time = "2024-08-21T21:35:55.074Z" }, + { url = "https://files.pythonhosted.org/packages/e3/9c/ad6708ced6cf9418334d2bf19bbba3c223511ed852eb85f79b1e7c20cdbd/clickhouse_connect-0.7.19-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:617c04f5c46eed3344a7861cd96fb05293e70d3b40d21541b1e459e7574efa96", size = 1055273, upload-time = "2024-08-21T21:35:56.478Z" }, + { url = "https://files.pythonhosted.org/packages/ea/99/88c24542d6218100793cfb13af54d7ad4143d6515b0b3d621ba3b5a2d8af/clickhouse_connect-0.7.19-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f08e33b8cc2dc1873edc5ee4088d4fc3c0dbb69b00e057547bcdc7e9680b43e5", size = 1067030, upload-time = "2024-08-21T21:35:58.096Z" }, + { url = "https://files.pythonhosted.org/packages/c8/84/19eb776b4e760317c21214c811f04f612cba7eee0f2818a7d6806898a994/clickhouse_connect-0.7.19-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:921886b887f762e5cc3eef57ef784d419a3f66df85fd86fa2e7fbbf464c4c54a", size = 1027207, upload-time = "2024-08-21T21:35:59.832Z" }, + { url = "https://files.pythonhosted.org/packages/22/81/c2982a33b088b6c9af5d0bdc46413adc5fedceae063b1f8b56570bb28887/clickhouse_connect-0.7.19-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:6ad0cf8552a9e985cfa6524b674ae7c8f5ba51df5bd3ecddbd86c82cdbef41a7", size = 1054850, upload-time = "2024-08-21T21:36:01.559Z" }, + { url = "https://files.pythonhosted.org/packages/7b/a4/4a84ed3e92323d12700011cc8c4039f00a8c888079d65e75a4d4758ba288/clickhouse_connect-0.7.19-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:70f838ef0861cdf0e2e198171a1f3fd2ee05cf58e93495eeb9b17dfafb278186", size = 1022784, upload-time = "2024-08-21T21:36:02.805Z" }, + { url = "https://files.pythonhosted.org/packages/5e/67/3f5cc6f78c9adbbd6a3183a3f9f3196a116be19e958d7eaa6e307b391fed/clickhouse_connect-0.7.19-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c5f0d207cb0dcc1adb28ced63f872d080924b7562b263a9d54d4693b670eb066", size = 1071084, upload-time = "2024-08-21T21:36:04.052Z" }, + { url = "https://files.pythonhosted.org/packages/01/8d/a294e1cc752e22bc6ee08aa421ea31ed9559b09d46d35499449140a5c374/clickhouse_connect-0.7.19-cp311-cp311-win32.whl", hash = "sha256:8c96c4c242b98fcf8005e678a26dbd4361748721b6fa158c1fe84ad15c7edbbe", size = 221156, upload-time = "2024-08-21T21:36:05.72Z" }, + { url = "https://files.pythonhosted.org/packages/68/69/09b3a4e53f5d3d770e9fa70f6f04642cdb37cc76d37279c55fd4e868f845/clickhouse_connect-0.7.19-cp311-cp311-win_amd64.whl", hash = "sha256:bda092bab224875ed7c7683707d63f8a2322df654c4716e6611893a18d83e908", size = 238826, upload-time = "2024-08-21T21:36:06.892Z" }, + { url = "https://files.pythonhosted.org/packages/af/f8/1d48719728bac33c1a9815e0a7230940e078fd985b09af2371715de78a3c/clickhouse_connect-0.7.19-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:8f170d08166438d29f0dcfc8a91b672c783dc751945559e65eefff55096f9274", size = 256687, upload-time = "2024-08-21T21:36:08.245Z" }, + { url = "https://files.pythonhosted.org/packages/ed/0d/3cbbbd204be045c4727f9007679ad97d3d1d559b43ba844373a79af54d16/clickhouse_connect-0.7.19-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:26b80cb8f66bde9149a9a2180e2cc4895c1b7d34f9dceba81630a9b9a9ae66b2", size = 247631, upload-time = "2024-08-21T21:36:09.679Z" }, + { url = "https://files.pythonhosted.org/packages/b6/44/adb55285226d60e9c46331a9980c88dad8c8de12abb895c4e3149a088092/clickhouse_connect-0.7.19-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9ba80e3598acf916c4d1b2515671f65d9efee612a783c17c56a5a646f4db59b9", size = 1053767, upload-time = "2024-08-21T21:36:11.361Z" }, + { url = "https://files.pythonhosted.org/packages/6c/f3/a109c26a41153768be57374cb823cac5daf74c9098a5c61081ffabeb4e59/clickhouse_connect-0.7.19-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0d38c30bd847af0ce7ff738152478f913854db356af4d5824096394d0eab873d", size = 1072014, upload-time = "2024-08-21T21:36:12.752Z" }, + { url = "https://files.pythonhosted.org/packages/51/80/9c200e5e392a538f2444c9a6a93e1cf0e36588c7e8720882ac001e23b246/clickhouse_connect-0.7.19-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d41d4b159071c0e4f607563932d4fa5c2a8fc27d3ba1200d0929b361e5191864", size = 1027423, upload-time = "2024-08-21T21:36:14.483Z" }, + { url = "https://files.pythonhosted.org/packages/33/a3/219fcd1572f1ce198dcef86da8c6c526b04f56e8b7a82e21119677f89379/clickhouse_connect-0.7.19-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:3682c2426f5dbda574611210e3c7c951b9557293a49eb60a7438552435873889", size = 1053683, upload-time = "2024-08-21T21:36:15.828Z" }, + { url = "https://files.pythonhosted.org/packages/5d/df/687d90fbc0fd8ce586c46400f3791deac120e4c080aa8b343c0f676dfb08/clickhouse_connect-0.7.19-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:6d492064dca278eb61be3a2d70a5f082e2ebc8ceebd4f33752ae234116192020", size = 1021120, upload-time = "2024-08-21T21:36:17.184Z" }, + { url = "https://files.pythonhosted.org/packages/c8/3b/39ba71b103275df8ec90d424dbaca2dba82b28398c3d2aeac5a0141b6aae/clickhouse_connect-0.7.19-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:62612da163b934c1ff35df6155a47cf17ac0e2d2f9f0f8f913641e5c02cdf39f", size = 1073652, upload-time = "2024-08-21T21:36:19.053Z" }, + { url = "https://files.pythonhosted.org/packages/b3/92/06df8790a7d93d5d5f1098604fc7d79682784818030091966a3ce3f766a8/clickhouse_connect-0.7.19-cp312-cp312-win32.whl", hash = "sha256:196e48c977affc045794ec7281b4d711e169def00535ecab5f9fdeb8c177f149", size = 221589, upload-time = "2024-08-21T21:36:20.796Z" }, + { url = "https://files.pythonhosted.org/packages/42/1f/935d0810b73184a1d306f92458cb0a2e9b0de2377f536da874e063b8e422/clickhouse_connect-0.7.19-cp312-cp312-win_amd64.whl", hash = "sha256:b771ca6a473d65103dcae82810d3a62475c5372fc38d8f211513c72b954fb020", size = 239584, upload-time = "2024-08-21T21:36:22.105Z" }, ] [[package]] @@ -925,18 +976,18 @@ dependencies = [ { name = "requests" }, { name = "requests-toolbelt" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ac/25/6d0481860583f44953bd791de0b7c4f6d7ead7223f8a17e776247b34a5b4/cloudscraper-1.2.71.tar.gz", hash = "sha256:429c6e8aa6916d5bad5c8a5eac50f3ea53c9ac22616f6cb21b18dcc71517d0d3", size = 93261 } +sdist = { url = "https://files.pythonhosted.org/packages/ac/25/6d0481860583f44953bd791de0b7c4f6d7ead7223f8a17e776247b34a5b4/cloudscraper-1.2.71.tar.gz", hash = "sha256:429c6e8aa6916d5bad5c8a5eac50f3ea53c9ac22616f6cb21b18dcc71517d0d3", size = 93261, upload-time = "2023-04-25T23:20:19.467Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/81/97/fc88803a451029688dffd7eb446dc1b529657577aec13aceff1cc9628c5d/cloudscraper-1.2.71-py2.py3-none-any.whl", hash = "sha256:76f50ca529ed2279e220837befdec892626f9511708e200d48d5bb76ded679b0", size = 99652 }, + { url = "https://files.pythonhosted.org/packages/81/97/fc88803a451029688dffd7eb446dc1b529657577aec13aceff1cc9628c5d/cloudscraper-1.2.71-py2.py3-none-any.whl", hash = "sha256:76f50ca529ed2279e220837befdec892626f9511708e200d48d5bb76ded679b0", size = 99652, upload-time = "2023-04-25T23:20:15.974Z" }, ] [[package]] name = "colorama" version = "0.4.6" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697 } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335 }, + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, ] [[package]] @@ -946,9 +997,9 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "humanfriendly" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/cc/c7/eed8f27100517e8c0e6b923d5f0845d0cb99763da6fdee00478f91db7325/coloredlogs-15.0.1.tar.gz", hash = "sha256:7c991aa71a4577af2f82600d8f8f3a89f936baeaf9b50a9c197da014e5bf16b0", size = 278520 } +sdist = { url = "https://files.pythonhosted.org/packages/cc/c7/eed8f27100517e8c0e6b923d5f0845d0cb99763da6fdee00478f91db7325/coloredlogs-15.0.1.tar.gz", hash = "sha256:7c991aa71a4577af2f82600d8f8f3a89f936baeaf9b50a9c197da014e5bf16b0", size = 278520, upload-time = "2021-06-11T10:22:45.202Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a7/06/3d6badcf13db419e25b07041d9c7b4a2c331d3f4e7134445ec5df57714cd/coloredlogs-15.0.1-py2.py3-none-any.whl", hash = "sha256:612ee75c546f53e92e70049c9dbfcc18c935a2b9a53b66085ce9ef6a6e5c0934", size = 46018 }, + { url = "https://files.pythonhosted.org/packages/a7/06/3d6badcf13db419e25b07041d9c7b4a2c331d3f4e7134445ec5df57714cd/coloredlogs-15.0.1-py2.py3-none-any.whl", hash = "sha256:612ee75c546f53e92e70049c9dbfcc18c935a2b9a53b66085ce9ef6a6e5c0934", size = 46018, upload-time = "2021-06-11T10:22:42.561Z" }, ] [[package]] @@ -962,53 +1013,53 @@ dependencies = [ { name = "six" }, { name = "xmltodict" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c4/f2/be99b41433b33a76896680920fca621f191875ca410a66778015e47a501b/cos-python-sdk-v5-1.9.30.tar.gz", hash = "sha256:a23fd090211bf90883066d90cd74317860aa67c6d3aa80fe5e44b18c7e9b2a81", size = 108384 } +sdist = { url = "https://files.pythonhosted.org/packages/c4/f2/be99b41433b33a76896680920fca621f191875ca410a66778015e47a501b/cos-python-sdk-v5-1.9.30.tar.gz", hash = "sha256:a23fd090211bf90883066d90cd74317860aa67c6d3aa80fe5e44b18c7e9b2a81", size = 108384, upload-time = "2024-06-14T08:02:37.063Z" } [[package]] name = "couchbase" -version = "4.3.5" +version = "4.3.6" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/31/6d/560ce2b7a93c6d90777008f39616e86bdf6c0f9bdcff685f7047d71786a6/couchbase-4.3.5.tar.gz", hash = "sha256:2e2f239b2959ece983195e3ee17a9142bc75b23b0f6733031198648a5f1107ed", size = 6514248 } +sdist = { url = "https://files.pythonhosted.org/packages/2f/70/7cf92b2443330e7a4b626a02fe15fbeb1531337d75e6ae6393294e960d18/couchbase-4.3.6.tar.gz", hash = "sha256:d58c5ccdad5d85fc026f328bf4190c4fc0041fdbe68ad900fb32fc5497c3f061", size = 6517695, upload-time = "2025-05-15T17:21:38.157Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/64/8f/8d4e94893d1a1087bdf1045a055c44bfbfafc109dd193566f8f6216fbdcc/couchbase-4.3.5-cp311-cp311-macosx_10_15_x86_64.whl", hash = "sha256:21cb325b4a5b017581ee038dc2362865bcd59b3aa65685432a362f9f8e8f3276", size = 4774089 }, - { url = "https://files.pythonhosted.org/packages/fa/ff/00e02271cdb23968f523661b92988bd14e2e68a7c546fe3660efd2e0fc5a/couchbase-4.3.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5f063f79af8fe2425025ac9675e95bc914329db72f40adfd609451dcaf177920", size = 4295794 }, - { url = "https://files.pythonhosted.org/packages/38/15/f0ad47c6e5a2245121a70e687caab25dd080c4d24df2dc098aebd634121a/couchbase-4.3.5-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:c28c05189c138408693d926211d80e0cb82fae02ac5e4619cb37efeb26349c1c", size = 4801822 }, - { url = "https://files.pythonhosted.org/packages/c8/58/9958caf475a3732d40557b62bfcc8bf1f3658338e80c17d7d8ceab05179b/couchbase-4.3.5-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:caf8f30cd1d587829685cffe575ec2b0f750a10611fe9f14e615070c1756079b", size = 5017314 }, - { url = "https://files.pythonhosted.org/packages/52/31/6f186553b4f58e7762d82950eb778deaf58fe48c8be58f7926e08ce3ee28/couchbase-4.3.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:0aaef43bac983332e8bd8484a286a9576a878a95b4e5bfc6bf9c4aa8b3ff71b4", size = 5675942 }, - { url = "https://files.pythonhosted.org/packages/06/ff/70240653c27372c4da12a4dd5dfc3603e7c91d890014a9c1bc25c4053615/couchbase-4.3.5-cp311-cp311-win_amd64.whl", hash = "sha256:07deb48cd32e726088d2b2b93bb52fdca0b26e3e36b7d8b3728ea993a90b4327", size = 4013966 }, - { url = "https://files.pythonhosted.org/packages/50/e3/2f56064284d2687e58aedab57511636832cc9cd5380d379849ec65bf8819/couchbase-4.3.5-cp312-cp312-macosx_10_15_x86_64.whl", hash = "sha256:5c9f5754d7391ad3c53adb46a8b401bfd4e0db38aba707a5ed6daad295091afd", size = 4775392 }, - { url = "https://files.pythonhosted.org/packages/36/a1/8ec76a289d8e5941d252195de6190f86d7c6e67d8a5b4c949d7282c8176d/couchbase-4.3.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:92cea5d666d484c3801c2186ba2e0dd9df1bc654296c5862c94bf7a56e7e1e34", size = 4023876 }, - { url = "https://files.pythonhosted.org/packages/00/4b/ec60796cd22acfc5eb3d3fc638923b19639aa9463a7f7106e8f2d9a11929/couchbase-4.3.5-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f471b746cf13e9789de13fccd1ba4fbd3ae95e72710c3b07eb98cf8d72a5053b", size = 4803302 }, - { url = "https://files.pythonhosted.org/packages/39/5b/814b5a422ea153981e8894c32b0451fefe5e3bd95128b59fe9744df07a9f/couchbase-4.3.5-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:80de6c8ddf39c4ee380d093fcf83a6300e837db8a6f60a8ddb7df6fcbb662595", size = 5022682 }, - { url = "https://files.pythonhosted.org/packages/26/1d/3cdd06a46c436bc2d8d14d57e994785783e809a37bb65ab3631dd1ca4fbd/couchbase-4.3.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:9ce0c9e073a616cc6353ea93e9bac0fe6f79ff6df03fccf0c846f19b5ddf957b", size = 5675465 }, - { url = "https://files.pythonhosted.org/packages/0e/ec/2544932af7e118acbb6fe0b59163b4a8c8c72faec8fdb4bbeb062939b94c/couchbase-4.3.5-cp312-cp312-win_amd64.whl", hash = "sha256:b78f98001f40450ef1bbaf7d73fa28f0734e9bd72b38b96c18eb2090a09094fb", size = 4022539 }, + { url = "https://files.pythonhosted.org/packages/f3/0a/eae21d3a9331f7c93e8483f686e1bcb9e3b48f2ce98193beb0637a620926/couchbase-4.3.6-cp311-cp311-macosx_10_15_x86_64.whl", hash = "sha256:4c10fd26271c5630196b9bcc0dd7e17a45fa9c7e46ed5756e5690d125423160c", size = 4775710, upload-time = "2025-05-15T17:20:29.388Z" }, + { url = "https://files.pythonhosted.org/packages/f6/98/0ca042a42f5807bbf8050f52fff39ebceebc7bea7e5897907758f3e1ad39/couchbase-4.3.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:811eee7a6013cea7b15a718e201ee1188df162c656d27c7882b618ab57a08f3a", size = 4020743, upload-time = "2025-05-15T17:20:31.515Z" }, + { url = "https://files.pythonhosted.org/packages/f8/0f/c91407cb082d2322217e8f7ca4abb8eda016a81a4db5a74b7ac6b737597d/couchbase-4.3.6-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:2fc177e0161beb1e6e8c4b9561efcb97c51aed55a77ee11836ca194d33ae22b7", size = 4796091, upload-time = "2025-05-15T17:20:33.818Z" }, + { url = "https://files.pythonhosted.org/packages/8c/02/5567b660543828bdbbc68dcae080e388cb0be391aa8a97cce9d8c8a6c147/couchbase-4.3.6-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:02afb1c1edd6b215f702510412b5177ed609df8135930c23789bbc5901dd1b45", size = 5015684, upload-time = "2025-05-15T17:20:36.364Z" }, + { url = "https://files.pythonhosted.org/packages/dc/d1/767908826d5bdd258addab26d7f1d21bc42bafbf5f30d1b556ace06295af/couchbase-4.3.6-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:594e9eb17bb76ba8e10eeee17a16aef897dd90d33c6771cf2b5b4091da415b32", size = 5673513, upload-time = "2025-05-15T17:20:38.972Z" }, + { url = "https://files.pythonhosted.org/packages/f2/25/39ecde0a06692abce8bb0df4f15542933f05883647a1a57cdc7bbed9c77c/couchbase-4.3.6-cp311-cp311-win_amd64.whl", hash = "sha256:db22c56e38b8313f65807aa48309c8b8c7c44d5517b9ff1d8b4404d4740ec286", size = 4010728, upload-time = "2025-05-15T17:20:43.286Z" }, + { url = "https://files.pythonhosted.org/packages/b1/55/c12b8f626de71363fbe30578f4a0de1b8bb41afbe7646ff8538c3b38ce2a/couchbase-4.3.6-cp312-cp312-macosx_10_15_x86_64.whl", hash = "sha256:a2ae13432b859f513485d4cee691e1e4fce4af23ed4218b9355874b146343f8c", size = 4693517, upload-time = "2025-05-15T17:20:45.433Z" }, + { url = "https://files.pythonhosted.org/packages/a1/aa/2184934d283d99b34a004f577bf724d918278a2962781ca5690d4fa4b6c6/couchbase-4.3.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4ea5ca7e34b5d023c8bab406211ab5d71e74a976ba25fa693b4f8e6c74f85aa2", size = 4022393, upload-time = "2025-05-15T17:20:47.442Z" }, + { url = "https://files.pythonhosted.org/packages/80/29/ba6d3b205a51c04c270c1b56ea31da678b7edc565b35a34237ec2cfc708d/couchbase-4.3.6-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:6eaca0a71fd8f9af4344b7d6474d7b74d1784ae9a658f6bc3751df5f9a4185ae", size = 4798396, upload-time = "2025-05-15T17:20:49.473Z" }, + { url = "https://files.pythonhosted.org/packages/4a/94/d7d791808bd9064c01f965015ff40ee76e6bac10eaf2c73308023b9bdedf/couchbase-4.3.6-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0470378b986f69368caed6d668ac6530e635b0c1abaef3d3f524cfac0dacd878", size = 5018099, upload-time = "2025-05-15T17:20:52.541Z" }, + { url = "https://files.pythonhosted.org/packages/a6/04/cec160f9f4b862788e2a0167616472a5695b2f569bd62204938ab674835d/couchbase-4.3.6-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:374ce392558f1688ac073aa0b15c256b1a441201d965811fd862357ff05d27a9", size = 5672633, upload-time = "2025-05-15T17:20:55.994Z" }, + { url = "https://files.pythonhosted.org/packages/1b/a2/1da2ab45412b9414e2c6a578e0e7a24f29b9261ef7de11707c2fc98045b8/couchbase-4.3.6-cp312-cp312-win_amd64.whl", hash = "sha256:cd734333de34d8594504c163bb6c47aea9cc1f2cefdf8e91875dd9bf14e61e29", size = 4013298, upload-time = "2025-05-15T17:20:59.533Z" }, ] [[package]] name = "coverage" version = "7.2.7" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/45/8b/421f30467e69ac0e414214856798d4bc32da1336df745e49e49ae5c1e2a8/coverage-7.2.7.tar.gz", hash = "sha256:924d94291ca674905fe9481f12294eb11f2d3d3fd1adb20314ba89e94f44ed59", size = 762575 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c6/fa/529f55c9a1029c840bcc9109d5a15ff00478b7ff550a1ae361f8745f8ad5/coverage-7.2.7-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:06a9a2be0b5b576c3f18f1a241f0473575c4a26021b52b2a85263a00f034d51f", size = 200895 }, - { url = "https://files.pythonhosted.org/packages/67/d7/cd8fe689b5743fffac516597a1222834c42b80686b99f5b44ef43ccc2a43/coverage-7.2.7-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5baa06420f837184130752b7c5ea0808762083bf3487b5038d68b012e5937dbe", size = 201120 }, - { url = "https://files.pythonhosted.org/packages/8c/95/16eed713202406ca0a37f8ac259bbf144c9d24f9b8097a8e6ead61da2dbb/coverage-7.2.7-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fdec9e8cbf13a5bf63290fc6013d216a4c7232efb51548594ca3631a7f13c3a3", size = 233178 }, - { url = "https://files.pythonhosted.org/packages/c1/49/4d487e2ad5d54ed82ac1101e467e8994c09d6123c91b2a962145f3d262c2/coverage-7.2.7-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:52edc1a60c0d34afa421c9c37078817b2e67a392cab17d97283b64c5833f427f", size = 230754 }, - { url = "https://files.pythonhosted.org/packages/a7/cd/3ce94ad9d407a052dc2a74fbeb1c7947f442155b28264eb467ee78dea812/coverage-7.2.7-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:63426706118b7f5cf6bb6c895dc215d8a418d5952544042c8a2d9fe87fcf09cb", size = 232558 }, - { url = "https://files.pythonhosted.org/packages/8f/a8/12cc7b261f3082cc299ab61f677f7e48d93e35ca5c3c2f7241ed5525ccea/coverage-7.2.7-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:afb17f84d56068a7c29f5fa37bfd38d5aba69e3304af08ee94da8ed5b0865833", size = 241509 }, - { url = "https://files.pythonhosted.org/packages/04/fa/43b55101f75a5e9115259e8be70ff9279921cb6b17f04c34a5702ff9b1f7/coverage-7.2.7-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:48c19d2159d433ccc99e729ceae7d5293fbffa0bdb94952d3579983d1c8c9d97", size = 239924 }, - { url = "https://files.pythonhosted.org/packages/68/5f/d2bd0f02aa3c3e0311986e625ccf97fdc511b52f4f1a063e4f37b624772f/coverage-7.2.7-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:0e1f928eaf5469c11e886fe0885ad2bf1ec606434e79842a879277895a50942a", size = 240977 }, - { url = "https://files.pythonhosted.org/packages/ba/92/69c0722882643df4257ecc5437b83f4c17ba9e67f15dc6b77bad89b6982e/coverage-7.2.7-cp311-cp311-win32.whl", hash = "sha256:33d6d3ea29d5b3a1a632b3c4e4f4ecae24ef170b0b9ee493883f2df10039959a", size = 203168 }, - { url = "https://files.pythonhosted.org/packages/b1/96/c12ed0dfd4ec587f3739f53eb677b9007853fd486ccb0e7d5512a27bab2e/coverage-7.2.7-cp311-cp311-win_amd64.whl", hash = "sha256:5b7540161790b2f28143191f5f8ec02fb132660ff175b7747b95dcb77ac26562", size = 204185 }, - { url = "https://files.pythonhosted.org/packages/ff/d5/52fa1891d1802ab2e1b346d37d349cb41cdd4fd03f724ebbf94e80577687/coverage-7.2.7-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f2f67fe12b22cd130d34d0ef79206061bfb5eda52feb6ce0dba0644e20a03cf4", size = 201020 }, - { url = "https://files.pythonhosted.org/packages/24/df/6765898d54ea20e3197a26d26bb65b084deefadd77ce7de946b9c96dfdc5/coverage-7.2.7-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a342242fe22407f3c17f4b499276a02b01e80f861f1682ad1d95b04018e0c0d4", size = 233994 }, - { url = "https://files.pythonhosted.org/packages/15/81/b108a60bc758b448c151e5abceed027ed77a9523ecbc6b8a390938301841/coverage-7.2.7-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:171717c7cb6b453aebac9a2ef603699da237f341b38eebfee9be75d27dc38e01", size = 231358 }, - { url = "https://files.pythonhosted.org/packages/61/90/c76b9462f39897ebd8714faf21bc985b65c4e1ea6dff428ea9dc711ed0dd/coverage-7.2.7-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:49969a9f7ffa086d973d91cec8d2e31080436ef0fb4a359cae927e742abfaaa6", size = 233316 }, - { url = "https://files.pythonhosted.org/packages/04/d6/8cba3bf346e8b1a4fb3f084df7d8cea25a6b6c56aaca1f2e53829be17e9e/coverage-7.2.7-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b46517c02ccd08092f4fa99f24c3b83d8f92f739b4657b0f146246a0ca6a831d", size = 240159 }, - { url = "https://files.pythonhosted.org/packages/6e/ea/4a252dc77ca0605b23d477729d139915e753ee89e4c9507630e12ad64a80/coverage-7.2.7-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:a3d33a6b3eae87ceaefa91ffdc130b5e8536182cd6dfdbfc1aa56b46ff8c86de", size = 238127 }, - { url = "https://files.pythonhosted.org/packages/9f/5c/d9760ac497c41f9c4841f5972d0edf05d50cad7814e86ee7d133ec4a0ac8/coverage-7.2.7-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:976b9c42fb2a43ebf304fa7d4a310e5f16cc99992f33eced91ef6f908bd8f33d", size = 239833 }, - { url = "https://files.pythonhosted.org/packages/69/8c/26a95b08059db1cbb01e4b0e6d40f2e9debb628c6ca86b78f625ceaf9bab/coverage-7.2.7-cp312-cp312-win32.whl", hash = "sha256:8de8bb0e5ad103888d65abef8bca41ab93721647590a3f740100cd65c3b00511", size = 203463 }, - { url = "https://files.pythonhosted.org/packages/b7/00/14b00a0748e9eda26e97be07a63cc911108844004687321ddcc213be956c/coverage-7.2.7-cp312-cp312-win_amd64.whl", hash = "sha256:9e31cb64d7de6b6f09702bb27c02d1904b3aebfca610c12772452c4e6c21a0d3", size = 204347 }, +sdist = { url = "https://files.pythonhosted.org/packages/45/8b/421f30467e69ac0e414214856798d4bc32da1336df745e49e49ae5c1e2a8/coverage-7.2.7.tar.gz", hash = "sha256:924d94291ca674905fe9481f12294eb11f2d3d3fd1adb20314ba89e94f44ed59", size = 762575, upload-time = "2023-05-29T20:08:50.273Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c6/fa/529f55c9a1029c840bcc9109d5a15ff00478b7ff550a1ae361f8745f8ad5/coverage-7.2.7-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:06a9a2be0b5b576c3f18f1a241f0473575c4a26021b52b2a85263a00f034d51f", size = 200895, upload-time = "2023-05-29T20:07:21.963Z" }, + { url = "https://files.pythonhosted.org/packages/67/d7/cd8fe689b5743fffac516597a1222834c42b80686b99f5b44ef43ccc2a43/coverage-7.2.7-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5baa06420f837184130752b7c5ea0808762083bf3487b5038d68b012e5937dbe", size = 201120, upload-time = "2023-05-29T20:07:23.765Z" }, + { url = "https://files.pythonhosted.org/packages/8c/95/16eed713202406ca0a37f8ac259bbf144c9d24f9b8097a8e6ead61da2dbb/coverage-7.2.7-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fdec9e8cbf13a5bf63290fc6013d216a4c7232efb51548594ca3631a7f13c3a3", size = 233178, upload-time = "2023-05-29T20:07:25.281Z" }, + { url = "https://files.pythonhosted.org/packages/c1/49/4d487e2ad5d54ed82ac1101e467e8994c09d6123c91b2a962145f3d262c2/coverage-7.2.7-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:52edc1a60c0d34afa421c9c37078817b2e67a392cab17d97283b64c5833f427f", size = 230754, upload-time = "2023-05-29T20:07:27.044Z" }, + { url = "https://files.pythonhosted.org/packages/a7/cd/3ce94ad9d407a052dc2a74fbeb1c7947f442155b28264eb467ee78dea812/coverage-7.2.7-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:63426706118b7f5cf6bb6c895dc215d8a418d5952544042c8a2d9fe87fcf09cb", size = 232558, upload-time = "2023-05-29T20:07:28.743Z" }, + { url = "https://files.pythonhosted.org/packages/8f/a8/12cc7b261f3082cc299ab61f677f7e48d93e35ca5c3c2f7241ed5525ccea/coverage-7.2.7-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:afb17f84d56068a7c29f5fa37bfd38d5aba69e3304af08ee94da8ed5b0865833", size = 241509, upload-time = "2023-05-29T20:07:30.434Z" }, + { url = "https://files.pythonhosted.org/packages/04/fa/43b55101f75a5e9115259e8be70ff9279921cb6b17f04c34a5702ff9b1f7/coverage-7.2.7-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:48c19d2159d433ccc99e729ceae7d5293fbffa0bdb94952d3579983d1c8c9d97", size = 239924, upload-time = "2023-05-29T20:07:32.065Z" }, + { url = "https://files.pythonhosted.org/packages/68/5f/d2bd0f02aa3c3e0311986e625ccf97fdc511b52f4f1a063e4f37b624772f/coverage-7.2.7-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:0e1f928eaf5469c11e886fe0885ad2bf1ec606434e79842a879277895a50942a", size = 240977, upload-time = "2023-05-29T20:07:34.184Z" }, + { url = "https://files.pythonhosted.org/packages/ba/92/69c0722882643df4257ecc5437b83f4c17ba9e67f15dc6b77bad89b6982e/coverage-7.2.7-cp311-cp311-win32.whl", hash = "sha256:33d6d3ea29d5b3a1a632b3c4e4f4ecae24ef170b0b9ee493883f2df10039959a", size = 203168, upload-time = "2023-05-29T20:07:35.869Z" }, + { url = "https://files.pythonhosted.org/packages/b1/96/c12ed0dfd4ec587f3739f53eb677b9007853fd486ccb0e7d5512a27bab2e/coverage-7.2.7-cp311-cp311-win_amd64.whl", hash = "sha256:5b7540161790b2f28143191f5f8ec02fb132660ff175b7747b95dcb77ac26562", size = 204185, upload-time = "2023-05-29T20:07:37.39Z" }, + { url = "https://files.pythonhosted.org/packages/ff/d5/52fa1891d1802ab2e1b346d37d349cb41cdd4fd03f724ebbf94e80577687/coverage-7.2.7-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f2f67fe12b22cd130d34d0ef79206061bfb5eda52feb6ce0dba0644e20a03cf4", size = 201020, upload-time = "2023-05-29T20:07:38.724Z" }, + { url = "https://files.pythonhosted.org/packages/24/df/6765898d54ea20e3197a26d26bb65b084deefadd77ce7de946b9c96dfdc5/coverage-7.2.7-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a342242fe22407f3c17f4b499276a02b01e80f861f1682ad1d95b04018e0c0d4", size = 233994, upload-time = "2023-05-29T20:07:40.274Z" }, + { url = "https://files.pythonhosted.org/packages/15/81/b108a60bc758b448c151e5abceed027ed77a9523ecbc6b8a390938301841/coverage-7.2.7-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:171717c7cb6b453aebac9a2ef603699da237f341b38eebfee9be75d27dc38e01", size = 231358, upload-time = "2023-05-29T20:07:41.998Z" }, + { url = "https://files.pythonhosted.org/packages/61/90/c76b9462f39897ebd8714faf21bc985b65c4e1ea6dff428ea9dc711ed0dd/coverage-7.2.7-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:49969a9f7ffa086d973d91cec8d2e31080436ef0fb4a359cae927e742abfaaa6", size = 233316, upload-time = "2023-05-29T20:07:43.539Z" }, + { url = "https://files.pythonhosted.org/packages/04/d6/8cba3bf346e8b1a4fb3f084df7d8cea25a6b6c56aaca1f2e53829be17e9e/coverage-7.2.7-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b46517c02ccd08092f4fa99f24c3b83d8f92f739b4657b0f146246a0ca6a831d", size = 240159, upload-time = "2023-05-29T20:07:44.982Z" }, + { url = "https://files.pythonhosted.org/packages/6e/ea/4a252dc77ca0605b23d477729d139915e753ee89e4c9507630e12ad64a80/coverage-7.2.7-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:a3d33a6b3eae87ceaefa91ffdc130b5e8536182cd6dfdbfc1aa56b46ff8c86de", size = 238127, upload-time = "2023-05-29T20:07:46.522Z" }, + { url = "https://files.pythonhosted.org/packages/9f/5c/d9760ac497c41f9c4841f5972d0edf05d50cad7814e86ee7d133ec4a0ac8/coverage-7.2.7-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:976b9c42fb2a43ebf304fa7d4a310e5f16cc99992f33eced91ef6f908bd8f33d", size = 239833, upload-time = "2023-05-29T20:07:47.992Z" }, + { url = "https://files.pythonhosted.org/packages/69/8c/26a95b08059db1cbb01e4b0e6d40f2e9debb628c6ca86b78f625ceaf9bab/coverage-7.2.7-cp312-cp312-win32.whl", hash = "sha256:8de8bb0e5ad103888d65abef8bca41ab93721647590a3f740100cd65c3b00511", size = 203463, upload-time = "2023-05-29T20:07:49.939Z" }, + { url = "https://files.pythonhosted.org/packages/b7/00/14b00a0748e9eda26e97be07a63cc911108844004687321ddcc213be956c/coverage-7.2.7-cp312-cp312-win_amd64.whl", hash = "sha256:9e31cb64d7de6b6f09702bb27c02d1904b3aebfca610c12772452c4e6c21a0d3", size = 204347, upload-time = "2023-05-29T20:07:51.909Z" }, ] [package.optional-dependencies] @@ -1020,75 +1071,70 @@ toml = [ name = "crc32c" version = "2.7.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/7f/4c/4e40cc26347ac8254d3f25b9f94710b8e8df24ee4dddc1ba41907a88a94d/crc32c-2.7.1.tar.gz", hash = "sha256:f91b144a21eef834d64178e01982bb9179c354b3e9e5f4c803b0e5096384968c", size = 45712 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/45/8e/2f37f46368bbfd50edfc11b96f0aa135699034b1b020966c70ebaff3463b/crc32c-2.7.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:19e03a50545a3ef400bd41667d5525f71030488629c57d819e2dd45064f16192", size = 49672 }, - { url = "https://files.pythonhosted.org/packages/ed/b8/e52f7c4b045b871c2984d70f37c31d4861b533a8082912dfd107a96cf7c1/crc32c-2.7.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:8c03286b1e5ce9bed7090084f206aacd87c5146b4b10de56fe9e86cbbbf851cf", size = 37155 }, - { url = "https://files.pythonhosted.org/packages/25/ee/0cfa82a68736697f3c7e435ba658c2ef8c997f42b89f6ab4545efe1b2649/crc32c-2.7.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:80ebbf144a1a56a532b353e81fa0f3edca4f4baa1bf92b1dde2c663a32bb6a15", size = 35372 }, - { url = "https://files.pythonhosted.org/packages/aa/92/c878aaba81c431fcd93a059e9f6c90db397c585742793f0bf6e0c531cc67/crc32c-2.7.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:96b794fd11945298fdd5eb1290a812efb497c14bc42592c5c992ca077458eeba", size = 54879 }, - { url = "https://files.pythonhosted.org/packages/5b/f5/ab828ab3907095e06b18918408748950a9f726ee2b37be1b0839fb925ee1/crc32c-2.7.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9df7194dd3c0efb5a21f5d70595b7a8b4fd9921fbbd597d6d8e7a11eca3e2d27", size = 52588 }, - { url = "https://files.pythonhosted.org/packages/6a/2b/9e29e9ac4c4213d60491db09487125db358cd9263490fbadbd55e48fbe03/crc32c-2.7.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d698eec444b18e296a104d0b9bb6c596c38bdcb79d24eba49604636e9d747305", size = 53674 }, - { url = "https://files.pythonhosted.org/packages/79/ed/df3c4c14bf1b29f5c9b52d51fb6793e39efcffd80b2941d994e8f7f5f688/crc32c-2.7.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:e07cf10ef852d219d179333fd706d1c415626f1f05e60bd75acf0143a4d8b225", size = 54691 }, - { url = "https://files.pythonhosted.org/packages/0c/47/4917af3c9c1df2fff28bbfa6492673c9adeae5599dcc207bbe209847489c/crc32c-2.7.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:d2a051f296e6e92e13efee3b41db388931cdb4a2800656cd1ed1d9fe4f13a086", size = 52896 }, - { url = "https://files.pythonhosted.org/packages/1b/6f/26fc3dda5835cda8f6cd9d856afe62bdeae428de4c34fea200b0888e8835/crc32c-2.7.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a1738259802978cdf428f74156175da6a5fdfb7256f647fdc0c9de1bc6cd7173", size = 53554 }, - { url = "https://files.pythonhosted.org/packages/56/3e/6f39127f7027c75d130c0ba348d86a6150dff23761fbc6a5f71659f4521e/crc32c-2.7.1-cp311-cp311-win32.whl", hash = "sha256:f7786d219a1a1bf27d0aa1869821d11a6f8e90415cfffc1e37791690d4a848a1", size = 38370 }, - { url = "https://files.pythonhosted.org/packages/c9/fb/1587c2705a3a47a3d0067eecf9a6fec510761c96dec45c7b038fb5c8ff46/crc32c-2.7.1-cp311-cp311-win_amd64.whl", hash = "sha256:887f6844bb3ad35f0778cd10793ad217f7123a5422e40041231b8c4c7329649d", size = 39795 }, - { url = "https://files.pythonhosted.org/packages/1d/02/998dc21333413ce63fe4c1ca70eafe61ca26afc7eb353f20cecdb77d614e/crc32c-2.7.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:f7d1c4e761fe42bf856130daf8b2658df33fe0ced3c43dadafdfeaa42b57b950", size = 49568 }, - { url = "https://files.pythonhosted.org/packages/9c/3e/e3656bfa76e50ef87b7136fef2dbf3c46e225629432fc9184fdd7fd187ff/crc32c-2.7.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:73361c79a6e4605204457f19fda18b042a94508a52e53d10a4239da5fb0f6a34", size = 37019 }, - { url = "https://files.pythonhosted.org/packages/0b/7d/5ff9904046ad15a08772515db19df43107bf5e3901a89c36a577b5f40ba0/crc32c-2.7.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:afd778fc8ac0ed2ffbfb122a9aa6a0e409a8019b894a1799cda12c01534493e0", size = 35373 }, - { url = "https://files.pythonhosted.org/packages/4d/41/4aedc961893f26858ab89fc772d0eaba91f9870f19eaa933999dcacb94ec/crc32c-2.7.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:56ef661b34e9f25991fface7f9ad85e81bbc1b3fe3b916fd58c893eabe2fa0b8", size = 54675 }, - { url = "https://files.pythonhosted.org/packages/d6/63/8cabf09b7e39b9fec8f7010646c8b33057fc8d67e6093b3cc15563d23533/crc32c-2.7.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:571aa4429444b5d7f588e4377663592145d2d25eb1635abb530f1281794fc7c9", size = 52386 }, - { url = "https://files.pythonhosted.org/packages/79/13/13576941bf7cf95026abae43d8427c812c0054408212bf8ed490eda846b0/crc32c-2.7.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c02a3bd67dea95cdb25844aaf44ca2e1b0c1fd70b287ad08c874a95ef4bb38db", size = 53495 }, - { url = "https://files.pythonhosted.org/packages/3d/b6/55ffb26d0517d2d6c6f430ce2ad36ae7647c995c5bfd7abce7f32bb2bad1/crc32c-2.7.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:99d17637c4867672cb8adeea007294e3c3df9d43964369516cfe2c1f47ce500a", size = 54456 }, - { url = "https://files.pythonhosted.org/packages/c2/1a/5562e54cb629ecc5543d3604dba86ddfc7c7b7bf31d64005b38a00d31d31/crc32c-2.7.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:f4a400ac3c69a32e180d8753fd7ec7bccb80ade7ab0812855dce8a208e72495f", size = 52647 }, - { url = "https://files.pythonhosted.org/packages/48/ec/ce4138eaf356cd9aae60bbe931755e5e0151b3eca5f491fce6c01b97fd59/crc32c-2.7.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:588587772e55624dd9c7a906ec9e8773ae0b6ac5e270fc0bc84ee2758eba90d5", size = 53332 }, - { url = "https://files.pythonhosted.org/packages/5e/b5/144b42cd838a901175a916078781cb2c3c9f977151c9ba085aebd6d15b22/crc32c-2.7.1-cp312-cp312-win32.whl", hash = "sha256:9f14b60e5a14206e8173dd617fa0c4df35e098a305594082f930dae5488da428", size = 38371 }, - { url = "https://files.pythonhosted.org/packages/ae/c4/7929dcd5d9b57db0cce4fe6f6c191049380fc6d8c9b9f5581967f4ec018e/crc32c-2.7.1-cp312-cp312-win_amd64.whl", hash = "sha256:7c810a246660a24dc818047dc5f89c7ce7b2814e1e08a8e99993f4103f7219e8", size = 39805 }, +sdist = { url = "https://files.pythonhosted.org/packages/7f/4c/4e40cc26347ac8254d3f25b9f94710b8e8df24ee4dddc1ba41907a88a94d/crc32c-2.7.1.tar.gz", hash = "sha256:f91b144a21eef834d64178e01982bb9179c354b3e9e5f4c803b0e5096384968c", size = 45712, upload-time = "2024-09-24T06:20:17.553Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/45/8e/2f37f46368bbfd50edfc11b96f0aa135699034b1b020966c70ebaff3463b/crc32c-2.7.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:19e03a50545a3ef400bd41667d5525f71030488629c57d819e2dd45064f16192", size = 49672, upload-time = "2024-09-24T06:18:18.032Z" }, + { url = "https://files.pythonhosted.org/packages/ed/b8/e52f7c4b045b871c2984d70f37c31d4861b533a8082912dfd107a96cf7c1/crc32c-2.7.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:8c03286b1e5ce9bed7090084f206aacd87c5146b4b10de56fe9e86cbbbf851cf", size = 37155, upload-time = "2024-09-24T06:18:19.373Z" }, + { url = "https://files.pythonhosted.org/packages/25/ee/0cfa82a68736697f3c7e435ba658c2ef8c997f42b89f6ab4545efe1b2649/crc32c-2.7.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:80ebbf144a1a56a532b353e81fa0f3edca4f4baa1bf92b1dde2c663a32bb6a15", size = 35372, upload-time = "2024-09-24T06:18:20.983Z" }, + { url = "https://files.pythonhosted.org/packages/aa/92/c878aaba81c431fcd93a059e9f6c90db397c585742793f0bf6e0c531cc67/crc32c-2.7.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:96b794fd11945298fdd5eb1290a812efb497c14bc42592c5c992ca077458eeba", size = 54879, upload-time = "2024-09-24T06:18:23.085Z" }, + { url = "https://files.pythonhosted.org/packages/5b/f5/ab828ab3907095e06b18918408748950a9f726ee2b37be1b0839fb925ee1/crc32c-2.7.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9df7194dd3c0efb5a21f5d70595b7a8b4fd9921fbbd597d6d8e7a11eca3e2d27", size = 52588, upload-time = "2024-09-24T06:18:24.463Z" }, + { url = "https://files.pythonhosted.org/packages/6a/2b/9e29e9ac4c4213d60491db09487125db358cd9263490fbadbd55e48fbe03/crc32c-2.7.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d698eec444b18e296a104d0b9bb6c596c38bdcb79d24eba49604636e9d747305", size = 53674, upload-time = "2024-09-24T06:18:25.624Z" }, + { url = "https://files.pythonhosted.org/packages/79/ed/df3c4c14bf1b29f5c9b52d51fb6793e39efcffd80b2941d994e8f7f5f688/crc32c-2.7.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:e07cf10ef852d219d179333fd706d1c415626f1f05e60bd75acf0143a4d8b225", size = 54691, upload-time = "2024-09-24T06:18:26.578Z" }, + { url = "https://files.pythonhosted.org/packages/0c/47/4917af3c9c1df2fff28bbfa6492673c9adeae5599dcc207bbe209847489c/crc32c-2.7.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:d2a051f296e6e92e13efee3b41db388931cdb4a2800656cd1ed1d9fe4f13a086", size = 52896, upload-time = "2024-09-24T06:18:28.174Z" }, + { url = "https://files.pythonhosted.org/packages/1b/6f/26fc3dda5835cda8f6cd9d856afe62bdeae428de4c34fea200b0888e8835/crc32c-2.7.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a1738259802978cdf428f74156175da6a5fdfb7256f647fdc0c9de1bc6cd7173", size = 53554, upload-time = "2024-09-24T06:18:29.104Z" }, + { url = "https://files.pythonhosted.org/packages/56/3e/6f39127f7027c75d130c0ba348d86a6150dff23761fbc6a5f71659f4521e/crc32c-2.7.1-cp311-cp311-win32.whl", hash = "sha256:f7786d219a1a1bf27d0aa1869821d11a6f8e90415cfffc1e37791690d4a848a1", size = 38370, upload-time = "2024-09-24T06:18:30.013Z" }, + { url = "https://files.pythonhosted.org/packages/c9/fb/1587c2705a3a47a3d0067eecf9a6fec510761c96dec45c7b038fb5c8ff46/crc32c-2.7.1-cp311-cp311-win_amd64.whl", hash = "sha256:887f6844bb3ad35f0778cd10793ad217f7123a5422e40041231b8c4c7329649d", size = 39795, upload-time = "2024-09-24T06:18:31.324Z" }, + { url = "https://files.pythonhosted.org/packages/1d/02/998dc21333413ce63fe4c1ca70eafe61ca26afc7eb353f20cecdb77d614e/crc32c-2.7.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:f7d1c4e761fe42bf856130daf8b2658df33fe0ced3c43dadafdfeaa42b57b950", size = 49568, upload-time = "2024-09-24T06:18:32.425Z" }, + { url = "https://files.pythonhosted.org/packages/9c/3e/e3656bfa76e50ef87b7136fef2dbf3c46e225629432fc9184fdd7fd187ff/crc32c-2.7.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:73361c79a6e4605204457f19fda18b042a94508a52e53d10a4239da5fb0f6a34", size = 37019, upload-time = "2024-09-24T06:18:34.097Z" }, + { url = "https://files.pythonhosted.org/packages/0b/7d/5ff9904046ad15a08772515db19df43107bf5e3901a89c36a577b5f40ba0/crc32c-2.7.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:afd778fc8ac0ed2ffbfb122a9aa6a0e409a8019b894a1799cda12c01534493e0", size = 35373, upload-time = "2024-09-24T06:18:35.02Z" }, + { url = "https://files.pythonhosted.org/packages/4d/41/4aedc961893f26858ab89fc772d0eaba91f9870f19eaa933999dcacb94ec/crc32c-2.7.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:56ef661b34e9f25991fface7f9ad85e81bbc1b3fe3b916fd58c893eabe2fa0b8", size = 54675, upload-time = "2024-09-24T06:18:35.954Z" }, + { url = "https://files.pythonhosted.org/packages/d6/63/8cabf09b7e39b9fec8f7010646c8b33057fc8d67e6093b3cc15563d23533/crc32c-2.7.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:571aa4429444b5d7f588e4377663592145d2d25eb1635abb530f1281794fc7c9", size = 52386, upload-time = "2024-09-24T06:18:36.896Z" }, + { url = "https://files.pythonhosted.org/packages/79/13/13576941bf7cf95026abae43d8427c812c0054408212bf8ed490eda846b0/crc32c-2.7.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c02a3bd67dea95cdb25844aaf44ca2e1b0c1fd70b287ad08c874a95ef4bb38db", size = 53495, upload-time = "2024-09-24T06:18:38.099Z" }, + { url = "https://files.pythonhosted.org/packages/3d/b6/55ffb26d0517d2d6c6f430ce2ad36ae7647c995c5bfd7abce7f32bb2bad1/crc32c-2.7.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:99d17637c4867672cb8adeea007294e3c3df9d43964369516cfe2c1f47ce500a", size = 54456, upload-time = "2024-09-24T06:18:39.051Z" }, + { url = "https://files.pythonhosted.org/packages/c2/1a/5562e54cb629ecc5543d3604dba86ddfc7c7b7bf31d64005b38a00d31d31/crc32c-2.7.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:f4a400ac3c69a32e180d8753fd7ec7bccb80ade7ab0812855dce8a208e72495f", size = 52647, upload-time = "2024-09-24T06:18:40.021Z" }, + { url = "https://files.pythonhosted.org/packages/48/ec/ce4138eaf356cd9aae60bbe931755e5e0151b3eca5f491fce6c01b97fd59/crc32c-2.7.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:588587772e55624dd9c7a906ec9e8773ae0b6ac5e270fc0bc84ee2758eba90d5", size = 53332, upload-time = "2024-09-24T06:18:40.925Z" }, + { url = "https://files.pythonhosted.org/packages/5e/b5/144b42cd838a901175a916078781cb2c3c9f977151c9ba085aebd6d15b22/crc32c-2.7.1-cp312-cp312-win32.whl", hash = "sha256:9f14b60e5a14206e8173dd617fa0c4df35e098a305594082f930dae5488da428", size = 38371, upload-time = "2024-09-24T06:18:42.711Z" }, + { url = "https://files.pythonhosted.org/packages/ae/c4/7929dcd5d9b57db0cce4fe6f6c191049380fc6d8c9b9f5581967f4ec018e/crc32c-2.7.1-cp312-cp312-win_amd64.whl", hash = "sha256:7c810a246660a24dc818047dc5f89c7ce7b2814e1e08a8e99993f4103f7219e8", size = 39805, upload-time = "2024-09-24T06:18:43.6Z" }, ] [[package]] name = "crcmod" version = "1.7" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/6b/b0/e595ce2a2527e169c3bcd6c33d2473c1918e0b7f6826a043ca1245dd4e5b/crcmod-1.7.tar.gz", hash = "sha256:dc7051a0db5f2bd48665a990d3ec1cc305a466a77358ca4492826f41f283601e", size = 89670 } +sdist = { url = "https://files.pythonhosted.org/packages/6b/b0/e595ce2a2527e169c3bcd6c33d2473c1918e0b7f6826a043ca1245dd4e5b/crcmod-1.7.tar.gz", hash = "sha256:dc7051a0db5f2bd48665a990d3ec1cc305a466a77358ca4492826f41f283601e", size = 89670, upload-time = "2010-06-27T14:35:29.538Z" } [[package]] name = "cryptography" -version = "44.0.2" +version = "42.0.8" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cffi", marker = "platform_python_implementation != 'PyPy'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/cd/25/4ce80c78963834b8a9fd1cc1266be5ed8d1840785c0f2e1b73b8d128d505/cryptography-44.0.2.tar.gz", hash = "sha256:c63454aa261a0cf0c5b4718349629793e9e634993538db841165b3df74f37ec0", size = 710807 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/92/ef/83e632cfa801b221570c5f58c0369db6fa6cef7d9ff859feab1aae1a8a0f/cryptography-44.0.2-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:efcfe97d1b3c79e486554efddeb8f6f53a4cdd4cf6086642784fa31fc384e1d7", size = 6676361 }, - { url = "https://files.pythonhosted.org/packages/30/ec/7ea7c1e4c8fc8329506b46c6c4a52e2f20318425d48e0fe597977c71dbce/cryptography-44.0.2-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:29ecec49f3ba3f3849362854b7253a9f59799e3763b0c9d0826259a88efa02f1", size = 3952350 }, - { url = "https://files.pythonhosted.org/packages/27/61/72e3afdb3c5ac510330feba4fc1faa0fe62e070592d6ad00c40bb69165e5/cryptography-44.0.2-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc821e161ae88bfe8088d11bb39caf2916562e0a2dc7b6d56714a48b784ef0bb", size = 4166572 }, - { url = "https://files.pythonhosted.org/packages/26/e4/ba680f0b35ed4a07d87f9e98f3ebccb05091f3bf6b5a478b943253b3bbd5/cryptography-44.0.2-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:3c00b6b757b32ce0f62c574b78b939afab9eecaf597c4d624caca4f9e71e7843", size = 3958124 }, - { url = "https://files.pythonhosted.org/packages/9c/e8/44ae3e68c8b6d1cbc59040288056df2ad7f7f03bbcaca6b503c737ab8e73/cryptography-44.0.2-cp37-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:7bdcd82189759aba3816d1f729ce42ffded1ac304c151d0a8e89b9996ab863d5", size = 3678122 }, - { url = "https://files.pythonhosted.org/packages/27/7b/664ea5e0d1eab511a10e480baf1c5d3e681c7d91718f60e149cec09edf01/cryptography-44.0.2-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:4973da6ca3db4405c54cd0b26d328be54c7747e89e284fcff166132eb7bccc9c", size = 4191831 }, - { url = "https://files.pythonhosted.org/packages/2a/07/79554a9c40eb11345e1861f46f845fa71c9e25bf66d132e123d9feb8e7f9/cryptography-44.0.2-cp37-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:4e389622b6927d8133f314949a9812972711a111d577a5d1f4bee5e58736b80a", size = 3960583 }, - { url = "https://files.pythonhosted.org/packages/bb/6d/858e356a49a4f0b591bd6789d821427de18432212e137290b6d8a817e9bf/cryptography-44.0.2-cp37-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:f514ef4cd14bb6fb484b4a60203e912cfcb64f2ab139e88c2274511514bf7308", size = 4191753 }, - { url = "https://files.pythonhosted.org/packages/b2/80/62df41ba4916067fa6b125aa8c14d7e9181773f0d5d0bd4dcef580d8b7c6/cryptography-44.0.2-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:1bc312dfb7a6e5d66082c87c34c8a62176e684b6fe3d90fcfe1568de675e6688", size = 4079550 }, - { url = "https://files.pythonhosted.org/packages/f3/cd/2558cc08f7b1bb40683f99ff4327f8dcfc7de3affc669e9065e14824511b/cryptography-44.0.2-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:3b721b8b4d948b218c88cb8c45a01793483821e709afe5f622861fc6182b20a7", size = 4298367 }, - { url = "https://files.pythonhosted.org/packages/71/59/94ccc74788945bc3bd4cf355d19867e8057ff5fdbcac781b1ff95b700fb1/cryptography-44.0.2-cp37-abi3-win32.whl", hash = "sha256:51e4de3af4ec3899d6d178a8c005226491c27c4ba84101bfb59c901e10ca9f79", size = 2772843 }, - { url = "https://files.pythonhosted.org/packages/ca/2c/0d0bbaf61ba05acb32f0841853cfa33ebb7a9ab3d9ed8bb004bd39f2da6a/cryptography-44.0.2-cp37-abi3-win_amd64.whl", hash = "sha256:c505d61b6176aaf982c5717ce04e87da5abc9a36a5b39ac03905c4aafe8de7aa", size = 3209057 }, - { url = "https://files.pythonhosted.org/packages/9e/be/7a26142e6d0f7683d8a382dd963745e65db895a79a280a30525ec92be890/cryptography-44.0.2-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:8e0ddd63e6bf1161800592c71ac794d3fb8001f2caebe0966e77c5234fa9efc3", size = 6677789 }, - { url = "https://files.pythonhosted.org/packages/06/88/638865be7198a84a7713950b1db7343391c6066a20e614f8fa286eb178ed/cryptography-44.0.2-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:81276f0ea79a208d961c433a947029e1a15948966658cf6710bbabb60fcc2639", size = 3951919 }, - { url = "https://files.pythonhosted.org/packages/d7/fc/99fe639bcdf58561dfad1faa8a7369d1dc13f20acd78371bb97a01613585/cryptography-44.0.2-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9a1e657c0f4ea2a23304ee3f964db058c9e9e635cc7019c4aa21c330755ef6fd", size = 4167812 }, - { url = "https://files.pythonhosted.org/packages/53/7b/aafe60210ec93d5d7f552592a28192e51d3c6b6be449e7fd0a91399b5d07/cryptography-44.0.2-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:6210c05941994290f3f7f175a4a57dbbb2afd9273657614c506d5976db061181", size = 3958571 }, - { url = "https://files.pythonhosted.org/packages/16/32/051f7ce79ad5a6ef5e26a92b37f172ee2d6e1cce09931646eef8de1e9827/cryptography-44.0.2-cp39-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:d1c3572526997b36f245a96a2b1713bf79ce99b271bbcf084beb6b9b075f29ea", size = 3679832 }, - { url = "https://files.pythonhosted.org/packages/78/2b/999b2a1e1ba2206f2d3bca267d68f350beb2b048a41ea827e08ce7260098/cryptography-44.0.2-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:b042d2a275c8cee83a4b7ae30c45a15e6a4baa65a179a0ec2d78ebb90e4f6699", size = 4193719 }, - { url = "https://files.pythonhosted.org/packages/72/97/430e56e39a1356e8e8f10f723211a0e256e11895ef1a135f30d7d40f2540/cryptography-44.0.2-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:d03806036b4f89e3b13b6218fefea8d5312e450935b1a2d55f0524e2ed7c59d9", size = 3960852 }, - { url = "https://files.pythonhosted.org/packages/89/33/c1cf182c152e1d262cac56850939530c05ca6c8d149aa0dcee490b417e99/cryptography-44.0.2-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:c7362add18b416b69d58c910caa217f980c5ef39b23a38a0880dfd87bdf8cd23", size = 4193906 }, - { url = "https://files.pythonhosted.org/packages/e1/99/87cf26d4f125380dc674233971069bc28d19b07f7755b29861570e513650/cryptography-44.0.2-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:8cadc6e3b5a1f144a039ea08a0bdb03a2a92e19c46be3285123d32029f40a922", size = 4081572 }, - { url = "https://files.pythonhosted.org/packages/b3/9f/6a3e0391957cc0c5f84aef9fbdd763035f2b52e998a53f99345e3ac69312/cryptography-44.0.2-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:6f101b1f780f7fc613d040ca4bdf835c6ef3b00e9bd7125a4255ec574c7916e4", size = 4298631 }, - { url = "https://files.pythonhosted.org/packages/e2/a5/5bc097adb4b6d22a24dea53c51f37e480aaec3465285c253098642696423/cryptography-44.0.2-cp39-abi3-win32.whl", hash = "sha256:3dc62975e31617badc19a906481deacdeb80b4bb454394b4098e3f2525a488c5", size = 2773792 }, - { url = "https://files.pythonhosted.org/packages/33/cf/1f7649b8b9a3543e042d3f348e398a061923ac05b507f3f4d95f11938aa9/cryptography-44.0.2-cp39-abi3-win_amd64.whl", hash = "sha256:5f6f90b72d8ccadb9c6e311c775c8305381db88374c65fa1a68250aa8a9cb3a6", size = 3210957 }, - { url = "https://files.pythonhosted.org/packages/d6/d7/f30e75a6aa7d0f65031886fa4a1485c2fbfe25a1896953920f6a9cfe2d3b/cryptography-44.0.2-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:909c97ab43a9c0c0b0ada7a1281430e4e5ec0458e6d9244c0e821bbf152f061d", size = 3887513 }, - { url = "https://files.pythonhosted.org/packages/9c/b4/7a494ce1032323ca9db9a3661894c66e0d7142ad2079a4249303402d8c71/cryptography-44.0.2-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:96e7a5e9d6e71f9f4fca8eebfd603f8e86c5225bb18eb621b2c1e50b290a9471", size = 4107432 }, - { url = "https://files.pythonhosted.org/packages/45/f8/6b3ec0bc56123b344a8d2b3264a325646d2dcdbdd9848b5e6f3d37db90b3/cryptography-44.0.2-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:d1b3031093a366ac767b3feb8bcddb596671b3aaff82d4050f984da0c248b615", size = 3891421 }, - { url = "https://files.pythonhosted.org/packages/57/ff/f3b4b2d007c2a646b0f69440ab06224f9cf37a977a72cdb7b50632174e8a/cryptography-44.0.2-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:04abd71114848aa25edb28e225ab5f268096f44cf0127f3d36975bdf1bdf3390", size = 4107081 }, +sdist = { url = "https://files.pythonhosted.org/packages/93/a7/1498799a2ea06148463a9a2c10ab2f6a921a74fb19e231b27dc412a748e2/cryptography-42.0.8.tar.gz", hash = "sha256:8d09d05439ce7baa8e9e95b07ec5b6c886f548deb7e0f69ef25f64b3bce842f2", size = 671250, upload-time = "2024-06-04T19:55:08.609Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f9/8b/1b929ba8139430e09e140e6939c2b29c18df1f2fc2149e41bdbdcdaf5d1f/cryptography-42.0.8-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:81d8a521705787afe7a18d5bfb47ea9d9cc068206270aad0b96a725022e18d2e", size = 5899961, upload-time = "2024-06-04T19:53:57.933Z" }, + { url = "https://files.pythonhosted.org/packages/fa/5d/31d833daa800e4fab33209843095df7adb4a78ea536929145534cbc15026/cryptography-42.0.8-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:961e61cefdcb06e0c6d7e3a1b22ebe8b996eb2bf50614e89384be54c48c6b63d", size = 3114353, upload-time = "2024-06-04T19:54:12.171Z" }, + { url = "https://files.pythonhosted.org/packages/5d/32/f6326c70a9f0f258a201d3b2632bca586ea24d214cec3cf36e374040e273/cryptography-42.0.8-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e3ec3672626e1b9e55afd0df6d774ff0e953452886e06e0f1eb7eb0c832e8902", size = 3647773, upload-time = "2024-06-04T19:54:07.051Z" }, + { url = "https://files.pythonhosted.org/packages/35/66/2d87e9ca95c82c7ee5f2c09716fc4c4242c1ae6647b9bd27e55e920e9f10/cryptography-42.0.8-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e599b53fd95357d92304510fb7bda8523ed1f79ca98dce2f43c115950aa78801", size = 3839763, upload-time = "2024-06-04T19:54:30.383Z" }, + { url = "https://files.pythonhosted.org/packages/c2/de/8083fa2e68d403553a01a9323f4f8b9d7ffed09928ba25635c29fb28c1e7/cryptography-42.0.8-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:5226d5d21ab681f432a9c1cf8b658c0cb02533eece706b155e5fbd8a0cdd3949", size = 3632661, upload-time = "2024-06-04T19:54:32.955Z" }, + { url = "https://files.pythonhosted.org/packages/07/40/d6f6819c62e808ea74639c3c640f7edd636b86cce62cb14943996a15df92/cryptography-42.0.8-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:6b7c4f03ce01afd3b76cf69a5455caa9cfa3de8c8f493e0d3ab7d20611c8dae9", size = 3851536, upload-time = "2024-06-04T19:53:53.131Z" }, + { url = "https://files.pythonhosted.org/packages/5c/46/de71d48abf2b6d3c808f4fbb0f4dc44a4e72786be23df0541aa2a3f6fd7e/cryptography-42.0.8-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:2346b911eb349ab547076f47f2e035fc8ff2c02380a7cbbf8d87114fa0f1c583", size = 3754209, upload-time = "2024-06-04T19:54:55.259Z" }, + { url = "https://files.pythonhosted.org/packages/25/c9/86f04e150c5d5d5e4a731a2c1e0e43da84d901f388e3fea3d5de98d689a7/cryptography-42.0.8-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:ad803773e9df0b92e0a817d22fd8a3675493f690b96130a5e24f1b8fabbea9c7", size = 3923551, upload-time = "2024-06-04T19:54:16.46Z" }, + { url = "https://files.pythonhosted.org/packages/53/c2/903014dafb7271fb148887d4355b2e90319cad6e810663be622b0c933fc9/cryptography-42.0.8-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:2f66d9cd9147ee495a8374a45ca445819f8929a3efcd2e3df6428e46c3cbb10b", size = 3739265, upload-time = "2024-06-04T19:54:23.194Z" }, + { url = "https://files.pythonhosted.org/packages/95/26/82d704d988a193cbdc69ac3b41c687c36eaed1642cce52530ad810c35645/cryptography-42.0.8-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:d45b940883a03e19e944456a558b67a41160e367a719833c53de6911cabba2b7", size = 3937371, upload-time = "2024-06-04T19:55:04.303Z" }, + { url = "https://files.pythonhosted.org/packages/cf/71/4e0d05c9acd638a225f57fb6162aa3d03613c11b76893c23ea4675bb28c5/cryptography-42.0.8-cp37-abi3-win32.whl", hash = "sha256:a0c5b2b0585b6af82d7e385f55a8bc568abff8923af147ee3c07bd8b42cda8b2", size = 2438849, upload-time = "2024-06-04T19:54:27.39Z" }, + { url = "https://files.pythonhosted.org/packages/06/0f/78da3cad74f2ba6c45321dc90394d70420ea846730dc042ef527f5a224b5/cryptography-42.0.8-cp37-abi3-win_amd64.whl", hash = "sha256:57080dee41209e556a9a4ce60d229244f7a66ef52750f813bfbe18959770cfba", size = 2889090, upload-time = "2024-06-04T19:54:14.245Z" }, + { url = "https://files.pythonhosted.org/packages/60/12/f064af29190cdb1d38fe07f3db6126091639e1dece7ec77c4ff037d49193/cryptography-42.0.8-cp39-abi3-macosx_10_12_universal2.whl", hash = "sha256:dea567d1b0e8bc5764b9443858b673b734100c2871dc93163f58c46a97a83d28", size = 5901232, upload-time = "2024-06-04T19:54:52.722Z" }, + { url = "https://files.pythonhosted.org/packages/43/c2/4a3eef67e009a522711ebd8ac89424c3a7fe591ece7035d964419ad52a1d/cryptography-42.0.8-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c4783183f7cb757b73b2ae9aed6599b96338eb957233c58ca8f49a49cc32fd5e", size = 3648711, upload-time = "2024-06-04T19:54:44.323Z" }, + { url = "https://files.pythonhosted.org/packages/49/1c/9f6d13cc8041c05eebff1154e4e71bedd1db8e174fff999054435994187a/cryptography-42.0.8-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a0608251135d0e03111152e41f0cc2392d1e74e35703960d4190b2e0f4ca9c70", size = 3841968, upload-time = "2024-06-04T19:54:57.911Z" }, + { url = "https://files.pythonhosted.org/packages/5f/f9/c3d4f19b82bdb25a3d857fe96e7e571c981810e47e3f299cc13ac429066a/cryptography-42.0.8-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:dc0fdf6787f37b1c6b08e6dfc892d9d068b5bdb671198c72072828b80bd5fe4c", size = 3633032, upload-time = "2024-06-04T19:54:48.518Z" }, + { url = "https://files.pythonhosted.org/packages/fa/e2/b7e6e8c261536c489d9cf908769880d94bd5d9a187e166b0dc838d2e6a56/cryptography-42.0.8-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:9c0c1716c8447ee7dbf08d6db2e5c41c688544c61074b54fc4564196f55c25a7", size = 3852478, upload-time = "2024-06-04T19:54:50.599Z" }, + { url = "https://files.pythonhosted.org/packages/a2/68/e16751f6b859bc120f53fddbf3ebada5c34f0e9689d8af32884d8b2e4b4c/cryptography-42.0.8-cp39-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:fff12c88a672ab9c9c1cf7b0c80e3ad9e2ebd9d828d955c126be4fd3e5578c9e", size = 3754102, upload-time = "2024-06-04T19:54:46.231Z" }, + { url = "https://files.pythonhosted.org/packages/0f/38/85c74d0ac4c540780e072b1e6f148ecb718418c1062edcb20d22f3ec5bbb/cryptography-42.0.8-cp39-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:cafb92b2bc622cd1aa6a1dce4b93307792633f4c5fe1f46c6b97cf67073ec961", size = 3925042, upload-time = "2024-06-04T19:54:34.767Z" }, + { url = "https://files.pythonhosted.org/packages/89/f4/a8b982e88eb5350407ebdbf4717b55043271d878705329e107f4783555f2/cryptography-42.0.8-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:31f721658a29331f895a5a54e7e82075554ccfb8b163a18719d342f5ffe5ecb1", size = 3738833, upload-time = "2024-06-04T19:54:05.231Z" }, + { url = "https://files.pythonhosted.org/packages/fd/2b/be327b580645927bb1a1f32d5a175b897a9b956bc085b095e15c40bac9ed/cryptography-42.0.8-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:b297f90c5723d04bcc8265fc2a0f86d4ea2e0f7ab4b6994459548d3a6b992a14", size = 3938751, upload-time = "2024-06-04T19:54:37.837Z" }, + { url = "https://files.pythonhosted.org/packages/3c/d5/c6a78ffccdbe4516711ebaa9ed2c7eb6ac5dfa3dc920f2c7e920af2418b0/cryptography-42.0.8-cp39-abi3-win32.whl", hash = "sha256:2f88d197e66c65be5e42cd72e5c18afbfae3f741742070e3019ac8f4ac57262c", size = 2439281, upload-time = "2024-06-04T19:53:55.903Z" }, + { url = "https://files.pythonhosted.org/packages/a2/7b/b0d330852dd5953daee6b15f742f15d9f18e9c0154eb4cfcc8718f0436da/cryptography-42.0.8-cp39-abi3-win_amd64.whl", hash = "sha256:fa76fbb7596cc5839320000cdd5d0955313696d9511debab7ee7278fc8b5c84a", size = 2886038, upload-time = "2024-06-04T19:54:18.707Z" }, ] [[package]] @@ -1099,27 +1145,27 @@ dependencies = [ { name = "marshmallow" }, { name = "typing-inspect" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/64/a4/f71d9cf3a5ac257c993b5ca3f93df5f7fb395c725e7f1e6479d2514173c3/dataclasses_json-0.6.7.tar.gz", hash = "sha256:b6b3e528266ea45b9535223bc53ca645f5208833c29229e847b3f26a1cc55fc0", size = 32227 } +sdist = { url = "https://files.pythonhosted.org/packages/64/a4/f71d9cf3a5ac257c993b5ca3f93df5f7fb395c725e7f1e6479d2514173c3/dataclasses_json-0.6.7.tar.gz", hash = "sha256:b6b3e528266ea45b9535223bc53ca645f5208833c29229e847b3f26a1cc55fc0", size = 32227, upload-time = "2024-06-09T16:20:19.103Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c3/be/d0d44e092656fe7a06b55e6103cbce807cdbdee17884a5367c68c9860853/dataclasses_json-0.6.7-py3-none-any.whl", hash = "sha256:0dbf33f26c8d5305befd61b39d2b3414e8a407bedc2834dea9b8d642666fb40a", size = 28686 }, + { url = "https://files.pythonhosted.org/packages/c3/be/d0d44e092656fe7a06b55e6103cbce807cdbdee17884a5367c68c9860853/dataclasses_json-0.6.7-py3-none-any.whl", hash = "sha256:0dbf33f26c8d5305befd61b39d2b3414e8a407bedc2834dea9b8d642666fb40a", size = 28686, upload-time = "2024-06-09T16:20:16.715Z" }, ] [[package]] name = "decorator" version = "5.2.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/43/fa/6d96a0978d19e17b68d634497769987b16c8f4cd0a7a05048bec693caa6b/decorator-5.2.1.tar.gz", hash = "sha256:65f266143752f734b0a7cc83c46f4618af75b8c5911b00ccb61d0ac9b6da0360", size = 56711 } +sdist = { url = "https://files.pythonhosted.org/packages/43/fa/6d96a0978d19e17b68d634497769987b16c8f4cd0a7a05048bec693caa6b/decorator-5.2.1.tar.gz", hash = "sha256:65f266143752f734b0a7cc83c46f4618af75b8c5911b00ccb61d0ac9b6da0360", size = 56711, upload-time = "2025-02-24T04:41:34.073Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/4e/8c/f3147f5c4b73e7550fe5f9352eaa956ae838d5c51eb58e7a25b9f3e2643b/decorator-5.2.1-py3-none-any.whl", hash = "sha256:d316bb415a2d9e2d2b3abcc4084c6502fc09240e292cd76a76afc106a1c8e04a", size = 9190 }, + { url = "https://files.pythonhosted.org/packages/4e/8c/f3147f5c4b73e7550fe5f9352eaa956ae838d5c51eb58e7a25b9f3e2643b/decorator-5.2.1-py3-none-any.whl", hash = "sha256:d316bb415a2d9e2d2b3abcc4084c6502fc09240e292cd76a76afc106a1c8e04a", size = 9190, upload-time = "2025-02-24T04:41:32.565Z" }, ] [[package]] name = "defusedxml" version = "0.7.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/0f/d5/c66da9b79e5bdb124974bfe172b4daf3c984ebd9c2a06e2b8a4dc7331c72/defusedxml-0.7.1.tar.gz", hash = "sha256:1bb3032db185915b62d7c6209c5a8792be6a32ab2fedacc84e01b52c51aa3e69", size = 75520 } +sdist = { url = "https://files.pythonhosted.org/packages/0f/d5/c66da9b79e5bdb124974bfe172b4daf3c984ebd9c2a06e2b8a4dc7331c72/defusedxml-0.7.1.tar.gz", hash = "sha256:1bb3032db185915b62d7c6209c5a8792be6a32ab2fedacc84e01b52c51aa3e69", size = 75520, upload-time = "2021-03-08T10:59:26.269Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/07/6c/aa3f2f849e01cb6a001cd8554a88d4c77c5c1a31c95bdf1cf9301e6d9ef4/defusedxml-0.7.1-py2.py3-none-any.whl", hash = "sha256:a352e7e428770286cc899e2542b6cdaedb2b4953ff269a210103ec58f6198a61", size = 25604 }, + { url = "https://files.pythonhosted.org/packages/07/6c/aa3f2f849e01cb6a001cd8554a88d4c77c5c1a31c95bdf1cf9301e6d9ef4/defusedxml-0.7.1-py2.py3-none-any.whl", hash = "sha256:a352e7e428770286cc899e2542b6cdaedb2b4953ff269a210103ec58f6198a61", size = 25604, upload-time = "2021-03-08T10:59:24.45Z" }, ] [[package]] @@ -1129,9 +1175,9 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "wrapt" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/98/97/06afe62762c9a8a86af0cfb7bfdab22a43ad17138b07af5b1a58442690a2/deprecated-1.2.18.tar.gz", hash = "sha256:422b6f6d859da6f2ef57857761bfb392480502a64c3028ca9bbe86085d72115d", size = 2928744 } +sdist = { url = "https://files.pythonhosted.org/packages/98/97/06afe62762c9a8a86af0cfb7bfdab22a43ad17138b07af5b1a58442690a2/deprecated-1.2.18.tar.gz", hash = "sha256:422b6f6d859da6f2ef57857761bfb392480502a64c3028ca9bbe86085d72115d", size = 2928744, upload-time = "2025-01-27T10:46:25.7Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/6e/c6/ac0b6c1e2d138f1002bcf799d330bd6d85084fece321e662a14223794041/Deprecated-1.2.18-py2.py3-none-any.whl", hash = "sha256:bd5011788200372a32418f888e326a09ff80d0214bd961147cfed01b5c018eec", size = 9998 }, + { url = "https://files.pythonhosted.org/packages/6e/c6/ac0b6c1e2d138f1002bcf799d330bd6d85084fece321e662a14223794041/Deprecated-1.2.18-py2.py3-none-any.whl", hash = "sha256:bd5011788200372a32418f888e326a09ff80d0214bd961147cfed01b5c018eec", size = 9998, upload-time = "2025-01-27T10:46:09.186Z" }, ] [[package]] @@ -1141,14 +1187,13 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "packaging" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/5a/d3/8ae2869247df154b64c1884d7346d412fed0c49df84db635aab2d1c40e62/deprecation-2.1.0.tar.gz", hash = "sha256:72b3bde64e5d778694b0cf68178aed03d15e15477116add3fb773e581f9518ff", size = 173788 } +sdist = { url = "https://files.pythonhosted.org/packages/5a/d3/8ae2869247df154b64c1884d7346d412fed0c49df84db635aab2d1c40e62/deprecation-2.1.0.tar.gz", hash = "sha256:72b3bde64e5d778694b0cf68178aed03d15e15477116add3fb773e581f9518ff", size = 173788, upload-time = "2020-04-20T14:23:38.738Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/02/c3/253a89ee03fc9b9682f1541728eb66db7db22148cd94f89ab22528cd1e1b/deprecation-2.1.0-py2.py3-none-any.whl", hash = "sha256:a10811591210e1fb0e768a8c25517cabeabcba6f0bf96564f8ff45189f90b14a", size = 11178 }, + { url = "https://files.pythonhosted.org/packages/02/c3/253a89ee03fc9b9682f1541728eb66db7db22148cd94f89ab22528cd1e1b/deprecation-2.1.0-py2.py3-none-any.whl", hash = "sha256:a10811591210e1fb0e768a8c25517cabeabcba6f0bf96564f8ff45189f90b14a", size = 11178, upload-time = "2020-04-20T14:23:36.581Z" }, ] [[package]] name = "dify-api" -version = "1.2.0" source = { virtual = "." } dependencies = [ { name = "authlib" }, @@ -1183,7 +1228,6 @@ dependencies = [ { name = "mailchimp-transactional" }, { name = "markdown" }, { name = "numpy" }, - { name = "oci" }, { name = "openai" }, { name = "openpyxl" }, { name = "opentelemetry-api" }, @@ -1203,7 +1247,6 @@ dependencies = [ { name = "opentelemetry-util-http" }, { name = "opik" }, { name = "pandas", extra = ["excel", "output-formatting", "performance"] }, - { name = "pandas-stubs" }, { name = "pandoc" }, { name = "psycogreen" }, { name = "psycopg2-binary" }, @@ -1223,29 +1266,33 @@ dependencies = [ { name = "sqlalchemy" }, { name = "starlette" }, { name = "tiktoken" }, - { name = "tokenizers" }, { name = "transformers" }, { name = "unstructured", extra = ["docx", "epub", "md", "ppt", "pptx"] }, - { name = "validators" }, + { name = "weave" }, + { name = "webvtt-py" }, { name = "yarl" }, ] [package.dev-dependencies] dev = [ + { name = "boto3-stubs" }, { name = "coverage" }, { name = "dotenv-linter" }, { name = "faker" }, { name = "lxml-stubs" }, { name = "mypy" }, + { name = "pandas-stubs" }, { name = "pytest" }, { name = "pytest-benchmark" }, { name = "pytest-cov" }, { name = "pytest-env" }, { name = "pytest-mock" }, { name = "ruff" }, + { name = "scipy-stubs" }, { name = "types-aiofiles" }, { name = "types-beautifulsoup4" }, { name = "types-cachetools" }, + { name = "types-cffi" }, { name = "types-colorama" }, { name = "types-defusedxml" }, { name = "types-deprecated" }, @@ -1255,6 +1302,8 @@ dev = [ { name = "types-gevent" }, { name = "types-greenlet" }, { name = "types-html5lib" }, + { name = "types-jmespath" }, + { name = "types-jsonschema" }, { name = "types-markdown" }, { name = "types-oauthlib" }, { name = "types-objgraph" }, @@ -1266,12 +1315,14 @@ dev = [ { name = "types-psycopg2" }, { name = "types-pygments" }, { name = "types-pymysql" }, + { name = "types-pyopenssl" }, { name = "types-python-dateutil" }, { name = "types-pywin32" }, { name = "types-pyyaml" }, { name = "types-regex" }, { name = "types-requests" }, { name = "types-requests-oauthlib" }, + { name = "types-setuptools" }, { name = "types-shapely" }, { name = "types-simplejson" }, { name = "types-six" }, @@ -1326,11 +1377,11 @@ requires-dist = [ { name = "boto3", specifier = "==1.35.99" }, { name = "bs4", specifier = "~=0.0.1" }, { name = "cachetools", specifier = "~=5.3.0" }, - { name = "celery", specifier = "~=5.4.0" }, + { name = "celery", specifier = "~=5.5.2" }, { name = "chardet", specifier = "~=5.1.0" }, { name = "flask", specifier = "~=3.1.0" }, { name = "flask-compress", specifier = "~=1.17" }, - { name = "flask-cors", specifier = "~=4.0.0" }, + { name = "flask-cors", specifier = "~=6.0.0" }, { name = "flask-login", specifier = "~=0.6.3" }, { name = "flask-migrate", specifier = "~=4.0.7" }, { name = "flask-restful", specifier = "~=0.3.10" }, @@ -1352,7 +1403,6 @@ requires-dist = [ { name = "mailchimp-transactional", specifier = "~=1.0.50" }, { name = "markdown", specifier = "~=3.5.1" }, { name = "numpy", specifier = "~=1.26.4" }, - { name = "oci", specifier = "~=2.135.1" }, { name = "openai", specifier = "~=1.61.0" }, { name = "openpyxl", specifier = "~=3.1.5" }, { name = "opentelemetry-api", specifier = "==1.27.0" }, @@ -1370,51 +1420,54 @@ requires-dist = [ { name = "opentelemetry-sdk", specifier = "==1.27.0" }, { name = "opentelemetry-semantic-conventions", specifier = "==0.48b0" }, { name = "opentelemetry-util-http", specifier = "==0.48b0" }, - { name = "opik", specifier = "~=1.3.4" }, + { name = "opik", specifier = "~=1.7.25" }, { name = "pandas", extras = ["excel", "output-formatting", "performance"], specifier = "~=2.2.2" }, - { name = "pandas-stubs", specifier = "~=2.2.3.241009" }, { name = "pandoc", specifier = "~=2.4" }, { name = "psycogreen", specifier = "~=1.0.2" }, { name = "psycopg2-binary", specifier = "~=2.9.6" }, { name = "pycryptodome", specifier = "==3.19.1" }, - { name = "pydantic", specifier = "~=2.9.2" }, - { name = "pydantic-extra-types", specifier = "~=2.9.0" }, - { name = "pydantic-settings", specifier = "~=2.6.0" }, + { name = "pydantic", specifier = "~=2.11.4" }, + { name = "pydantic-extra-types", specifier = "~=2.10.3" }, + { name = "pydantic-settings", specifier = "~=2.9.1" }, { name = "pyjwt", specifier = "~=2.8.0" }, - { name = "pypdfium2", specifier = "~=4.30.0" }, + { name = "pypdfium2", specifier = "==4.30.0" }, { name = "python-docx", specifier = "~=1.1.0" }, { name = "python-dotenv", specifier = "==1.0.1" }, { name = "pyyaml", specifier = "~=6.0.1" }, - { name = "readabilipy", specifier = "==0.2.0" }, - { name = "redis", extras = ["hiredis"], specifier = "~=5.0.3" }, - { name = "resend", specifier = "~=0.7.0" }, - { name = "sentry-sdk", extras = ["flask"], specifier = "~=1.44.1" }, + { name = "readabilipy", specifier = "~=0.3.0" }, + { name = "redis", extras = ["hiredis"], specifier = "~=6.1.0" }, + { name = "resend", specifier = "~=2.9.0" }, + { name = "sentry-sdk", extras = ["flask"], specifier = "~=2.28.0" }, { name = "sqlalchemy", specifier = "~=2.0.29" }, { name = "starlette", specifier = "==0.41.0" }, - { name = "tiktoken", specifier = "~=0.8.0" }, - { name = "tokenizers", specifier = "~=0.15.0" }, - { name = "transformers", specifier = "~=4.35.0" }, + { name = "tiktoken", specifier = "~=0.9.0" }, + { name = "transformers", specifier = "~=4.51.0" }, { name = "unstructured", extras = ["docx", "epub", "md", "ppt", "pptx"], specifier = "~=0.16.1" }, - { name = "validators", specifier = "==0.21.0" }, + { name = "weave", specifier = "~=0.51.0" }, + { name = "webvtt-py", specifier = "~=0.5.1" }, { name = "yarl", specifier = "~=1.18.3" }, ] [package.metadata.requires-dev] dev = [ + { name = "boto3-stubs", specifier = ">=1.38.20" }, { name = "coverage", specifier = "~=7.2.4" }, { name = "dotenv-linter", specifier = "~=0.5.0" }, { name = "faker", specifier = "~=32.1.0" }, { name = "lxml-stubs", specifier = "~=0.5.1" }, - { name = "mypy", specifier = "~=1.15.0" }, + { name = "mypy", specifier = "~=1.16.0" }, + { name = "pandas-stubs", specifier = "~=2.2.3" }, { name = "pytest", specifier = "~=8.3.2" }, { name = "pytest-benchmark", specifier = "~=4.0.0" }, { name = "pytest-cov", specifier = "~=4.1.0" }, { name = "pytest-env", specifier = "~=1.1.3" }, { name = "pytest-mock", specifier = "~=3.14.0" }, { name = "ruff", specifier = "~=0.11.5" }, + { name = "scipy-stubs", specifier = ">=1.15.3.0" }, { name = "types-aiofiles", specifier = "~=24.1.0" }, { name = "types-beautifulsoup4", specifier = "~=4.12.0" }, { name = "types-cachetools", specifier = "~=5.5.0" }, + { name = "types-cffi", specifier = ">=1.17.0" }, { name = "types-colorama", specifier = "~=0.4.15" }, { name = "types-defusedxml", specifier = "~=0.7.0" }, { name = "types-deprecated", specifier = "~=1.2.15" }, @@ -1424,6 +1477,8 @@ dev = [ { name = "types-gevent", specifier = "~=24.11.0" }, { name = "types-greenlet", specifier = "~=3.1.0" }, { name = "types-html5lib", specifier = "~=1.1.11" }, + { name = "types-jmespath", specifier = ">=1.0.2.20240106" }, + { name = "types-jsonschema", specifier = "~=4.23.0" }, { name = "types-markdown", specifier = "~=3.7.0" }, { name = "types-oauthlib", specifier = "~=3.2.0" }, { name = "types-objgraph", specifier = "~=3.6.0" }, @@ -1435,18 +1490,20 @@ dev = [ { name = "types-psycopg2", specifier = "~=2.9.21" }, { name = "types-pygments", specifier = "~=2.19.0" }, { name = "types-pymysql", specifier = "~=1.1.0" }, + { name = "types-pyopenssl", specifier = ">=24.1.0" }, { name = "types-python-dateutil", specifier = "~=2.9.0" }, { name = "types-pywin32", specifier = "~=310.0.0" }, { name = "types-pyyaml", specifier = "~=6.0.12" }, { name = "types-regex", specifier = "~=2024.11.6" }, { name = "types-requests", specifier = "~=2.32.0" }, { name = "types-requests-oauthlib", specifier = "~=2.0.0" }, + { name = "types-setuptools", specifier = ">=80.9.0" }, { name = "types-shapely", specifier = "~=2.0.0" }, - { name = "types-simplejson", specifier = "~=3.20.0" }, - { name = "types-six", specifier = "~=1.17.0" }, - { name = "types-tensorflow", specifier = "~=2.18.0" }, - { name = "types-tqdm", specifier = "~=4.67.0" }, - { name = "types-ujson", specifier = "~=5.10.0" }, + { name = "types-simplejson", specifier = ">=3.20.0" }, + { name = "types-six", specifier = ">=1.17.0" }, + { name = "types-tensorflow", specifier = ">=2.18.0" }, + { name = "types-tqdm", specifier = ">=4.67.0" }, + { name = "types-ujson", specifier = ">=5.10.0" }, ] storage = [ { name = "azure-storage-blob", specifier = "==12.13.0" }, @@ -1477,32 +1534,53 @@ vdb = [ { name = "pymilvus", specifier = "~=2.5.0" }, { name = "pymochow", specifier = "==1.3.1" }, { name = "pyobvector", specifier = "~=0.1.6" }, - { name = "qdrant-client", specifier = "==1.7.3" }, + { name = "qdrant-client", specifier = "==1.9.0" }, { name = "tablestore", specifier = "==6.1.0" }, { name = "tcvectordb", specifier = "~=1.6.4" }, { name = "tidb-vector", specifier = "==0.0.9" }, { name = "upstash-vector", specifier = "==0.6.0" }, - { name = "volcengine-compat", specifier = "~=1.0.156" }, - { name = "weaviate-client", specifier = "~=3.21.0" }, + { name = "volcengine-compat", specifier = "~=1.0.0" }, + { name = "weaviate-client", specifier = "~=3.24.0" }, { name = "xinference-client", specifier = "~=1.2.2" }, ] +[[package]] +name = "diskcache" +version = "5.6.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/3f/21/1c1ffc1a039ddcc459db43cc108658f32c57d271d7289a2794e401d0fdb6/diskcache-5.6.3.tar.gz", hash = "sha256:2c3a3fa2743d8535d832ec61c2054a1641f41775aa7c556758a109941e33e4fc", size = 67916, upload-time = "2023-08-31T06:12:00.316Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3f/27/4570e78fc0bf5ea0ca45eb1de3818a23787af9b390c0b0a0033a1b8236f9/diskcache-5.6.3-py3-none-any.whl", hash = "sha256:5e31b2d5fbad117cc363ebaf6b689474db18a1f6438bc82358b024abd4c2ca19", size = 45550, upload-time = "2023-08-31T06:11:58.822Z" }, +] + [[package]] name = "distro" version = "1.9.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/fc/f8/98eea607f65de6527f8a2e8885fc8015d3e6f5775df186e443e0964a11c3/distro-1.9.0.tar.gz", hash = "sha256:2fa77c6fd8940f116ee1d6b94a2f90b13b5ea8d019b98bc8bafdcabcdd9bdbed", size = 60722 } +sdist = { url = "https://files.pythonhosted.org/packages/fc/f8/98eea607f65de6527f8a2e8885fc8015d3e6f5775df186e443e0964a11c3/distro-1.9.0.tar.gz", hash = "sha256:2fa77c6fd8940f116ee1d6b94a2f90b13b5ea8d019b98bc8bafdcabcdd9bdbed", size = 60722, upload-time = "2023-12-24T09:54:32.31Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/12/b3/231ffd4ab1fc9d679809f356cebee130ac7daa00d6d6f3206dd4fd137e9e/distro-1.9.0-py3-none-any.whl", hash = "sha256:7bffd925d65168f85027d8da9af6bddab658135b840670a223589bc0c8ef02b2", size = 20277 }, + { url = "https://files.pythonhosted.org/packages/12/b3/231ffd4ab1fc9d679809f356cebee130ac7daa00d6d6f3206dd4fd137e9e/distro-1.9.0-py3-none-any.whl", hash = "sha256:7bffd925d65168f85027d8da9af6bddab658135b840670a223589bc0c8ef02b2", size = 20277, upload-time = "2023-12-24T09:54:30.421Z" }, +] + +[[package]] +name = "docker-pycreds" +version = "0.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "six" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c5/e6/d1f6c00b7221e2d7c4b470132c931325c8b22c51ca62417e300f5ce16009/docker-pycreds-0.4.0.tar.gz", hash = "sha256:6ce3270bcaf404cc4c3e27e4b6c70d3521deae82fb508767870fdbf772d584d4", size = 8754, upload-time = "2018-11-29T03:26:50.996Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f5/e8/f6bd1eee09314e7e6dee49cbe2c5e22314ccdb38db16c9fc72d2fa80d054/docker_pycreds-0.4.0-py2.py3-none-any.whl", hash = "sha256:7266112468627868005106ec19cd0d722702d2b7d5912a28e19b826c3d37af49", size = 8982, upload-time = "2018-11-29T03:26:49.575Z" }, ] [[package]] name = "docstring-parser" version = "0.16" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/08/12/9c22a58c0b1e29271051222d8906257616da84135af9ed167c9e28f85cb3/docstring_parser-0.16.tar.gz", hash = "sha256:538beabd0af1e2db0146b6bd3caa526c35a34d61af9fd2887f3a8a27a739aa6e", size = 26565 } +sdist = { url = "https://files.pythonhosted.org/packages/08/12/9c22a58c0b1e29271051222d8906257616da84135af9ed167c9e28f85cb3/docstring_parser-0.16.tar.gz", hash = "sha256:538beabd0af1e2db0146b6bd3caa526c35a34d61af9fd2887f3a8a27a739aa6e", size = 26565, upload-time = "2024-03-15T10:39:44.419Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d5/7c/e9fcff7623954d86bdc17782036cbf715ecab1bec4847c008557affe1ca8/docstring_parser-0.16-py3-none-any.whl", hash = "sha256:bf0a1387354d3691d102edef7ec124f219ef639982d096e26e3b60aeffa90637", size = 36533 }, + { url = "https://files.pythonhosted.org/packages/d5/7c/e9fcff7623954d86bdc17782036cbf715ecab1bec4847c008557affe1ca8/docstring_parser-0.16-py3-none-any.whl", hash = "sha256:bf0a1387354d3691d102edef7ec124f219ef639982d096e26e3b60aeffa90637", size = 36533, upload-time = "2024-03-15T10:39:41.527Z" }, ] [[package]] @@ -1516,18 +1594,18 @@ dependencies = [ { name = "ply" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ef/fe/77e184ccc312f6263cbcc48a9579eec99f5c7ff72a9b1bd7812cafc22bbb/dotenv_linter-0.5.0.tar.gz", hash = "sha256:4862a8393e5ecdfb32982f1b32dbc006fff969a7b3c8608ba7db536108beeaea", size = 15346 } +sdist = { url = "https://files.pythonhosted.org/packages/ef/fe/77e184ccc312f6263cbcc48a9579eec99f5c7ff72a9b1bd7812cafc22bbb/dotenv_linter-0.5.0.tar.gz", hash = "sha256:4862a8393e5ecdfb32982f1b32dbc006fff969a7b3c8608ba7db536108beeaea", size = 15346, upload-time = "2024-03-13T11:52:10.52Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f0/01/62ed4374340e6cf17c5084828974d96db8085e4018439ac41dc3cbbbcab3/dotenv_linter-0.5.0-py3-none-any.whl", hash = "sha256:fd01cca7f2140cb1710f49cbc1bf0e62397a75a6f0522d26a8b9b2331143c8bd", size = 21770 }, + { url = "https://files.pythonhosted.org/packages/f0/01/62ed4374340e6cf17c5084828974d96db8085e4018439ac41dc3cbbbcab3/dotenv_linter-0.5.0-py3-none-any.whl", hash = "sha256:fd01cca7f2140cb1710f49cbc1bf0e62397a75a6f0522d26a8b9b2331143c8bd", size = 21770, upload-time = "2024-03-13T11:52:08.607Z" }, ] [[package]] name = "durationpy" -version = "0.9" +version = "0.10" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/31/e9/f49c4e7fccb77fa5c43c2480e09a857a78b41e7331a75e128ed5df45c56b/durationpy-0.9.tar.gz", hash = "sha256:fd3feb0a69a0057d582ef643c355c40d2fa1c942191f914d12203b1a01ac722a", size = 3186 } +sdist = { url = "https://files.pythonhosted.org/packages/9d/a4/e44218c2b394e31a6dd0d6b095c4e1f32d0be54c2a4b250032d717647bab/durationpy-0.10.tar.gz", hash = "sha256:1fa6893409a6e739c9c72334fc65cca1f355dbdd93405d30f726deb5bde42fba", size = 3335, upload-time = "2025-05-17T13:52:37.26Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/4c/a3/ac312faeceffd2d8f86bc6dcb5c401188ba5a01bc88e69bed97578a0dfcd/durationpy-0.9-py3-none-any.whl", hash = "sha256:e65359a7af5cedad07fb77a2dd3f390f8eb0b74cb845589fa6c057086834dd38", size = 3461 }, + { url = "https://files.pythonhosted.org/packages/b0/0d/9feae160378a3553fa9a339b0e9c1a048e147a4127210e286ef18b730f03/durationpy-0.10-py3-none-any.whl", hash = "sha256:3b41e1b601234296b4fb368338fdcd3e13e0b4fb5b67345948f4f2bf9868b286", size = 3922, upload-time = "2025-05-17T13:52:36.463Z" }, ] [[package]] @@ -1538,9 +1616,9 @@ dependencies = [ { name = "certifi" }, { name = "urllib3" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/6a/54/d498a766ac8fa475f931da85a154666cc81a70f8eb4a780bc8e4e934e9ac/elastic_transport-8.17.1.tar.gz", hash = "sha256:5edef32ac864dca8e2f0a613ef63491ee8d6b8cfb52881fa7313ba9290cac6d2", size = 73425 } +sdist = { url = "https://files.pythonhosted.org/packages/6a/54/d498a766ac8fa475f931da85a154666cc81a70f8eb4a780bc8e4e934e9ac/elastic_transport-8.17.1.tar.gz", hash = "sha256:5edef32ac864dca8e2f0a613ef63491ee8d6b8cfb52881fa7313ba9290cac6d2", size = 73425, upload-time = "2025-03-13T07:28:30.776Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/cf/cd/b71d5bc74cde7fc6fd9b2ff9389890f45d9762cbbbf81dc5e51fd7588c4a/elastic_transport-8.17.1-py3-none-any.whl", hash = "sha256:192718f498f1d10c5e9aa8b9cf32aed405e469a7f0e9d6a8923431dbb2c59fb8", size = 64969 }, + { url = "https://files.pythonhosted.org/packages/cf/cd/b71d5bc74cde7fc6fd9b2ff9389890f45d9762cbbbf81dc5e51fd7588c4a/elastic_transport-8.17.1-py3-none-any.whl", hash = "sha256:192718f498f1d10c5e9aa8b9cf32aed405e469a7f0e9d6a8923431dbb2c59fb8", size = 64969, upload-time = "2025-03-13T07:28:29.031Z" }, ] [[package]] @@ -1550,27 +1628,27 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "elastic-transport" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/36/63/8dc82cbf1bfbca2a2af8eeaa4a7eccc2cf7a87bf217130f6bc66d33b4d8f/elasticsearch-8.14.0.tar.gz", hash = "sha256:aa2490029dd96f4015b333c1827aa21fd6c0a4d223b00dfb0fe933b8d09a511b", size = 382506 } +sdist = { url = "https://files.pythonhosted.org/packages/36/63/8dc82cbf1bfbca2a2af8eeaa4a7eccc2cf7a87bf217130f6bc66d33b4d8f/elasticsearch-8.14.0.tar.gz", hash = "sha256:aa2490029dd96f4015b333c1827aa21fd6c0a4d223b00dfb0fe933b8d09a511b", size = 382506, upload-time = "2024-06-06T13:31:10.205Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a2/09/c9dec8bd95bff6aaa8fe29a834257a6606608d0b2ed9932a1857683f736f/elasticsearch-8.14.0-py3-none-any.whl", hash = "sha256:cef8ef70a81af027f3da74a4f7d9296b390c636903088439087b8262a468c130", size = 480236 }, + { url = "https://files.pythonhosted.org/packages/a2/09/c9dec8bd95bff6aaa8fe29a834257a6606608d0b2ed9932a1857683f736f/elasticsearch-8.14.0-py3-none-any.whl", hash = "sha256:cef8ef70a81af027f3da74a4f7d9296b390c636903088439087b8262a468c130", size = 480236, upload-time = "2024-06-06T13:31:00.987Z" }, ] [[package]] name = "emoji" version = "2.14.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/cb/7d/01cddcbb6f5cc0ba72e00ddf9b1fa206c802d557fd0a20b18e130edf1336/emoji-2.14.1.tar.gz", hash = "sha256:f8c50043d79a2c1410ebfae833ae1868d5941a67a6cd4d18377e2eb0bd79346b", size = 597182 } +sdist = { url = "https://files.pythonhosted.org/packages/cb/7d/01cddcbb6f5cc0ba72e00ddf9b1fa206c802d557fd0a20b18e130edf1336/emoji-2.14.1.tar.gz", hash = "sha256:f8c50043d79a2c1410ebfae833ae1868d5941a67a6cd4d18377e2eb0bd79346b", size = 597182, upload-time = "2025-01-16T06:31:24.983Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/91/db/a0335710caaa6d0aebdaa65ad4df789c15d89b7babd9a30277838a7d9aac/emoji-2.14.1-py3-none-any.whl", hash = "sha256:35a8a486c1460addb1499e3bf7929d3889b2e2841a57401903699fef595e942b", size = 590617 }, + { url = "https://files.pythonhosted.org/packages/91/db/a0335710caaa6d0aebdaa65ad4df789c15d89b7babd9a30277838a7d9aac/emoji-2.14.1-py3-none-any.whl", hash = "sha256:35a8a486c1460addb1499e3bf7929d3889b2e2841a57401903699fef595e942b", size = 590617, upload-time = "2025-01-16T06:31:23.526Z" }, ] [[package]] name = "enum34" version = "1.1.10" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/11/c4/2da1f4952ba476677a42f25cd32ab8aaf0e1c0d0e00b89822b835c7e654c/enum34-1.1.10.tar.gz", hash = "sha256:cce6a7477ed816bd2542d03d53db9f0db935dd013b70f336a95c73979289f248", size = 28187 } +sdist = { url = "https://files.pythonhosted.org/packages/11/c4/2da1f4952ba476677a42f25cd32ab8aaf0e1c0d0e00b89822b835c7e654c/enum34-1.1.10.tar.gz", hash = "sha256:cce6a7477ed816bd2542d03d53db9f0db935dd013b70f336a95c73979289f248", size = 28187, upload-time = "2020-03-10T17:48:00.865Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/63/f6/ccb1c83687756aeabbf3ca0f213508fcfb03883ff200d201b3a4c60cedcc/enum34-1.1.10-py3-none-any.whl", hash = "sha256:c3858660960c984d6ab0ebad691265180da2b43f07e061c0f8dca9ef3cffd328", size = 11224 }, + { url = "https://files.pythonhosted.org/packages/63/f6/ccb1c83687756aeabbf3ca0f213508fcfb03883ff200d201b3a4c60cedcc/enum34-1.1.10-py3-none-any.whl", hash = "sha256:c3858660960c984d6ab0ebad691265180da2b43f07e061c0f8dca9ef3cffd328", size = 11224, upload-time = "2020-03-10T17:48:03.174Z" }, ] [[package]] @@ -1580,24 +1658,15 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pycryptodome" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/f7/af/d83276f9e288bd6a62f44d67ae1eafd401028ba1b2b643ae4014b51da5bd/esdk-obs-python-3.24.6.1.tar.gz", hash = "sha256:c45fed143e99d9256c8560c1d78f651eae0d2e809d16e962f8b286b773c33bf0", size = 85798 } +sdist = { url = "https://files.pythonhosted.org/packages/f7/af/d83276f9e288bd6a62f44d67ae1eafd401028ba1b2b643ae4014b51da5bd/esdk-obs-python-3.24.6.1.tar.gz", hash = "sha256:c45fed143e99d9256c8560c1d78f651eae0d2e809d16e962f8b286b773c33bf0", size = 85798, upload-time = "2024-07-26T13:13:22.467Z" } [[package]] name = "et-xmlfile" version = "2.0.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d3/38/af70d7ab1ae9d4da450eeec1fa3918940a5fafb9055e934af8d6eb0c2313/et_xmlfile-2.0.0.tar.gz", hash = "sha256:dab3f4764309081ce75662649be815c4c9081e88f0837825f90fd28317d4da54", size = 17234 } +sdist = { url = "https://files.pythonhosted.org/packages/d3/38/af70d7ab1ae9d4da450eeec1fa3918940a5fafb9055e934af8d6eb0c2313/et_xmlfile-2.0.0.tar.gz", hash = "sha256:dab3f4764309081ce75662649be815c4c9081e88f0837825f90fd28317d4da54", size = 17234, upload-time = "2024-10-25T17:25:40.039Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c1/8b/5fe2cc11fee489817272089c4203e679c63b570a5aaeb18d852ae3cbba6a/et_xmlfile-2.0.0-py3-none-any.whl", hash = "sha256:7a91720bc756843502c3b7504c77b8fe44217c85c537d85037f0f536151b2caa", size = 18059 }, -] - -[[package]] -name = "eval-type-backport" -version = "0.2.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/30/ea/8b0ac4469d4c347c6a385ff09dc3c048c2d021696664e26c7ee6791631b5/eval_type_backport-0.2.2.tar.gz", hash = "sha256:f0576b4cf01ebb5bd358d02314d31846af5e07678387486e2c798af0e7d849c1", size = 9079 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ce/31/55cd413eaccd39125368be33c46de24a1f639f2e12349b0361b4678f3915/eval_type_backport-0.2.2-py3-none-any.whl", hash = "sha256:cb6ad7c393517f476f96d456d0412ea80f0a8cf96f6892834cd9340149111b0a", size = 5830 }, + { url = "https://files.pythonhosted.org/packages/c1/8b/5fe2cc11fee489817272089c4203e679c63b570a5aaeb18d852ae3cbba6a/et_xmlfile-2.0.0-py3-none-any.whl", hash = "sha256:7a91720bc756843502c3b7504c77b8fe44217c85c537d85037f0f536151b2caa", size = 18059, upload-time = "2024-10-25T17:25:39.051Z" }, ] [[package]] @@ -1608,9 +1677,9 @@ dependencies = [ { name = "python-dateutil" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/1c/2a/dd2c8f55d69013d0eee30ec4c998250fb7da957f5fe860ed077b3df1725b/faker-32.1.0.tar.gz", hash = "sha256:aac536ba04e6b7beb2332c67df78485fc29c1880ff723beac6d1efd45e2f10f5", size = 1850193 } +sdist = { url = "https://files.pythonhosted.org/packages/1c/2a/dd2c8f55d69013d0eee30ec4c998250fb7da957f5fe860ed077b3df1725b/faker-32.1.0.tar.gz", hash = "sha256:aac536ba04e6b7beb2332c67df78485fc29c1880ff723beac6d1efd45e2f10f5", size = 1850193, upload-time = "2024-11-12T22:04:34.812Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/7e/fa/4a82dea32d6262a96e6841cdd4a45c11ac09eecdff018e745565410ac70e/Faker-32.1.0-py3-none-any.whl", hash = "sha256:c77522577863c264bdc9dad3a2a750ad3f7ee43ff8185072e482992288898814", size = 1889123 }, + { url = "https://files.pythonhosted.org/packages/7e/fa/4a82dea32d6262a96e6841cdd4a45c11ac09eecdff018e745565410ac70e/Faker-32.1.0-py3-none-any.whl", hash = "sha256:c77522577863c264bdc9dad3a2a750ad3f7ee43ff8185072e482992288898814", size = 1889123, upload-time = "2024-11-12T22:04:32.298Z" }, ] [[package]] @@ -1622,43 +1691,44 @@ dependencies = [ { name = "starlette" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/f4/55/ae499352d82338331ca1e28c7f4a63bfd09479b16395dce38cf50a39e2c2/fastapi-0.115.12.tar.gz", hash = "sha256:1e2c2a2646905f9e83d32f04a3f86aff4a286669c6c950ca95b5fd68c2602681", size = 295236 } +sdist = { url = "https://files.pythonhosted.org/packages/f4/55/ae499352d82338331ca1e28c7f4a63bfd09479b16395dce38cf50a39e2c2/fastapi-0.115.12.tar.gz", hash = "sha256:1e2c2a2646905f9e83d32f04a3f86aff4a286669c6c950ca95b5fd68c2602681", size = 295236, upload-time = "2025-03-23T22:55:43.822Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/50/b3/b51f09c2ba432a576fe63758bddc81f78f0c6309d9e5c10d194313bf021e/fastapi-0.115.12-py3-none-any.whl", hash = "sha256:e94613d6c05e27be7ffebdd6ea5f388112e5e430c8f7d6494a9d1d88d43e814d", size = 95164 }, + { url = "https://files.pythonhosted.org/packages/50/b3/b51f09c2ba432a576fe63758bddc81f78f0c6309d9e5c10d194313bf021e/fastapi-0.115.12-py3-none-any.whl", hash = "sha256:e94613d6c05e27be7ffebdd6ea5f388112e5e430c8f7d6494a9d1d88d43e814d", size = 95164, upload-time = "2025-03-23T22:55:42.101Z" }, ] [[package]] name = "filelock" version = "3.18.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/0a/10/c23352565a6544bdc5353e0b15fc1c563352101f30e24bf500207a54df9a/filelock-3.18.0.tar.gz", hash = "sha256:adbc88eabb99d2fec8c9c1b229b171f18afa655400173ddc653d5d01501fb9f2", size = 18075 } +sdist = { url = "https://files.pythonhosted.org/packages/0a/10/c23352565a6544bdc5353e0b15fc1c563352101f30e24bf500207a54df9a/filelock-3.18.0.tar.gz", hash = "sha256:adbc88eabb99d2fec8c9c1b229b171f18afa655400173ddc653d5d01501fb9f2", size = 18075, upload-time = "2025-03-14T07:11:40.47Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/4d/36/2a115987e2d8c300a974597416d9de88f2444426de9571f4b59b2cca3acc/filelock-3.18.0-py3-none-any.whl", hash = "sha256:c401f4f8377c4464e6db25fff06205fd89bdd83b65eb0488ed1b160f780e21de", size = 16215 }, + { url = "https://files.pythonhosted.org/packages/4d/36/2a115987e2d8c300a974597416d9de88f2444426de9571f4b59b2cca3acc/filelock-3.18.0-py3-none-any.whl", hash = "sha256:c401f4f8377c4464e6db25fff06205fd89bdd83b65eb0488ed1b160f780e21de", size = 16215, upload-time = "2025-03-14T07:11:39.145Z" }, ] [[package]] name = "filetype" version = "1.2.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/bb/29/745f7d30d47fe0f251d3ad3dc2978a23141917661998763bebb6da007eb1/filetype-1.2.0.tar.gz", hash = "sha256:66b56cd6474bf41d8c54660347d37afcc3f7d1970648de365c102ef77548aadb", size = 998020 } +sdist = { url = "https://files.pythonhosted.org/packages/bb/29/745f7d30d47fe0f251d3ad3dc2978a23141917661998763bebb6da007eb1/filetype-1.2.0.tar.gz", hash = "sha256:66b56cd6474bf41d8c54660347d37afcc3f7d1970648de365c102ef77548aadb", size = 998020, upload-time = "2022-11-02T17:34:04.141Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/18/79/1b8fa1bb3568781e84c9200f951c735f3f157429f44be0495da55894d620/filetype-1.2.0-py2.py3-none-any.whl", hash = "sha256:7ce71b6880181241cf7ac8697a2f1eb6a8bd9b429f7ad6d27b8db9ba5f1c2d25", size = 19970 }, + { url = "https://files.pythonhosted.org/packages/18/79/1b8fa1bb3568781e84c9200f951c735f3f157429f44be0495da55894d620/filetype-1.2.0-py2.py3-none-any.whl", hash = "sha256:7ce71b6880181241cf7ac8697a2f1eb6a8bd9b429f7ad6d27b8db9ba5f1c2d25", size = 19970, upload-time = "2022-11-02T17:34:01.425Z" }, ] [[package]] name = "flask" -version = "3.1.0" +version = "3.1.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "blinker" }, { name = "click" }, { name = "itsdangerous" }, { name = "jinja2" }, + { name = "markupsafe" }, { name = "werkzeug" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/89/50/dff6380f1c7f84135484e176e0cac8690af72fa90e932ad2a0a60e28c69b/flask-3.1.0.tar.gz", hash = "sha256:5f873c5184c897c8d9d1b05df1e3d01b14910ce69607a117bd3277098a5836ac", size = 680824 } +sdist = { url = "https://files.pythonhosted.org/packages/c0/de/e47735752347f4128bcf354e0da07ef311a78244eba9e3dc1d4a5ab21a98/flask-3.1.1.tar.gz", hash = "sha256:284c7b8f2f58cb737f0cf1c30fd7eaf0ccfcde196099d24ecede3fc2005aa59e", size = 753440, upload-time = "2025-05-13T15:01:17.447Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/af/47/93213ee66ef8fae3b93b3e29206f6b251e65c97bd91d8e1c5596ef15af0a/flask-3.1.0-py3-none-any.whl", hash = "sha256:d667207822eb83f1c4b50949b1623c8fc8d51f2341d65f72e1a1815397551136", size = 102979 }, + { url = "https://files.pythonhosted.org/packages/3d/68/9d4508e893976286d2ead7f8f571314af6c2037af34853a30fd769c02e9d/flask-3.1.1-py3-none-any.whl", hash = "sha256:07aae2bb5eaf77993ef57e357491839f5fd9f4dc281593a81a9e4d79a24f295c", size = 103305, upload-time = "2025-05-13T15:01:15.591Z" }, ] [[package]] @@ -1672,21 +1742,22 @@ dependencies = [ { name = "zstandard" }, { name = "zstandard", extra = ["cffi"], marker = "platform_python_implementation == 'PyPy'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/cc/1f/260db5a4517d59bfde7b4a0d71052df68fb84983bda9231100e3b80f5989/flask_compress-1.17.tar.gz", hash = "sha256:1ebb112b129ea7c9e7d6ee6d5cc0d64f226cbc50c4daddf1a58b9bd02253fbd8", size = 15733 } +sdist = { url = "https://files.pythonhosted.org/packages/cc/1f/260db5a4517d59bfde7b4a0d71052df68fb84983bda9231100e3b80f5989/flask_compress-1.17.tar.gz", hash = "sha256:1ebb112b129ea7c9e7d6ee6d5cc0d64f226cbc50c4daddf1a58b9bd02253fbd8", size = 15733, upload-time = "2024-10-14T08:13:33.196Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f7/54/ff08f947d07c0a8a5d8f1c8e57b142c97748ca912b259db6467ab35983cd/Flask_Compress-1.17-py3-none-any.whl", hash = "sha256:415131f197c41109f08e8fdfc3a6628d83d81680fb5ecd0b3a97410e02397b20", size = 8723 }, + { url = "https://files.pythonhosted.org/packages/f7/54/ff08f947d07c0a8a5d8f1c8e57b142c97748ca912b259db6467ab35983cd/Flask_Compress-1.17-py3-none-any.whl", hash = "sha256:415131f197c41109f08e8fdfc3a6628d83d81680fb5ecd0b3a97410e02397b20", size = 8723, upload-time = "2024-10-14T08:13:31.726Z" }, ] [[package]] name = "flask-cors" -version = "4.0.2" +version = "6.0.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "flask" }, + { name = "werkzeug" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/1c/41/89ea5af8b9d647036237c528abb2fdf8bb10b23b3f750e8e2da07873b270/flask_cors-4.0.2.tar.gz", hash = "sha256:493b98e2d1e2f1a4720a7af25693ef2fe32fbafec09a2f72c59f3e475eda61d2", size = 30954 } +sdist = { url = "https://files.pythonhosted.org/packages/20/e7/b3c6afdd984672b55dff07482699c688af6c01bd7fd5dd55f9c9d1a88d1c/flask_cors-6.0.0.tar.gz", hash = "sha256:4592c1570246bf7beee96b74bc0adbbfcb1b0318f6ba05c412e8909eceec3393", size = 11875, upload-time = "2025-05-17T14:35:16.98Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e1/60/e941089faf4f50f2e0231d7f7af69308616a37e99da3ec75df60b8809db7/Flask_Cors-4.0.2-py2.py3-none-any.whl", hash = "sha256:38364faf1a7a5d0a55bd1d2e2f83ee9e359039182f5e6a029557e1f56d92c09a", size = 14467 }, + { url = "https://files.pythonhosted.org/packages/ba/f0/0ee29090016345938f016ee98aa8b5de1c500ee93491dc0c76495848fca1/flask_cors-6.0.0-py3-none-any.whl", hash = "sha256:6332073356452343a8ccddbfec7befdc3fdd040141fe776ec9b94c262f058657", size = 11549, upload-time = "2025-05-17T14:35:15.766Z" }, ] [[package]] @@ -1697,9 +1768,9 @@ dependencies = [ { name = "flask" }, { name = "werkzeug" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c3/6e/2f4e13e373bb49e68c02c51ceadd22d172715a06716f9299d9df01b6ddb2/Flask-Login-0.6.3.tar.gz", hash = "sha256:5e23d14a607ef12806c699590b89d0f0e0d67baeec599d75947bf9c147330333", size = 48834 } +sdist = { url = "https://files.pythonhosted.org/packages/c3/6e/2f4e13e373bb49e68c02c51ceadd22d172715a06716f9299d9df01b6ddb2/Flask-Login-0.6.3.tar.gz", hash = "sha256:5e23d14a607ef12806c699590b89d0f0e0d67baeec599d75947bf9c147330333", size = 48834, upload-time = "2023-10-30T14:53:21.151Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/59/f5/67e9cc5c2036f58115f9fe0f00d203cf6780c3ff8ae0e705e7a9d9e8ff9e/Flask_Login-0.6.3-py3-none-any.whl", hash = "sha256:849b25b82a436bf830a054e74214074af59097171562ab10bfa999e6b78aae5d", size = 17303 }, + { url = "https://files.pythonhosted.org/packages/59/f5/67e9cc5c2036f58115f9fe0f00d203cf6780c3ff8ae0e705e7a9d9e8ff9e/Flask_Login-0.6.3-py3-none-any.whl", hash = "sha256:849b25b82a436bf830a054e74214074af59097171562ab10bfa999e6b78aae5d", size = 17303, upload-time = "2023-10-30T14:53:19.636Z" }, ] [[package]] @@ -1711,9 +1782,9 @@ dependencies = [ { name = "flask" }, { name = "flask-sqlalchemy" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/3b/e2/4008fc0d298d7ce797021b194bbe151d4d12db670691648a226d4fc8aefc/Flask-Migrate-4.0.7.tar.gz", hash = "sha256:dff7dd25113c210b069af280ea713b883f3840c1e3455274745d7355778c8622", size = 21770 } +sdist = { url = "https://files.pythonhosted.org/packages/3b/e2/4008fc0d298d7ce797021b194bbe151d4d12db670691648a226d4fc8aefc/Flask-Migrate-4.0.7.tar.gz", hash = "sha256:dff7dd25113c210b069af280ea713b883f3840c1e3455274745d7355778c8622", size = 21770, upload-time = "2024-03-11T18:43:01.498Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/93/01/587023575286236f95d2ab8a826c320375ed5ea2102bb103ed89704ffa6b/Flask_Migrate-4.0.7-py3-none-any.whl", hash = "sha256:5c532be17e7b43a223b7500d620edae33795df27c75811ddf32560f7d48ec617", size = 21127 }, + { url = "https://files.pythonhosted.org/packages/93/01/587023575286236f95d2ab8a826c320375ed5ea2102bb103ed89704ffa6b/Flask_Migrate-4.0.7-py3-none-any.whl", hash = "sha256:5c532be17e7b43a223b7500d620edae33795df27c75811ddf32560f7d48ec617", size = 21127, upload-time = "2024-03-11T18:42:59.462Z" }, ] [[package]] @@ -1726,9 +1797,9 @@ dependencies = [ { name = "pytz" }, { name = "six" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c0/ce/a0a133db616ea47f78a41e15c4c68b9f08cab3df31eb960f61899200a119/Flask-RESTful-0.3.10.tar.gz", hash = "sha256:fe4af2ef0027df8f9b4f797aba20c5566801b6ade995ac63b588abf1a59cec37", size = 110453 } +sdist = { url = "https://files.pythonhosted.org/packages/c0/ce/a0a133db616ea47f78a41e15c4c68b9f08cab3df31eb960f61899200a119/Flask-RESTful-0.3.10.tar.gz", hash = "sha256:fe4af2ef0027df8f9b4f797aba20c5566801b6ade995ac63b588abf1a59cec37", size = 110453, upload-time = "2023-05-21T03:58:55.781Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d7/7b/f0b45f0df7d2978e5ae51804bb5939b7897b2ace24306009da0cc34d8d1f/Flask_RESTful-0.3.10-py2.py3-none-any.whl", hash = "sha256:1cf93c535172f112e080b0d4503a8d15f93a48c88bdd36dd87269bdaf405051b", size = 26217 }, + { url = "https://files.pythonhosted.org/packages/d7/7b/f0b45f0df7d2978e5ae51804bb5939b7897b2ace24306009da0cc34d8d1f/Flask_RESTful-0.3.10-py2.py3-none-any.whl", hash = "sha256:1cf93c535172f112e080b0d4503a8d15f93a48c88bdd36dd87269bdaf405051b", size = 26217, upload-time = "2023-05-21T03:58:54.004Z" }, ] [[package]] @@ -1739,75 +1810,79 @@ dependencies = [ { name = "flask" }, { name = "sqlalchemy" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/91/53/b0a9fcc1b1297f51e68b69ed3b7c3c40d8c45be1391d77ae198712914392/flask_sqlalchemy-3.1.1.tar.gz", hash = "sha256:e4b68bb881802dda1a7d878b2fc84c06d1ee57fb40b874d3dc97dabfa36b8312", size = 81899 } +sdist = { url = "https://files.pythonhosted.org/packages/91/53/b0a9fcc1b1297f51e68b69ed3b7c3c40d8c45be1391d77ae198712914392/flask_sqlalchemy-3.1.1.tar.gz", hash = "sha256:e4b68bb881802dda1a7d878b2fc84c06d1ee57fb40b874d3dc97dabfa36b8312", size = 81899, upload-time = "2023-09-11T21:42:36.147Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/1d/6a/89963a5c6ecf166e8be29e0d1bf6806051ee8fe6c82e232842e3aeac9204/flask_sqlalchemy-3.1.1-py3-none-any.whl", hash = "sha256:4ba4be7f419dc72f4efd8802d69974803c37259dd42f3913b0dcf75c9447e0a0", size = 25125 }, + { url = "https://files.pythonhosted.org/packages/1d/6a/89963a5c6ecf166e8be29e0d1bf6806051ee8fe6c82e232842e3aeac9204/flask_sqlalchemy-3.1.1-py3-none-any.whl", hash = "sha256:4ba4be7f419dc72f4efd8802d69974803c37259dd42f3913b0dcf75c9447e0a0", size = 25125, upload-time = "2023-09-11T21:42:34.514Z" }, ] [[package]] name = "flatbuffers" version = "25.2.10" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e4/30/eb5dce7994fc71a2f685d98ec33cc660c0a5887db5610137e60d8cbc4489/flatbuffers-25.2.10.tar.gz", hash = "sha256:97e451377a41262f8d9bd4295cc836133415cc03d8cb966410a4af92eb00d26e", size = 22170 } +sdist = { url = "https://files.pythonhosted.org/packages/e4/30/eb5dce7994fc71a2f685d98ec33cc660c0a5887db5610137e60d8cbc4489/flatbuffers-25.2.10.tar.gz", hash = "sha256:97e451377a41262f8d9bd4295cc836133415cc03d8cb966410a4af92eb00d26e", size = 22170, upload-time = "2025-02-11T04:26:46.257Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b8/25/155f9f080d5e4bc0082edfda032ea2bc2b8fab3f4d25d46c1e9dd22a1a89/flatbuffers-25.2.10-py2.py3-none-any.whl", hash = "sha256:ebba5f4d5ea615af3f7fd70fc310636fbb2bbd1f566ac0a23d98dd412de50051", size = 30953 }, + { url = "https://files.pythonhosted.org/packages/b8/25/155f9f080d5e4bc0082edfda032ea2bc2b8fab3f4d25d46c1e9dd22a1a89/flatbuffers-25.2.10-py2.py3-none-any.whl", hash = "sha256:ebba5f4d5ea615af3f7fd70fc310636fbb2bbd1f566ac0a23d98dd412de50051", size = 30953, upload-time = "2025-02-11T04:26:44.484Z" }, ] [[package]] name = "frozenlist" -version = "1.5.0" +version = "1.6.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/8f/ed/0f4cec13a93c02c47ec32d81d11c0c1efbadf4a471e3f3ce7cad366cbbd3/frozenlist-1.5.0.tar.gz", hash = "sha256:81d5af29e61b9c8348e876d442253723928dce6433e0e76cd925cd83f1b4b817", size = 39930 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/79/43/0bed28bf5eb1c9e4301003b74453b8e7aa85fb293b31dde352aac528dafc/frozenlist-1.5.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:fd74520371c3c4175142d02a976aee0b4cb4a7cc912a60586ffd8d5929979b30", size = 94987 }, - { url = "https://files.pythonhosted.org/packages/bb/bf/b74e38f09a246e8abbe1e90eb65787ed745ccab6eaa58b9c9308e052323d/frozenlist-1.5.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2f3f7a0fbc219fb4455264cae4d9f01ad41ae6ee8524500f381de64ffaa077d5", size = 54584 }, - { url = "https://files.pythonhosted.org/packages/2c/31/ab01375682f14f7613a1ade30149f684c84f9b8823a4391ed950c8285656/frozenlist-1.5.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f47c9c9028f55a04ac254346e92977bf0f166c483c74b4232bee19a6697e4778", size = 52499 }, - { url = "https://files.pythonhosted.org/packages/98/a8/d0ac0b9276e1404f58fec3ab6e90a4f76b778a49373ccaf6a563f100dfbc/frozenlist-1.5.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0996c66760924da6e88922756d99b47512a71cfd45215f3570bf1e0b694c206a", size = 276357 }, - { url = "https://files.pythonhosted.org/packages/ad/c9/c7761084fa822f07dac38ac29f841d4587570dd211e2262544aa0b791d21/frozenlist-1.5.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a2fe128eb4edeabe11896cb6af88fca5346059f6c8d807e3b910069f39157869", size = 287516 }, - { url = "https://files.pythonhosted.org/packages/a1/ff/cd7479e703c39df7bdab431798cef89dc75010d8aa0ca2514c5b9321db27/frozenlist-1.5.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1a8ea951bbb6cacd492e3948b8da8c502a3f814f5d20935aae74b5df2b19cf3d", size = 283131 }, - { url = "https://files.pythonhosted.org/packages/59/a0/370941beb47d237eca4fbf27e4e91389fd68699e6f4b0ebcc95da463835b/frozenlist-1.5.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:de537c11e4aa01d37db0d403b57bd6f0546e71a82347a97c6a9f0dcc532b3a45", size = 261320 }, - { url = "https://files.pythonhosted.org/packages/b8/5f/c10123e8d64867bc9b4f2f510a32042a306ff5fcd7e2e09e5ae5100ee333/frozenlist-1.5.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c2623347b933fcb9095841f1cc5d4ff0b278addd743e0e966cb3d460278840d", size = 274877 }, - { url = "https://files.pythonhosted.org/packages/fa/79/38c505601ae29d4348f21706c5d89755ceded02a745016ba2f58bd5f1ea6/frozenlist-1.5.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:cee6798eaf8b1416ef6909b06f7dc04b60755206bddc599f52232606e18179d3", size = 269592 }, - { url = "https://files.pythonhosted.org/packages/19/e2/39f3a53191b8204ba9f0bb574b926b73dd2efba2a2b9d2d730517e8f7622/frozenlist-1.5.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:f5f9da7f5dbc00a604fe74aa02ae7c98bcede8a3b8b9666f9f86fc13993bc71a", size = 265934 }, - { url = "https://files.pythonhosted.org/packages/d5/c9/3075eb7f7f3a91f1a6b00284af4de0a65a9ae47084930916f5528144c9dd/frozenlist-1.5.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:90646abbc7a5d5c7c19461d2e3eeb76eb0b204919e6ece342feb6032c9325ae9", size = 283859 }, - { url = "https://files.pythonhosted.org/packages/05/f5/549f44d314c29408b962fa2b0e69a1a67c59379fb143b92a0a065ffd1f0f/frozenlist-1.5.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:bdac3c7d9b705d253b2ce370fde941836a5f8b3c5c2b8fd70940a3ea3af7f4f2", size = 287560 }, - { url = "https://files.pythonhosted.org/packages/9d/f8/cb09b3c24a3eac02c4c07a9558e11e9e244fb02bf62c85ac2106d1eb0c0b/frozenlist-1.5.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:03d33c2ddbc1816237a67f66336616416e2bbb6beb306e5f890f2eb22b959cdf", size = 277150 }, - { url = "https://files.pythonhosted.org/packages/37/48/38c2db3f54d1501e692d6fe058f45b6ad1b358d82cd19436efab80cfc965/frozenlist-1.5.0-cp311-cp311-win32.whl", hash = "sha256:237f6b23ee0f44066219dae14c70ae38a63f0440ce6750f868ee08775073f942", size = 45244 }, - { url = "https://files.pythonhosted.org/packages/ca/8c/2ddffeb8b60a4bce3b196c32fcc30d8830d4615e7b492ec2071da801b8ad/frozenlist-1.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:0cc974cc93d32c42e7b0f6cf242a6bd941c57c61b618e78b6c0a96cb72788c1d", size = 51634 }, - { url = "https://files.pythonhosted.org/packages/79/73/fa6d1a96ab7fd6e6d1c3500700963eab46813847f01ef0ccbaa726181dd5/frozenlist-1.5.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:31115ba75889723431aa9a4e77d5f398f5cf976eea3bdf61749731f62d4a4a21", size = 94026 }, - { url = "https://files.pythonhosted.org/packages/ab/04/ea8bf62c8868b8eada363f20ff1b647cf2e93377a7b284d36062d21d81d1/frozenlist-1.5.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7437601c4d89d070eac8323f121fcf25f88674627505334654fd027b091db09d", size = 54150 }, - { url = "https://files.pythonhosted.org/packages/d0/9a/8e479b482a6f2070b26bda572c5e6889bb3ba48977e81beea35b5ae13ece/frozenlist-1.5.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7948140d9f8ece1745be806f2bfdf390127cf1a763b925c4a805c603df5e697e", size = 51927 }, - { url = "https://files.pythonhosted.org/packages/e3/12/2aad87deb08a4e7ccfb33600871bbe8f0e08cb6d8224371387f3303654d7/frozenlist-1.5.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:feeb64bc9bcc6b45c6311c9e9b99406660a9c05ca8a5b30d14a78555088b0b3a", size = 282647 }, - { url = "https://files.pythonhosted.org/packages/77/f2/07f06b05d8a427ea0060a9cef6e63405ea9e0d761846b95ef3fb3be57111/frozenlist-1.5.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:683173d371daad49cffb8309779e886e59c2f369430ad28fe715f66d08d4ab1a", size = 289052 }, - { url = "https://files.pythonhosted.org/packages/bd/9f/8bf45a2f1cd4aa401acd271b077989c9267ae8463e7c8b1eb0d3f561b65e/frozenlist-1.5.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7d57d8f702221405a9d9b40f9da8ac2e4a1a8b5285aac6100f3393675f0a85ee", size = 291719 }, - { url = "https://files.pythonhosted.org/packages/41/d1/1f20fd05a6c42d3868709b7604c9f15538a29e4f734c694c6bcfc3d3b935/frozenlist-1.5.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:30c72000fbcc35b129cb09956836c7d7abf78ab5416595e4857d1cae8d6251a6", size = 267433 }, - { url = "https://files.pythonhosted.org/packages/af/f2/64b73a9bb86f5a89fb55450e97cd5c1f84a862d4ff90d9fd1a73ab0f64a5/frozenlist-1.5.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:000a77d6034fbad9b6bb880f7ec073027908f1b40254b5d6f26210d2dab1240e", size = 283591 }, - { url = "https://files.pythonhosted.org/packages/29/e2/ffbb1fae55a791fd6c2938dd9ea779509c977435ba3940b9f2e8dc9d5316/frozenlist-1.5.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5d7f5a50342475962eb18b740f3beecc685a15b52c91f7d975257e13e029eca9", size = 273249 }, - { url = "https://files.pythonhosted.org/packages/2e/6e/008136a30798bb63618a114b9321b5971172a5abddff44a100c7edc5ad4f/frozenlist-1.5.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:87f724d055eb4785d9be84e9ebf0f24e392ddfad00b3fe036e43f489fafc9039", size = 271075 }, - { url = "https://files.pythonhosted.org/packages/ae/f0/4e71e54a026b06724cec9b6c54f0b13a4e9e298cc8db0f82ec70e151f5ce/frozenlist-1.5.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:6e9080bb2fb195a046e5177f10d9d82b8a204c0736a97a153c2466127de87784", size = 285398 }, - { url = "https://files.pythonhosted.org/packages/4d/36/70ec246851478b1c0b59f11ef8ade9c482ff447c1363c2bd5fad45098b12/frozenlist-1.5.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:9b93d7aaa36c966fa42efcaf716e6b3900438632a626fb09c049f6a2f09fc631", size = 294445 }, - { url = "https://files.pythonhosted.org/packages/37/e0/47f87544055b3349b633a03c4d94b405956cf2437f4ab46d0928b74b7526/frozenlist-1.5.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:52ef692a4bc60a6dd57f507429636c2af8b6046db8b31b18dac02cbc8f507f7f", size = 280569 }, - { url = "https://files.pythonhosted.org/packages/f9/7c/490133c160fb6b84ed374c266f42800e33b50c3bbab1652764e6e1fc498a/frozenlist-1.5.0-cp312-cp312-win32.whl", hash = "sha256:29d94c256679247b33a3dc96cce0f93cbc69c23bf75ff715919332fdbb6a32b8", size = 44721 }, - { url = "https://files.pythonhosted.org/packages/b1/56/4e45136ffc6bdbfa68c29ca56ef53783ef4c2fd395f7cbf99a2624aa9aaa/frozenlist-1.5.0-cp312-cp312-win_amd64.whl", hash = "sha256:8969190d709e7c48ea386db202d708eb94bdb29207a1f269bab1196ce0dcca1f", size = 51329 }, - { url = "https://files.pythonhosted.org/packages/c6/c8/a5be5b7550c10858fcf9b0ea054baccab474da77d37f1e828ce043a3a5d4/frozenlist-1.5.0-py3-none-any.whl", hash = "sha256:d994863bba198a4a518b467bb971c56e1db3f180a25c6cf7bb1949c267f748c3", size = 11901 }, +sdist = { url = "https://files.pythonhosted.org/packages/ee/f4/d744cba2da59b5c1d88823cf9e8a6c74e4659e2b27604ed973be2a0bf5ab/frozenlist-1.6.0.tar.gz", hash = "sha256:b99655c32c1c8e06d111e7f41c06c29a5318cb1835df23a45518e02a47c63b68", size = 42831, upload-time = "2025-04-17T22:38:53.099Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/53/b5/bc883b5296ec902115c00be161da93bf661199c465ec4c483feec6ea4c32/frozenlist-1.6.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ae8337990e7a45683548ffb2fee1af2f1ed08169284cd829cdd9a7fa7470530d", size = 160912, upload-time = "2025-04-17T22:36:17.235Z" }, + { url = "https://files.pythonhosted.org/packages/6f/93/51b058b563d0704b39c56baa222828043aafcac17fd3734bec5dbeb619b1/frozenlist-1.6.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:8c952f69dd524558694818a461855f35d36cc7f5c0adddce37e962c85d06eac0", size = 124315, upload-time = "2025-04-17T22:36:18.735Z" }, + { url = "https://files.pythonhosted.org/packages/c9/e0/46cd35219428d350558b874d595e132d1c17a9471a1bd0d01d518a261e7c/frozenlist-1.6.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8f5fef13136c4e2dee91bfb9a44e236fff78fc2cd9f838eddfc470c3d7d90afe", size = 122230, upload-time = "2025-04-17T22:36:20.6Z" }, + { url = "https://files.pythonhosted.org/packages/d1/0f/7ad2ce928ad06d6dd26a61812b959ded573d3e9d0ee6109d96c2be7172e9/frozenlist-1.6.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:716bbba09611b4663ecbb7cd022f640759af8259e12a6ca939c0a6acd49eedba", size = 314842, upload-time = "2025-04-17T22:36:22.088Z" }, + { url = "https://files.pythonhosted.org/packages/34/76/98cbbd8a20a5c3359a2004ae5e5b216af84a150ccbad67c8f8f30fb2ea91/frozenlist-1.6.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:7b8c4dc422c1a3ffc550b465090e53b0bf4839047f3e436a34172ac67c45d595", size = 304919, upload-time = "2025-04-17T22:36:24.247Z" }, + { url = "https://files.pythonhosted.org/packages/9a/fa/258e771ce3a44348c05e6b01dffc2bc67603fba95761458c238cd09a2c77/frozenlist-1.6.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b11534872256e1666116f6587a1592ef395a98b54476addb5e8d352925cb5d4a", size = 324074, upload-time = "2025-04-17T22:36:26.291Z" }, + { url = "https://files.pythonhosted.org/packages/d5/a4/047d861fd8c538210e12b208c0479912273f991356b6bdee7ea8356b07c9/frozenlist-1.6.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1c6eceb88aaf7221f75be6ab498dc622a151f5f88d536661af3ffc486245a626", size = 321292, upload-time = "2025-04-17T22:36:27.909Z" }, + { url = "https://files.pythonhosted.org/packages/c0/25/cfec8af758b4525676cabd36efcaf7102c1348a776c0d1ad046b8a7cdc65/frozenlist-1.6.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:62c828a5b195570eb4b37369fcbbd58e96c905768d53a44d13044355647838ff", size = 301569, upload-time = "2025-04-17T22:36:29.448Z" }, + { url = "https://files.pythonhosted.org/packages/87/2f/0c819372fa9f0c07b153124bf58683b8d0ca7bb73ea5ccde9b9ef1745beb/frozenlist-1.6.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e1c6bd2c6399920c9622362ce95a7d74e7f9af9bfec05fff91b8ce4b9647845a", size = 313625, upload-time = "2025-04-17T22:36:31.55Z" }, + { url = "https://files.pythonhosted.org/packages/50/5f/f0cf8b0fdedffdb76b3745aa13d5dbe404d63493cc211ce8250f2025307f/frozenlist-1.6.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:49ba23817781e22fcbd45fd9ff2b9b8cdb7b16a42a4851ab8025cae7b22e96d0", size = 312523, upload-time = "2025-04-17T22:36:33.078Z" }, + { url = "https://files.pythonhosted.org/packages/e1/6c/38c49108491272d3e84125bbabf2c2d0b304899b52f49f0539deb26ad18d/frozenlist-1.6.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:431ef6937ae0f853143e2ca67d6da76c083e8b1fe3df0e96f3802fd37626e606", size = 322657, upload-time = "2025-04-17T22:36:34.688Z" }, + { url = "https://files.pythonhosted.org/packages/bd/4b/3bd3bad5be06a9d1b04b1c22be80b5fe65b502992d62fab4bdb25d9366ee/frozenlist-1.6.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:9d124b38b3c299ca68433597ee26b7819209cb8a3a9ea761dfe9db3a04bba584", size = 303414, upload-time = "2025-04-17T22:36:36.363Z" }, + { url = "https://files.pythonhosted.org/packages/5b/89/7e225a30bef6e85dbfe22622c24afe932e9444de3b40d58b1ea589a14ef8/frozenlist-1.6.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:118e97556306402e2b010da1ef21ea70cb6d6122e580da64c056b96f524fbd6a", size = 320321, upload-time = "2025-04-17T22:36:38.16Z" }, + { url = "https://files.pythonhosted.org/packages/22/72/7e3acef4dd9e86366cb8f4d8f28e852c2b7e116927e9722b31a6f71ea4b0/frozenlist-1.6.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:fb3b309f1d4086b5533cf7bbcf3f956f0ae6469664522f1bde4feed26fba60f1", size = 323975, upload-time = "2025-04-17T22:36:40.289Z" }, + { url = "https://files.pythonhosted.org/packages/d8/85/e5da03d20507e13c66ce612c9792b76811b7a43e3320cce42d95b85ac755/frozenlist-1.6.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:54dece0d21dce4fdb188a1ffc555926adf1d1c516e493c2914d7c370e454bc9e", size = 316553, upload-time = "2025-04-17T22:36:42.045Z" }, + { url = "https://files.pythonhosted.org/packages/ac/8e/6c609cbd0580ae8a0661c408149f196aade7d325b1ae7adc930501b81acb/frozenlist-1.6.0-cp311-cp311-win32.whl", hash = "sha256:654e4ba1d0b2154ca2f096bed27461cf6160bc7f504a7f9a9ef447c293caf860", size = 115511, upload-time = "2025-04-17T22:36:44.067Z" }, + { url = "https://files.pythonhosted.org/packages/f2/13/a84804cfde6de12d44ed48ecbf777ba62b12ff09e761f76cdd1ff9e14bb1/frozenlist-1.6.0-cp311-cp311-win_amd64.whl", hash = "sha256:3e911391bffdb806001002c1f860787542f45916c3baf764264a52765d5a5603", size = 120863, upload-time = "2025-04-17T22:36:45.465Z" }, + { url = "https://files.pythonhosted.org/packages/9c/8a/289b7d0de2fbac832ea80944d809759976f661557a38bb8e77db5d9f79b7/frozenlist-1.6.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:c5b9e42ace7d95bf41e19b87cec8f262c41d3510d8ad7514ab3862ea2197bfb1", size = 160193, upload-time = "2025-04-17T22:36:47.382Z" }, + { url = "https://files.pythonhosted.org/packages/19/80/2fd17d322aec7f430549f0669f599997174f93ee17929ea5b92781ec902c/frozenlist-1.6.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ca9973735ce9f770d24d5484dcb42f68f135351c2fc81a7a9369e48cf2998a29", size = 123831, upload-time = "2025-04-17T22:36:49.401Z" }, + { url = "https://files.pythonhosted.org/packages/99/06/f5812da431273f78c6543e0b2f7de67dfd65eb0a433978b2c9c63d2205e4/frozenlist-1.6.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6ac40ec76041c67b928ca8aaffba15c2b2ee3f5ae8d0cb0617b5e63ec119ca25", size = 121862, upload-time = "2025-04-17T22:36:51.899Z" }, + { url = "https://files.pythonhosted.org/packages/d0/31/9e61c6b5fc493cf24d54881731204d27105234d09878be1a5983182cc4a5/frozenlist-1.6.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:95b7a8a3180dfb280eb044fdec562f9b461614c0ef21669aea6f1d3dac6ee576", size = 316361, upload-time = "2025-04-17T22:36:53.402Z" }, + { url = "https://files.pythonhosted.org/packages/9d/55/22ca9362d4f0222324981470fd50192be200154d51509ee6eb9baa148e96/frozenlist-1.6.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:c444d824e22da6c9291886d80c7d00c444981a72686e2b59d38b285617cb52c8", size = 307115, upload-time = "2025-04-17T22:36:55.016Z" }, + { url = "https://files.pythonhosted.org/packages/ae/39/4fff42920a57794881e7bb3898dc7f5f539261711ea411b43bba3cde8b79/frozenlist-1.6.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bb52c8166499a8150bfd38478248572c924c003cbb45fe3bcd348e5ac7c000f9", size = 322505, upload-time = "2025-04-17T22:36:57.12Z" }, + { url = "https://files.pythonhosted.org/packages/55/f2/88c41f374c1e4cf0092a5459e5f3d6a1e17ed274c98087a76487783df90c/frozenlist-1.6.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b35298b2db9c2468106278537ee529719228950a5fdda686582f68f247d1dc6e", size = 322666, upload-time = "2025-04-17T22:36:58.735Z" }, + { url = "https://files.pythonhosted.org/packages/75/51/034eeb75afdf3fd03997856195b500722c0b1a50716664cde64e28299c4b/frozenlist-1.6.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d108e2d070034f9d57210f22fefd22ea0d04609fc97c5f7f5a686b3471028590", size = 302119, upload-time = "2025-04-17T22:37:00.512Z" }, + { url = "https://files.pythonhosted.org/packages/2b/a6/564ecde55ee633270a793999ef4fd1d2c2b32b5a7eec903b1012cb7c5143/frozenlist-1.6.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4e1be9111cb6756868ac242b3c2bd1f09d9aea09846e4f5c23715e7afb647103", size = 316226, upload-time = "2025-04-17T22:37:02.102Z" }, + { url = "https://files.pythonhosted.org/packages/f1/c8/6c0682c32377f402b8a6174fb16378b683cf6379ab4d2827c580892ab3c7/frozenlist-1.6.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:94bb451c664415f02f07eef4ece976a2c65dcbab9c2f1705b7031a3a75349d8c", size = 312788, upload-time = "2025-04-17T22:37:03.578Z" }, + { url = "https://files.pythonhosted.org/packages/b6/b8/10fbec38f82c5d163ca1750bfff4ede69713badf236a016781cf1f10a0f0/frozenlist-1.6.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:d1a686d0b0949182b8faddea596f3fc11f44768d1f74d4cad70213b2e139d821", size = 325914, upload-time = "2025-04-17T22:37:05.213Z" }, + { url = "https://files.pythonhosted.org/packages/62/ca/2bf4f3a1bd40cdedd301e6ecfdbb291080d5afc5f9ce350c0739f773d6b9/frozenlist-1.6.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:ea8e59105d802c5a38bdbe7362822c522230b3faba2aa35c0fa1765239b7dd70", size = 305283, upload-time = "2025-04-17T22:37:06.985Z" }, + { url = "https://files.pythonhosted.org/packages/09/64/20cc13ccf94abc2a1f482f74ad210703dc78a590d0b805af1c9aa67f76f9/frozenlist-1.6.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:abc4e880a9b920bc5020bf6a431a6bb40589d9bca3975c980495f63632e8382f", size = 319264, upload-time = "2025-04-17T22:37:08.618Z" }, + { url = "https://files.pythonhosted.org/packages/20/ff/86c6a2bbe98cfc231519f5e6d712a0898488ceac804a917ce014f32e68f6/frozenlist-1.6.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:9a79713adfe28830f27a3c62f6b5406c37376c892b05ae070906f07ae4487046", size = 326482, upload-time = "2025-04-17T22:37:10.196Z" }, + { url = "https://files.pythonhosted.org/packages/2f/da/8e381f66367d79adca245d1d71527aac774e30e291d41ef161ce2d80c38e/frozenlist-1.6.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:9a0318c2068e217a8f5e3b85e35899f5a19e97141a45bb925bb357cfe1daf770", size = 318248, upload-time = "2025-04-17T22:37:12.284Z" }, + { url = "https://files.pythonhosted.org/packages/39/24/1a1976563fb476ab6f0fa9fefaac7616a4361dbe0461324f9fd7bf425dbe/frozenlist-1.6.0-cp312-cp312-win32.whl", hash = "sha256:853ac025092a24bb3bf09ae87f9127de9fe6e0c345614ac92536577cf956dfcc", size = 115161, upload-time = "2025-04-17T22:37:13.902Z" }, + { url = "https://files.pythonhosted.org/packages/80/2e/fb4ed62a65f8cd66044706b1013f0010930d8cbb0729a2219561ea075434/frozenlist-1.6.0-cp312-cp312-win_amd64.whl", hash = "sha256:2bdfe2d7e6c9281c6e55523acd6c2bf77963cb422fdc7d142fb0cb6621b66878", size = 120548, upload-time = "2025-04-17T22:37:15.326Z" }, + { url = "https://files.pythonhosted.org/packages/71/3e/b04a0adda73bd52b390d730071c0d577073d3d26740ee1bad25c3ad0f37b/frozenlist-1.6.0-py3-none-any.whl", hash = "sha256:535eec9987adb04701266b92745d6cdcef2e77669299359c3009c3404dd5d191", size = 12404, upload-time = "2025-04-17T22:38:51.668Z" }, ] [[package]] name = "fsspec" -version = "2025.3.2" +version = "2025.5.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/45/d8/8425e6ba5fcec61a1d16e41b1b71d2bf9344f1fe48012c2b48b9620feae5/fsspec-2025.3.2.tar.gz", hash = "sha256:e52c77ef398680bbd6a98c0e628fbc469491282981209907bbc8aea76a04fdc6", size = 299281 } +sdist = { url = "https://files.pythonhosted.org/packages/00/f7/27f15d41f0ed38e8fcc488584b57e902b331da7f7c6dcda53721b15838fc/fsspec-2025.5.1.tar.gz", hash = "sha256:2e55e47a540b91843b755e83ded97c6e897fa0942b11490113f09e9c443c2475", size = 303033, upload-time = "2025-05-24T12:03:23.792Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/44/4b/e0cfc1a6f17e990f3e64b7d941ddc4acdc7b19d6edd51abf495f32b1a9e4/fsspec-2025.3.2-py3-none-any.whl", hash = "sha256:2daf8dc3d1dfa65b6aa37748d112773a7a08416f6c70d96b264c96476ecaf711", size = 194435 }, + { url = "https://files.pythonhosted.org/packages/bb/61/78c7b3851add1481b048b5fdc29067397a1784e2910592bc81bb3f608635/fsspec-2025.5.1-py3-none-any.whl", hash = "sha256:24d3a2e663d5fc735ab256263c4075f374a174c3410c0b25e5bd1970bceaa462", size = 199052, upload-time = "2025-05-24T12:03:21.66Z" }, ] [[package]] name = "future" version = "1.0.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a7/b2/4140c69c6a66432916b26158687e821ba631a4c9273c474343badf84d3ba/future-1.0.0.tar.gz", hash = "sha256:bd2968309307861edae1458a4f8a4f3598c03be43b97521076aebf5d94c07b05", size = 1228490 } +sdist = { url = "https://files.pythonhosted.org/packages/a7/b2/4140c69c6a66432916b26158687e821ba631a4c9273c474343badf84d3ba/future-1.0.0.tar.gz", hash = "sha256:bd2968309307861edae1458a4f8a4f3598c03be43b97521076aebf5d94c07b05", size = 1228490, upload-time = "2024-02-21T11:52:38.461Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/da/71/ae30dadffc90b9006d77af76b393cb9dfbfc9629f339fc1574a1c52e6806/future-1.0.0-py3-none-any.whl", hash = "sha256:929292d34f5872e70396626ef385ec22355a1fae8ad29e1a734c3e43f9fbc216", size = 491326 }, + { url = "https://files.pythonhosted.org/packages/da/71/ae30dadffc90b9006d77af76b393cb9dfbfc9629f339fc1574a1c52e6806/future-1.0.0-py3-none-any.whl", hash = "sha256:929292d34f5872e70396626ef385ec22355a1fae8ad29e1a734c3e43f9fbc216", size = 491326, upload-time = "2024-02-21T11:52:35.956Z" }, ] [[package]] @@ -1820,46 +1895,70 @@ dependencies = [ { name = "zope-event" }, { name = "zope-interface" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ab/75/a53f1cb732420f5e5d79b2563fc3504d22115e7ecfe7966e5cf9b3582ae7/gevent-24.11.1.tar.gz", hash = "sha256:8bd1419114e9e4a3ed33a5bad766afff9a3cf765cb440a582a1b3a9bc80c1aca", size = 5976624 } +sdist = { url = "https://files.pythonhosted.org/packages/ab/75/a53f1cb732420f5e5d79b2563fc3504d22115e7ecfe7966e5cf9b3582ae7/gevent-24.11.1.tar.gz", hash = "sha256:8bd1419114e9e4a3ed33a5bad766afff9a3cf765cb440a582a1b3a9bc80c1aca", size = 5976624, upload-time = "2024-11-11T15:36:45.991Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ea/fd/86a170f77ef51a15297573c50dbec4cc67ddc98b677cc2d03cc7f2927f4c/gevent-24.11.1-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:351d1c0e4ef2b618ace74c91b9b28b3eaa0dd45141878a964e03c7873af09f62", size = 2951424, upload-time = "2024-11-11T14:32:36.451Z" }, + { url = "https://files.pythonhosted.org/packages/7f/0a/987268c9d446f61883bc627c77c5ed4a97869c0f541f76661a62b2c411f6/gevent-24.11.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b5efe72e99b7243e222ba0c2c2ce9618d7d36644c166d63373af239da1036bab", size = 4878504, upload-time = "2024-11-11T15:20:03.521Z" }, + { url = "https://files.pythonhosted.org/packages/dc/d4/2f77ddd837c0e21b4a4460bcb79318b6754d95ef138b7a29f3221c7e9993/gevent-24.11.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9d3b249e4e1f40c598ab8393fc01ae6a3b4d51fc1adae56d9ba5b315f6b2d758", size = 5007668, upload-time = "2024-11-11T15:21:00.422Z" }, + { url = "https://files.pythonhosted.org/packages/80/a0/829e0399a1f9b84c344b72d2be9aa60fe2a64e993cac221edcc14f069679/gevent-24.11.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81d918e952954675f93fb39001da02113ec4d5f4921bf5a0cc29719af6824e5d", size = 5067055, upload-time = "2024-11-11T15:22:44.279Z" }, + { url = "https://files.pythonhosted.org/packages/1e/67/0e693f9ddb7909c2414f8fcfc2409aa4157884c147bc83dab979e9cf717c/gevent-24.11.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c9c935b83d40c748b6421625465b7308d87c7b3717275acd587eef2bd1c39546", size = 6761883, upload-time = "2024-11-11T14:57:09.359Z" }, + { url = "https://files.pythonhosted.org/packages/fa/b6/b69883fc069d7148dd23c5dda20826044e54e7197f3c8e72b8cc2cd4035a/gevent-24.11.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff96c5739834c9a594db0e12bf59cb3fa0e5102fc7b893972118a3166733d61c", size = 5440802, upload-time = "2024-11-11T15:37:04.983Z" }, + { url = "https://files.pythonhosted.org/packages/32/4e/b00094d995ff01fd88b3cf6b9d1d794f935c31c645c431e65cd82d808c9c/gevent-24.11.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d6c0a065e31ef04658f799215dddae8752d636de2bed61365c358f9c91e7af61", size = 6866992, upload-time = "2024-11-11T15:03:44.208Z" }, + { url = "https://files.pythonhosted.org/packages/37/ed/58dbe9fb09d36f6477ff8db0459ebd3be9a77dc05ae5d96dc91ad657610d/gevent-24.11.1-cp311-cp311-win_amd64.whl", hash = "sha256:97e2f3999a5c0656f42065d02939d64fffaf55861f7d62b0107a08f52c984897", size = 1543736, upload-time = "2024-11-11T15:03:06.121Z" }, + { url = "https://files.pythonhosted.org/packages/dd/32/301676f67ffa996ff1c4175092fb0c48c83271cc95e5c67650b87156b6cf/gevent-24.11.1-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:a3d75fa387b69c751a3d7c5c3ce7092a171555126e136c1d21ecd8b50c7a6e46", size = 2956467, upload-time = "2024-11-11T14:32:33.238Z" }, + { url = "https://files.pythonhosted.org/packages/6b/84/aef1a598123cef2375b6e2bf9d17606b961040f8a10e3dcc3c3dd2a99f05/gevent-24.11.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:beede1d1cff0c6fafae3ab58a0c470d7526196ef4cd6cc18e7769f207f2ea4eb", size = 5136486, upload-time = "2024-11-11T15:20:04.972Z" }, + { url = "https://files.pythonhosted.org/packages/92/7b/04f61187ee1df7a913b3fca63b0a1206c29141ab4d2a57e7645237b6feb5/gevent-24.11.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:85329d556aaedced90a993226d7d1186a539c843100d393f2349b28c55131c85", size = 5299718, upload-time = "2024-11-11T15:21:03.354Z" }, + { url = "https://files.pythonhosted.org/packages/36/2a/ebd12183ac25eece91d084be2111e582b061f4d15ead32239b43ed47e9ba/gevent-24.11.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:816b3883fa6842c1cf9d2786722014a0fd31b6312cca1f749890b9803000bad6", size = 5400118, upload-time = "2024-11-11T15:22:45.897Z" }, + { url = "https://files.pythonhosted.org/packages/ec/c9/f006c0cd59f0720fbb62ee11da0ad4c4c0fd12799afd957dd491137e80d9/gevent-24.11.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b24d800328c39456534e3bc3e1684a28747729082684634789c2f5a8febe7671", size = 6775163, upload-time = "2024-11-11T14:57:11.991Z" }, + { url = "https://files.pythonhosted.org/packages/49/f1/5edf00b674b10d67e3b967c2d46b8a124c2bc8cfd59d4722704392206444/gevent-24.11.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:a5f1701ce0f7832f333dd2faf624484cbac99e60656bfbb72504decd42970f0f", size = 5479886, upload-time = "2024-11-11T15:37:06.558Z" }, + { url = "https://files.pythonhosted.org/packages/22/11/c48e62744a32c0d48984268ae62b99edb81eaf0e03b42de52e2f09855509/gevent-24.11.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:d740206e69dfdfdcd34510c20adcb9777ce2cc18973b3441ab9767cd8948ca8a", size = 6891452, upload-time = "2024-11-11T15:03:46.892Z" }, + { url = "https://files.pythonhosted.org/packages/11/b2/5d20664ef6a077bec9f27f7a7ee761edc64946d0b1e293726a3d074a9a18/gevent-24.11.1-cp312-cp312-win_amd64.whl", hash = "sha256:68bee86b6e1c041a187347ef84cf03a792f0b6c7238378bf6ba4118af11feaae", size = 1541631, upload-time = "2024-11-11T14:55:34.977Z" }, +] + +[[package]] +name = "gitdb" +version = "4.0.12" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "smmap" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/72/94/63b0fc47eb32792c7ba1fe1b694daec9a63620db1e313033d18140c2320a/gitdb-4.0.12.tar.gz", hash = "sha256:5ef71f855d191a3326fcfbc0d5da835f26b13fbcba60c32c21091c349ffdb571", size = 394684, upload-time = "2025-01-02T07:20:46.413Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a0/61/5c78b91c3143ed5c14207f463aecfc8f9dbb5092fb2869baf37c273b2705/gitdb-4.0.12-py3-none-any.whl", hash = "sha256:67073e15955400952c6565cc3e707c554a4eea2e428946f7a4c162fab9bd9bcf", size = 62794, upload-time = "2025-01-02T07:20:43.624Z" }, +] + +[[package]] +name = "gitpython" +version = "3.1.44" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "gitdb" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c0/89/37df0b71473153574a5cdef8f242de422a0f5d26d7a9e231e6f169b4ad14/gitpython-3.1.44.tar.gz", hash = "sha256:c87e30b26253bf5418b01b0660f818967f3c503193838337fe5e573331249269", size = 214196, upload-time = "2025-01-02T07:32:43.59Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ea/fd/86a170f77ef51a15297573c50dbec4cc67ddc98b677cc2d03cc7f2927f4c/gevent-24.11.1-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:351d1c0e4ef2b618ace74c91b9b28b3eaa0dd45141878a964e03c7873af09f62", size = 2951424 }, - { url = "https://files.pythonhosted.org/packages/7f/0a/987268c9d446f61883bc627c77c5ed4a97869c0f541f76661a62b2c411f6/gevent-24.11.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b5efe72e99b7243e222ba0c2c2ce9618d7d36644c166d63373af239da1036bab", size = 4878504 }, - { url = "https://files.pythonhosted.org/packages/dc/d4/2f77ddd837c0e21b4a4460bcb79318b6754d95ef138b7a29f3221c7e9993/gevent-24.11.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9d3b249e4e1f40c598ab8393fc01ae6a3b4d51fc1adae56d9ba5b315f6b2d758", size = 5007668 }, - { url = "https://files.pythonhosted.org/packages/80/a0/829e0399a1f9b84c344b72d2be9aa60fe2a64e993cac221edcc14f069679/gevent-24.11.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81d918e952954675f93fb39001da02113ec4d5f4921bf5a0cc29719af6824e5d", size = 5067055 }, - { url = "https://files.pythonhosted.org/packages/1e/67/0e693f9ddb7909c2414f8fcfc2409aa4157884c147bc83dab979e9cf717c/gevent-24.11.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c9c935b83d40c748b6421625465b7308d87c7b3717275acd587eef2bd1c39546", size = 6761883 }, - { url = "https://files.pythonhosted.org/packages/fa/b6/b69883fc069d7148dd23c5dda20826044e54e7197f3c8e72b8cc2cd4035a/gevent-24.11.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff96c5739834c9a594db0e12bf59cb3fa0e5102fc7b893972118a3166733d61c", size = 5440802 }, - { url = "https://files.pythonhosted.org/packages/32/4e/b00094d995ff01fd88b3cf6b9d1d794f935c31c645c431e65cd82d808c9c/gevent-24.11.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d6c0a065e31ef04658f799215dddae8752d636de2bed61365c358f9c91e7af61", size = 6866992 }, - { url = "https://files.pythonhosted.org/packages/37/ed/58dbe9fb09d36f6477ff8db0459ebd3be9a77dc05ae5d96dc91ad657610d/gevent-24.11.1-cp311-cp311-win_amd64.whl", hash = "sha256:97e2f3999a5c0656f42065d02939d64fffaf55861f7d62b0107a08f52c984897", size = 1543736 }, - { url = "https://files.pythonhosted.org/packages/dd/32/301676f67ffa996ff1c4175092fb0c48c83271cc95e5c67650b87156b6cf/gevent-24.11.1-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:a3d75fa387b69c751a3d7c5c3ce7092a171555126e136c1d21ecd8b50c7a6e46", size = 2956467 }, - { url = "https://files.pythonhosted.org/packages/6b/84/aef1a598123cef2375b6e2bf9d17606b961040f8a10e3dcc3c3dd2a99f05/gevent-24.11.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:beede1d1cff0c6fafae3ab58a0c470d7526196ef4cd6cc18e7769f207f2ea4eb", size = 5136486 }, - { url = "https://files.pythonhosted.org/packages/92/7b/04f61187ee1df7a913b3fca63b0a1206c29141ab4d2a57e7645237b6feb5/gevent-24.11.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:85329d556aaedced90a993226d7d1186a539c843100d393f2349b28c55131c85", size = 5299718 }, - { url = "https://files.pythonhosted.org/packages/36/2a/ebd12183ac25eece91d084be2111e582b061f4d15ead32239b43ed47e9ba/gevent-24.11.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:816b3883fa6842c1cf9d2786722014a0fd31b6312cca1f749890b9803000bad6", size = 5400118 }, - { url = "https://files.pythonhosted.org/packages/ec/c9/f006c0cd59f0720fbb62ee11da0ad4c4c0fd12799afd957dd491137e80d9/gevent-24.11.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b24d800328c39456534e3bc3e1684a28747729082684634789c2f5a8febe7671", size = 6775163 }, - { url = "https://files.pythonhosted.org/packages/49/f1/5edf00b674b10d67e3b967c2d46b8a124c2bc8cfd59d4722704392206444/gevent-24.11.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:a5f1701ce0f7832f333dd2faf624484cbac99e60656bfbb72504decd42970f0f", size = 5479886 }, - { url = "https://files.pythonhosted.org/packages/22/11/c48e62744a32c0d48984268ae62b99edb81eaf0e03b42de52e2f09855509/gevent-24.11.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:d740206e69dfdfdcd34510c20adcb9777ce2cc18973b3441ab9767cd8948ca8a", size = 6891452 }, - { url = "https://files.pythonhosted.org/packages/11/b2/5d20664ef6a077bec9f27f7a7ee761edc64946d0b1e293726a3d074a9a18/gevent-24.11.1-cp312-cp312-win_amd64.whl", hash = "sha256:68bee86b6e1c041a187347ef84cf03a792f0b6c7238378bf6ba4118af11feaae", size = 1541631 }, + { url = "https://files.pythonhosted.org/packages/1d/9a/4114a9057db2f1462d5c8f8390ab7383925fe1ac012eaa42402ad65c2963/GitPython-3.1.44-py3-none-any.whl", hash = "sha256:9e0e10cda9bed1ee64bc9a6de50e7e38a9c9943241cd7f585f6df3ed28011110", size = 207599, upload-time = "2025-01-02T07:32:40.731Z" }, ] [[package]] name = "gmpy2" version = "2.2.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/07/bd/c6c154ce734a3e6187871b323297d8e5f3bdf9feaafc5212381538bc19e4/gmpy2-2.2.1.tar.gz", hash = "sha256:e83e07567441b78cb87544910cb3cc4fe94e7da987e93ef7622e76fb96650432", size = 234228 } +sdist = { url = "https://files.pythonhosted.org/packages/07/bd/c6c154ce734a3e6187871b323297d8e5f3bdf9feaafc5212381538bc19e4/gmpy2-2.2.1.tar.gz", hash = "sha256:e83e07567441b78cb87544910cb3cc4fe94e7da987e93ef7622e76fb96650432", size = 234228, upload-time = "2024-07-21T05:33:00.715Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ac/ec/ab67751ac0c4088ed21cf9a2a7f9966bf702ca8ebfc3204879cf58c90179/gmpy2-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:98e947491c67523d3147a500f377bb64d0b115e4ab8a12d628fb324bb0e142bf", size = 880346 }, - { url = "https://files.pythonhosted.org/packages/97/7c/bdc4a7a2b0e543787a9354e80fdcf846c4e9945685218cef4ca938d25594/gmpy2-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4ccd319a3a87529484167ae1391f937ac4a8724169fd5822bbb541d1eab612b0", size = 694518 }, - { url = "https://files.pythonhosted.org/packages/fc/44/ea903003bb4c3af004912fb0d6488e346bd76968f11a7472a1e60dee7dd7/gmpy2-2.2.1-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:827bcd433e5d62f1b732f45e6949419da4a53915d6c80a3c7a5a03d5a783a03a", size = 1653491 }, - { url = "https://files.pythonhosted.org/packages/c9/70/5bce281b7cd664c04f1c9d47a37087db37b2be887bce738340e912ad86c8/gmpy2-2.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b7131231fc96f57272066295c81cbf11b3233a9471659bca29ddc90a7bde9bfa", size = 1706487 }, - { url = "https://files.pythonhosted.org/packages/2a/52/1f773571f21cf0319fc33218a1b384f29de43053965c05ed32f7e6729115/gmpy2-2.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:1cc6f2bb68ee00c20aae554e111dc781a76140e00c31e4eda5c8f2d4168ed06c", size = 1637415 }, - { url = "https://files.pythonhosted.org/packages/99/4c/390daf67c221b3f4f10b5b7d9293e61e4dbd48956a38947679c5a701af27/gmpy2-2.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ae388fe46e3d20af4675451a4b6c12fc1bb08e6e0e69ee47072638be21bf42d8", size = 1657781 }, - { url = "https://files.pythonhosted.org/packages/61/cd/86e47bccb3636389e29c4654a0e5ac52926d832897f2f64632639b63ffc1/gmpy2-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:8b472ee3c123b77979374da2293ebf2c170b88212e173d64213104956d4678fb", size = 1203346 }, - { url = "https://files.pythonhosted.org/packages/9a/ee/8f9f65e2bac334cfe13b3fc3f8962d5fc2858ebcf4517690d2d24afa6d0e/gmpy2-2.2.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:90d03a1be1b1ad3944013fae5250316c3f4e6aec45ecdf189a5c7422d640004d", size = 885231 }, - { url = "https://files.pythonhosted.org/packages/07/1c/bf29f6bf8acd72c3cf85d04e7db1bb26dd5507ee2387770bb787bc54e2a5/gmpy2-2.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:bd09dd43d199908c1d1d501c5de842b3bf754f99b94af5b5ef0e26e3b716d2d5", size = 696569 }, - { url = "https://files.pythonhosted.org/packages/7c/cc/38d33eadeccd81b604a95b67d43c71b246793b7c441f1d7c3b41978cd1cf/gmpy2-2.2.1-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3232859fda3e96fd1aecd6235ae20476ed4506562bcdef6796a629b78bb96acd", size = 1655776 }, - { url = "https://files.pythonhosted.org/packages/96/8d/d017599d6db8e9b96d6e84ea5102c33525cb71c82876b1813a2ece5d94ec/gmpy2-2.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:30fba6f7cf43fb7f8474216701b5aaddfa5e6a06d560e88a67f814062934e863", size = 1707529 }, - { url = "https://files.pythonhosted.org/packages/d0/93/91b4a0af23ae4216fd7ebcfd955dcbe152c5ef170598aee421310834de0a/gmpy2-2.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:9b33cae533ede8173bc7d4bb855b388c5b636ca9f22a32c949f2eb7e0cc531b2", size = 1634195 }, - { url = "https://files.pythonhosted.org/packages/d7/ba/08ee99f19424cd33d5f0f17b2184e34d2fa886eebafcd3e164ccba15d9f2/gmpy2-2.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:954e7e1936c26e370ca31bbd49729ebeeb2006a8f9866b1e778ebb89add2e941", size = 1656779 }, - { url = "https://files.pythonhosted.org/packages/14/e1/7b32ae2b23c8363d87b7f4bbac9abe9a1f820c2417d2e99ca3b4afd9379b/gmpy2-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:c929870137b20d9c3f7dd97f43615b2d2c1a2470e50bafd9a5eea2e844f462e9", size = 1204668 }, + { url = "https://files.pythonhosted.org/packages/ac/ec/ab67751ac0c4088ed21cf9a2a7f9966bf702ca8ebfc3204879cf58c90179/gmpy2-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:98e947491c67523d3147a500f377bb64d0b115e4ab8a12d628fb324bb0e142bf", size = 880346, upload-time = "2024-07-21T05:31:25.531Z" }, + { url = "https://files.pythonhosted.org/packages/97/7c/bdc4a7a2b0e543787a9354e80fdcf846c4e9945685218cef4ca938d25594/gmpy2-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4ccd319a3a87529484167ae1391f937ac4a8724169fd5822bbb541d1eab612b0", size = 694518, upload-time = "2024-07-21T05:31:27.78Z" }, + { url = "https://files.pythonhosted.org/packages/fc/44/ea903003bb4c3af004912fb0d6488e346bd76968f11a7472a1e60dee7dd7/gmpy2-2.2.1-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:827bcd433e5d62f1b732f45e6949419da4a53915d6c80a3c7a5a03d5a783a03a", size = 1653491, upload-time = "2024-07-21T05:31:29.968Z" }, + { url = "https://files.pythonhosted.org/packages/c9/70/5bce281b7cd664c04f1c9d47a37087db37b2be887bce738340e912ad86c8/gmpy2-2.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b7131231fc96f57272066295c81cbf11b3233a9471659bca29ddc90a7bde9bfa", size = 1706487, upload-time = "2024-07-21T05:31:32.476Z" }, + { url = "https://files.pythonhosted.org/packages/2a/52/1f773571f21cf0319fc33218a1b384f29de43053965c05ed32f7e6729115/gmpy2-2.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:1cc6f2bb68ee00c20aae554e111dc781a76140e00c31e4eda5c8f2d4168ed06c", size = 1637415, upload-time = "2024-07-21T05:31:34.591Z" }, + { url = "https://files.pythonhosted.org/packages/99/4c/390daf67c221b3f4f10b5b7d9293e61e4dbd48956a38947679c5a701af27/gmpy2-2.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ae388fe46e3d20af4675451a4b6c12fc1bb08e6e0e69ee47072638be21bf42d8", size = 1657781, upload-time = "2024-07-21T05:31:36.81Z" }, + { url = "https://files.pythonhosted.org/packages/61/cd/86e47bccb3636389e29c4654a0e5ac52926d832897f2f64632639b63ffc1/gmpy2-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:8b472ee3c123b77979374da2293ebf2c170b88212e173d64213104956d4678fb", size = 1203346, upload-time = "2024-07-21T05:31:39.344Z" }, + { url = "https://files.pythonhosted.org/packages/9a/ee/8f9f65e2bac334cfe13b3fc3f8962d5fc2858ebcf4517690d2d24afa6d0e/gmpy2-2.2.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:90d03a1be1b1ad3944013fae5250316c3f4e6aec45ecdf189a5c7422d640004d", size = 885231, upload-time = "2024-07-21T05:31:41.471Z" }, + { url = "https://files.pythonhosted.org/packages/07/1c/bf29f6bf8acd72c3cf85d04e7db1bb26dd5507ee2387770bb787bc54e2a5/gmpy2-2.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:bd09dd43d199908c1d1d501c5de842b3bf754f99b94af5b5ef0e26e3b716d2d5", size = 696569, upload-time = "2024-07-21T05:31:43.768Z" }, + { url = "https://files.pythonhosted.org/packages/7c/cc/38d33eadeccd81b604a95b67d43c71b246793b7c441f1d7c3b41978cd1cf/gmpy2-2.2.1-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3232859fda3e96fd1aecd6235ae20476ed4506562bcdef6796a629b78bb96acd", size = 1655776, upload-time = "2024-07-21T05:31:46.272Z" }, + { url = "https://files.pythonhosted.org/packages/96/8d/d017599d6db8e9b96d6e84ea5102c33525cb71c82876b1813a2ece5d94ec/gmpy2-2.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:30fba6f7cf43fb7f8474216701b5aaddfa5e6a06d560e88a67f814062934e863", size = 1707529, upload-time = "2024-07-21T05:31:48.732Z" }, + { url = "https://files.pythonhosted.org/packages/d0/93/91b4a0af23ae4216fd7ebcfd955dcbe152c5ef170598aee421310834de0a/gmpy2-2.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:9b33cae533ede8173bc7d4bb855b388c5b636ca9f22a32c949f2eb7e0cc531b2", size = 1634195, upload-time = "2024-07-21T05:31:50.99Z" }, + { url = "https://files.pythonhosted.org/packages/d7/ba/08ee99f19424cd33d5f0f17b2184e34d2fa886eebafcd3e164ccba15d9f2/gmpy2-2.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:954e7e1936c26e370ca31bbd49729ebeeb2006a8f9866b1e778ebb89add2e941", size = 1656779, upload-time = "2024-07-21T05:31:53.657Z" }, + { url = "https://files.pythonhosted.org/packages/14/e1/7b32ae2b23c8363d87b7f4bbac9abe9a1f820c2417d2e99ca3b4afd9379b/gmpy2-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:c929870137b20d9c3f7dd97f43615b2d2c1a2470e50bafd9a5eea2e844f462e9", size = 1204668, upload-time = "2024-07-21T05:31:56.264Z" }, ] [[package]] @@ -1869,9 +1968,9 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "beautifulsoup4" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/89/97/b49c69893cddea912c7a660a4b6102c6b02cd268f8c7162dd70b7c16f753/google-3.0.0.tar.gz", hash = "sha256:143530122ee5130509ad5e989f0512f7cb218b2d4eddbafbad40fd10e8d8ccbe", size = 44978 } +sdist = { url = "https://files.pythonhosted.org/packages/89/97/b49c69893cddea912c7a660a4b6102c6b02cd268f8c7162dd70b7c16f753/google-3.0.0.tar.gz", hash = "sha256:143530122ee5130509ad5e989f0512f7cb218b2d4eddbafbad40fd10e8d8ccbe", size = 44978, upload-time = "2020-07-11T14:50:45.678Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ac/35/17c9141c4ae21e9a29a43acdfd848e3e468a810517f862cad07977bf8fe9/google-3.0.0-py2.py3-none-any.whl", hash = "sha256:889cf695f84e4ae2c55fbc0cfdaf4c1e729417fa52ab1db0485202ba173e4935", size = 45258 }, + { url = "https://files.pythonhosted.org/packages/ac/35/17c9141c4ae21e9a29a43acdfd848e3e468a810517f862cad07977bf8fe9/google-3.0.0-py2.py3-none-any.whl", hash = "sha256:889cf695f84e4ae2c55fbc0cfdaf4c1e729417fa52ab1db0485202ba173e4935", size = 45258, upload-time = "2020-07-11T14:49:58.287Z" }, ] [[package]] @@ -1885,9 +1984,9 @@ dependencies = [ { name = "protobuf" }, { name = "requests" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/b2/8f/ecd68579bd2bf5e9321df60dcdee6e575adf77fedacb1d8378760b2b16b6/google-api-core-2.18.0.tar.gz", hash = "sha256:62d97417bfc674d6cef251e5c4d639a9655e00c45528c4364fbfebb478ce72a9", size = 148047 } +sdist = { url = "https://files.pythonhosted.org/packages/b2/8f/ecd68579bd2bf5e9321df60dcdee6e575adf77fedacb1d8378760b2b16b6/google-api-core-2.18.0.tar.gz", hash = "sha256:62d97417bfc674d6cef251e5c4d639a9655e00c45528c4364fbfebb478ce72a9", size = 148047, upload-time = "2024-03-21T20:16:56.269Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/86/75/59a3ad90d9b4ff5b3e0537611dbe885aeb96124521c9d35aa079f1e0f2c9/google_api_core-2.18.0-py3-none-any.whl", hash = "sha256:5a63aa102e0049abe85b5b88cb9409234c1f70afcda21ce1e40b285b9629c1d6", size = 138293 }, + { url = "https://files.pythonhosted.org/packages/86/75/59a3ad90d9b4ff5b3e0537611dbe885aeb96124521c9d35aa079f1e0f2c9/google_api_core-2.18.0-py3-none-any.whl", hash = "sha256:5a63aa102e0049abe85b5b88cb9409234c1f70afcda21ce1e40b285b9629c1d6", size = 138293, upload-time = "2024-03-21T20:16:53.645Z" }, ] [package.optional-dependencies] @@ -1907,9 +2006,9 @@ dependencies = [ { name = "httplib2" }, { name = "uritemplate" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/35/8b/d990f947c261304a5c1599d45717d02c27d46af5f23e1fee5dc19c8fa79d/google-api-python-client-2.90.0.tar.gz", hash = "sha256:cbcb3ba8be37c6806676a49df16ac412077e5e5dc7fa967941eff977b31fba03", size = 10891311 } +sdist = { url = "https://files.pythonhosted.org/packages/35/8b/d990f947c261304a5c1599d45717d02c27d46af5f23e1fee5dc19c8fa79d/google-api-python-client-2.90.0.tar.gz", hash = "sha256:cbcb3ba8be37c6806676a49df16ac412077e5e5dc7fa967941eff977b31fba03", size = 10891311, upload-time = "2023-06-20T16:29:25.008Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/39/03/209b5c36a621ae644dc7d4743746cd3b38b18e133f8779ecaf6b95cc01ce/google_api_python_client-2.90.0-py2.py3-none-any.whl", hash = "sha256:4a41ffb7797d4f28e44635fb1e7076240b741c6493e7c3233c0e4421cec7c913", size = 11379891 }, + { url = "https://files.pythonhosted.org/packages/39/03/209b5c36a621ae644dc7d4743746cd3b38b18e133f8779ecaf6b95cc01ce/google_api_python_client-2.90.0-py2.py3-none-any.whl", hash = "sha256:4a41ffb7797d4f28e44635fb1e7076240b741c6493e7c3233c0e4421cec7c913", size = 11379891, upload-time = "2023-06-20T16:29:19.532Z" }, ] [[package]] @@ -1921,9 +2020,9 @@ dependencies = [ { name = "pyasn1-modules" }, { name = "rsa" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/18/b2/f14129111cfd61793609643a07ecb03651a71dd65c6974f63b0310ff4b45/google-auth-2.29.0.tar.gz", hash = "sha256:672dff332d073227550ffc7457868ac4218d6c500b155fe6cc17d2b13602c360", size = 244326 } +sdist = { url = "https://files.pythonhosted.org/packages/18/b2/f14129111cfd61793609643a07ecb03651a71dd65c6974f63b0310ff4b45/google-auth-2.29.0.tar.gz", hash = "sha256:672dff332d073227550ffc7457868ac4218d6c500b155fe6cc17d2b13602c360", size = 244326, upload-time = "2024-03-20T17:24:27.72Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/9e/8d/ddbcf81ec751d8ee5fd18ac11ff38a0e110f39dfbf105e6d9db69d556dd0/google_auth-2.29.0-py2.py3-none-any.whl", hash = "sha256:d452ad095688cd52bae0ad6fafe027f6a6d6f560e810fec20914e17a09526415", size = 189186 }, + { url = "https://files.pythonhosted.org/packages/9e/8d/ddbcf81ec751d8ee5fd18ac11ff38a0e110f39dfbf105e6d9db69d556dd0/google_auth-2.29.0-py2.py3-none-any.whl", hash = "sha256:d452ad095688cd52bae0ad6fafe027f6a6d6f560e810fec20914e17a09526415", size = 189186, upload-time = "2024-03-20T17:24:24.292Z" }, ] [[package]] @@ -1934,9 +2033,9 @@ dependencies = [ { name = "google-auth" }, { name = "httplib2" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/56/be/217a598a818567b28e859ff087f347475c807a5649296fb5a817c58dacef/google-auth-httplib2-0.2.0.tar.gz", hash = "sha256:38aa7badf48f974f1eb9861794e9c0cb2a0511a4ec0679b1f886d108f5640e05", size = 10842 } +sdist = { url = "https://files.pythonhosted.org/packages/56/be/217a598a818567b28e859ff087f347475c807a5649296fb5a817c58dacef/google-auth-httplib2-0.2.0.tar.gz", hash = "sha256:38aa7badf48f974f1eb9861794e9c0cb2a0511a4ec0679b1f886d108f5640e05", size = 10842, upload-time = "2023-12-12T17:40:30.722Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/be/8a/fe34d2f3f9470a27b01c9e76226965863f153d5fbe276f83608562e49c04/google_auth_httplib2-0.2.0-py2.py3-none-any.whl", hash = "sha256:b65a0a2123300dd71281a7bf6e64d65a0759287df52729bdd1ae2e47dc311a3d", size = 9253 }, + { url = "https://files.pythonhosted.org/packages/be/8a/fe34d2f3f9470a27b01c9e76226965863f153d5fbe276f83608562e49c04/google_auth_httplib2-0.2.0-py2.py3-none-any.whl", hash = "sha256:b65a0a2123300dd71281a7bf6e64d65a0759287df52729bdd1ae2e47dc311a3d", size = 9253, upload-time = "2023-12-12T17:40:13.055Z" }, ] [[package]] @@ -1956,14 +2055,14 @@ dependencies = [ { name = "pydantic" }, { name = "shapely" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/47/21/5930a1420f82bec246ae09e1b7cc8458544f3befe669193b33a7b5c0691c/google-cloud-aiplatform-1.49.0.tar.gz", hash = "sha256:e6e6d01079bb5def49e4be4db4d12b13c624b5c661079c869c13c855e5807429", size = 5766450 } +sdist = { url = "https://files.pythonhosted.org/packages/47/21/5930a1420f82bec246ae09e1b7cc8458544f3befe669193b33a7b5c0691c/google-cloud-aiplatform-1.49.0.tar.gz", hash = "sha256:e6e6d01079bb5def49e4be4db4d12b13c624b5c661079c869c13c855e5807429", size = 5766450, upload-time = "2024-04-29T17:25:31.646Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/39/6a/7d9e1c03c814e760361fe8b0ffd373ead4124ace66ed33bb16d526ae1ecf/google_cloud_aiplatform-1.49.0-py2.py3-none-any.whl", hash = "sha256:8072d9e0c18d8942c704233d1a93b8d6312fc7b278786a283247950e28ae98df", size = 4914049 }, + { url = "https://files.pythonhosted.org/packages/39/6a/7d9e1c03c814e760361fe8b0ffd373ead4124ace66ed33bb16d526ae1ecf/google_cloud_aiplatform-1.49.0-py2.py3-none-any.whl", hash = "sha256:8072d9e0c18d8942c704233d1a93b8d6312fc7b278786a283247950e28ae98df", size = 4914049, upload-time = "2024-04-29T17:25:27.625Z" }, ] [[package]] name = "google-cloud-bigquery" -version = "3.31.0" +version = "3.34.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "google-api-core", extra = ["grpc"] }, @@ -1974,9 +2073,9 @@ dependencies = [ { name = "python-dateutil" }, { name = "requests" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/73/91/4c7274f4d5faf13ac000b06353deaf3579575bf0e4bbad07fa68b9f09ba9/google_cloud_bigquery-3.31.0.tar.gz", hash = "sha256:b89dc716dbe4abdb7a4f873f7050100287bc98514e0614c5d54cd6a8e9fb0991", size = 479961 } +sdist = { url = "https://files.pythonhosted.org/packages/24/f9/e9da2d56d7028f05c0e2f5edf6ce43c773220c3172666c3dd925791d763d/google_cloud_bigquery-3.34.0.tar.gz", hash = "sha256:5ee1a78ba5c2ccb9f9a8b2bf3ed76b378ea68f49b6cac0544dc55cc97ff7c1ce", size = 489091, upload-time = "2025-05-29T17:18:06.03Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e8/bc/4cb8c61fc6dd817a4a390b745ec7b305f4578f547a16d09d54c8a790624b/google_cloud_bigquery-3.31.0-py3-none-any.whl", hash = "sha256:97f4a3219854ff01d6a3a57312feecb0b6e13062226b823f867e2d3619c4787b", size = 250099 }, + { url = "https://files.pythonhosted.org/packages/b1/7e/7115c4f67ca0bc678f25bff1eab56cc37d06eb9a3978940b2ebd0705aa0a/google_cloud_bigquery-3.34.0-py3-none-any.whl", hash = "sha256:de20ded0680f8136d92ff5256270b5920dfe4fae479f5d0f73e90e5df30b1cf7", size = 253555, upload-time = "2025-05-29T17:18:02.904Z" }, ] [[package]] @@ -1987,9 +2086,9 @@ dependencies = [ { name = "google-api-core" }, { name = "google-auth" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d6/b8/2b53838d2acd6ec6168fd284a990c76695e84c65deee79c9f3a4276f6b4f/google_cloud_core-2.4.3.tar.gz", hash = "sha256:1fab62d7102844b278fe6dead3af32408b1df3eb06f5c7e8634cbd40edc4da53", size = 35861 } +sdist = { url = "https://files.pythonhosted.org/packages/d6/b8/2b53838d2acd6ec6168fd284a990c76695e84c65deee79c9f3a4276f6b4f/google_cloud_core-2.4.3.tar.gz", hash = "sha256:1fab62d7102844b278fe6dead3af32408b1df3eb06f5c7e8634cbd40edc4da53", size = 35861, upload-time = "2025-03-10T21:05:38.948Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/40/86/bda7241a8da2d28a754aad2ba0f6776e35b67e37c36ae0c45d49370f1014/google_cloud_core-2.4.3-py2.py3-none-any.whl", hash = "sha256:5130f9f4c14b4fafdff75c79448f9495cfade0d8775facf1b09c3bf67e027f6e", size = 29348 }, + { url = "https://files.pythonhosted.org/packages/40/86/bda7241a8da2d28a754aad2ba0f6776e35b67e37c36ae0c45d49370f1014/google_cloud_core-2.4.3-py2.py3-none-any.whl", hash = "sha256:5130f9f4c14b4fafdff75c79448f9495cfade0d8775facf1b09c3bf67e027f6e", size = 29348, upload-time = "2025-03-10T21:05:37.785Z" }, ] [[package]] @@ -2003,9 +2102,9 @@ dependencies = [ { name = "proto-plus" }, { name = "protobuf" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/6e/ca/a4648f5038cb94af4b3942815942a03aa9398f9fb0bef55b3f1585b9940d/google_cloud_resource_manager-1.14.2.tar.gz", hash = "sha256:962e2d904c550d7bac48372607904ff7bb3277e3bb4a36d80cc9a37e28e6eb74", size = 446370 } +sdist = { url = "https://files.pythonhosted.org/packages/6e/ca/a4648f5038cb94af4b3942815942a03aa9398f9fb0bef55b3f1585b9940d/google_cloud_resource_manager-1.14.2.tar.gz", hash = "sha256:962e2d904c550d7bac48372607904ff7bb3277e3bb4a36d80cc9a37e28e6eb74", size = 446370, upload-time = "2025-03-17T11:35:56.343Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b1/ea/a92631c358da377af34d3a9682c97af83185c2d66363d5939ab4a1169a7f/google_cloud_resource_manager-1.14.2-py3-none-any.whl", hash = "sha256:d0fa954dedd1d2b8e13feae9099c01b8aac515b648e612834f9942d2795a9900", size = 394344 }, + { url = "https://files.pythonhosted.org/packages/b1/ea/a92631c358da377af34d3a9682c97af83185c2d66363d5939ab4a1169a7f/google_cloud_resource_manager-1.14.2-py3-none-any.whl", hash = "sha256:d0fa954dedd1d2b8e13feae9099c01b8aac515b648e612834f9942d2795a9900", size = 394344, upload-time = "2025-03-17T11:35:54.722Z" }, ] [[package]] @@ -2020,29 +2119,29 @@ dependencies = [ { name = "google-resumable-media" }, { name = "requests" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/17/c5/0bc3f97cf4c14a731ecc5a95c5cde6883aec7289dc74817f9b41f866f77e/google-cloud-storage-2.16.0.tar.gz", hash = "sha256:dda485fa503710a828d01246bd16ce9db0823dc51bbca742ce96a6817d58669f", size = 5525307 } +sdist = { url = "https://files.pythonhosted.org/packages/17/c5/0bc3f97cf4c14a731ecc5a95c5cde6883aec7289dc74817f9b41f866f77e/google-cloud-storage-2.16.0.tar.gz", hash = "sha256:dda485fa503710a828d01246bd16ce9db0823dc51bbca742ce96a6817d58669f", size = 5525307, upload-time = "2024-03-18T23:55:37.102Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/cb/e5/7d045d188f4ef85d94b9e3ae1bf876170c6b9f4c9a950124978efc36f680/google_cloud_storage-2.16.0-py2.py3-none-any.whl", hash = "sha256:91a06b96fb79cf9cdfb4e759f178ce11ea885c79938f89590344d079305f5852", size = 125604 }, + { url = "https://files.pythonhosted.org/packages/cb/e5/7d045d188f4ef85d94b9e3ae1bf876170c6b9f4c9a950124978efc36f680/google_cloud_storage-2.16.0-py2.py3-none-any.whl", hash = "sha256:91a06b96fb79cf9cdfb4e759f178ce11ea885c79938f89590344d079305f5852", size = 125604, upload-time = "2024-03-18T23:55:33.987Z" }, ] [[package]] name = "google-crc32c" version = "1.7.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/19/ae/87802e6d9f9d69adfaedfcfd599266bf386a54d0be058b532d04c794f76d/google_crc32c-1.7.1.tar.gz", hash = "sha256:2bff2305f98846f3e825dbeec9ee406f89da7962accdb29356e4eadc251bd472", size = 14495 } +sdist = { url = "https://files.pythonhosted.org/packages/19/ae/87802e6d9f9d69adfaedfcfd599266bf386a54d0be058b532d04c794f76d/google_crc32c-1.7.1.tar.gz", hash = "sha256:2bff2305f98846f3e825dbeec9ee406f89da7962accdb29356e4eadc251bd472", size = 14495, upload-time = "2025-03-26T14:29:13.32Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f7/94/220139ea87822b6fdfdab4fb9ba81b3fff7ea2c82e2af34adc726085bffc/google_crc32c-1.7.1-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:6fbab4b935989e2c3610371963ba1b86afb09537fd0c633049be82afe153ac06", size = 30468 }, - { url = "https://files.pythonhosted.org/packages/94/97/789b23bdeeb9d15dc2904660463ad539d0318286d7633fe2760c10ed0c1c/google_crc32c-1.7.1-cp311-cp311-macosx_12_0_x86_64.whl", hash = "sha256:ed66cbe1ed9cbaaad9392b5259b3eba4a9e565420d734e6238813c428c3336c9", size = 30313 }, - { url = "https://files.pythonhosted.org/packages/81/b8/976a2b843610c211e7ccb3e248996a61e87dbb2c09b1499847e295080aec/google_crc32c-1.7.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ee6547b657621b6cbed3562ea7826c3e11cab01cd33b74e1f677690652883e77", size = 33048 }, - { url = "https://files.pythonhosted.org/packages/c9/16/a3842c2cf591093b111d4a5e2bfb478ac6692d02f1b386d2a33283a19dc9/google_crc32c-1.7.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d68e17bad8f7dd9a49181a1f5a8f4b251c6dbc8cc96fb79f1d321dfd57d66f53", size = 32669 }, - { url = "https://files.pythonhosted.org/packages/04/17/ed9aba495916fcf5fe4ecb2267ceb851fc5f273c4e4625ae453350cfd564/google_crc32c-1.7.1-cp311-cp311-win_amd64.whl", hash = "sha256:6335de12921f06e1f774d0dd1fbea6bf610abe0887a1638f64d694013138be5d", size = 33476 }, - { url = "https://files.pythonhosted.org/packages/dd/b7/787e2453cf8639c94b3d06c9d61f512234a82e1d12d13d18584bd3049904/google_crc32c-1.7.1-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:2d73a68a653c57281401871dd4aeebbb6af3191dcac751a76ce430df4d403194", size = 30470 }, - { url = "https://files.pythonhosted.org/packages/ed/b4/6042c2b0cbac3ec3a69bb4c49b28d2f517b7a0f4a0232603c42c58e22b44/google_crc32c-1.7.1-cp312-cp312-macosx_12_0_x86_64.whl", hash = "sha256:22beacf83baaf59f9d3ab2bbb4db0fb018da8e5aebdce07ef9f09fce8220285e", size = 30315 }, - { url = "https://files.pythonhosted.org/packages/29/ad/01e7a61a5d059bc57b702d9ff6a18b2585ad97f720bd0a0dbe215df1ab0e/google_crc32c-1.7.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:19eafa0e4af11b0a4eb3974483d55d2d77ad1911e6cf6f832e1574f6781fd337", size = 33180 }, - { url = "https://files.pythonhosted.org/packages/3b/a5/7279055cf004561894ed3a7bfdf5bf90a53f28fadd01af7cd166e88ddf16/google_crc32c-1.7.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b6d86616faaea68101195c6bdc40c494e4d76f41e07a37ffdef270879c15fb65", size = 32794 }, - { url = "https://files.pythonhosted.org/packages/0f/d6/77060dbd140c624e42ae3ece3df53b9d811000729a5c821b9fd671ceaac6/google_crc32c-1.7.1-cp312-cp312-win_amd64.whl", hash = "sha256:b7491bdc0c7564fcf48c0179d2048ab2f7c7ba36b84ccd3a3e1c3f7a72d3bba6", size = 33477 }, - { url = "https://files.pythonhosted.org/packages/16/1b/1693372bf423ada422f80fd88260dbfd140754adb15cbc4d7e9a68b1cb8e/google_crc32c-1.7.1-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:85fef7fae11494e747c9fd1359a527e5970fc9603c90764843caabd3a16a0a48", size = 28241 }, - { url = "https://files.pythonhosted.org/packages/fd/3c/2a19a60a473de48717b4efb19398c3f914795b64a96cf3fbe82588044f78/google_crc32c-1.7.1-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6efb97eb4369d52593ad6f75e7e10d053cf00c48983f7a973105bc70b0ac4d82", size = 28048 }, + { url = "https://files.pythonhosted.org/packages/f7/94/220139ea87822b6fdfdab4fb9ba81b3fff7ea2c82e2af34adc726085bffc/google_crc32c-1.7.1-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:6fbab4b935989e2c3610371963ba1b86afb09537fd0c633049be82afe153ac06", size = 30468, upload-time = "2025-03-26T14:32:52.215Z" }, + { url = "https://files.pythonhosted.org/packages/94/97/789b23bdeeb9d15dc2904660463ad539d0318286d7633fe2760c10ed0c1c/google_crc32c-1.7.1-cp311-cp311-macosx_12_0_x86_64.whl", hash = "sha256:ed66cbe1ed9cbaaad9392b5259b3eba4a9e565420d734e6238813c428c3336c9", size = 30313, upload-time = "2025-03-26T14:57:38.758Z" }, + { url = "https://files.pythonhosted.org/packages/81/b8/976a2b843610c211e7ccb3e248996a61e87dbb2c09b1499847e295080aec/google_crc32c-1.7.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ee6547b657621b6cbed3562ea7826c3e11cab01cd33b74e1f677690652883e77", size = 33048, upload-time = "2025-03-26T14:41:30.679Z" }, + { url = "https://files.pythonhosted.org/packages/c9/16/a3842c2cf591093b111d4a5e2bfb478ac6692d02f1b386d2a33283a19dc9/google_crc32c-1.7.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d68e17bad8f7dd9a49181a1f5a8f4b251c6dbc8cc96fb79f1d321dfd57d66f53", size = 32669, upload-time = "2025-03-26T14:41:31.432Z" }, + { url = "https://files.pythonhosted.org/packages/04/17/ed9aba495916fcf5fe4ecb2267ceb851fc5f273c4e4625ae453350cfd564/google_crc32c-1.7.1-cp311-cp311-win_amd64.whl", hash = "sha256:6335de12921f06e1f774d0dd1fbea6bf610abe0887a1638f64d694013138be5d", size = 33476, upload-time = "2025-03-26T14:29:10.211Z" }, + { url = "https://files.pythonhosted.org/packages/dd/b7/787e2453cf8639c94b3d06c9d61f512234a82e1d12d13d18584bd3049904/google_crc32c-1.7.1-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:2d73a68a653c57281401871dd4aeebbb6af3191dcac751a76ce430df4d403194", size = 30470, upload-time = "2025-03-26T14:34:31.655Z" }, + { url = "https://files.pythonhosted.org/packages/ed/b4/6042c2b0cbac3ec3a69bb4c49b28d2f517b7a0f4a0232603c42c58e22b44/google_crc32c-1.7.1-cp312-cp312-macosx_12_0_x86_64.whl", hash = "sha256:22beacf83baaf59f9d3ab2bbb4db0fb018da8e5aebdce07ef9f09fce8220285e", size = 30315, upload-time = "2025-03-26T15:01:54.634Z" }, + { url = "https://files.pythonhosted.org/packages/29/ad/01e7a61a5d059bc57b702d9ff6a18b2585ad97f720bd0a0dbe215df1ab0e/google_crc32c-1.7.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:19eafa0e4af11b0a4eb3974483d55d2d77ad1911e6cf6f832e1574f6781fd337", size = 33180, upload-time = "2025-03-26T14:41:32.168Z" }, + { url = "https://files.pythonhosted.org/packages/3b/a5/7279055cf004561894ed3a7bfdf5bf90a53f28fadd01af7cd166e88ddf16/google_crc32c-1.7.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b6d86616faaea68101195c6bdc40c494e4d76f41e07a37ffdef270879c15fb65", size = 32794, upload-time = "2025-03-26T14:41:33.264Z" }, + { url = "https://files.pythonhosted.org/packages/0f/d6/77060dbd140c624e42ae3ece3df53b9d811000729a5c821b9fd671ceaac6/google_crc32c-1.7.1-cp312-cp312-win_amd64.whl", hash = "sha256:b7491bdc0c7564fcf48c0179d2048ab2f7c7ba36b84ccd3a3e1c3f7a72d3bba6", size = 33477, upload-time = "2025-03-26T14:29:10.94Z" }, + { url = "https://files.pythonhosted.org/packages/16/1b/1693372bf423ada422f80fd88260dbfd140754adb15cbc4d7e9a68b1cb8e/google_crc32c-1.7.1-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:85fef7fae11494e747c9fd1359a527e5970fc9603c90764843caabd3a16a0a48", size = 28241, upload-time = "2025-03-26T14:41:45.898Z" }, + { url = "https://files.pythonhosted.org/packages/fd/3c/2a19a60a473de48717b4efb19398c3f914795b64a96cf3fbe82588044f78/google_crc32c-1.7.1-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6efb97eb4369d52593ad6f75e7e10d053cf00c48983f7a973105bc70b0ac4d82", size = 28048, upload-time = "2025-03-26T14:41:46.696Z" }, ] [[package]] @@ -2052,9 +2151,9 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "google-crc32c" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/58/5a/0efdc02665dca14e0837b62c8a1a93132c264bd02054a15abb2218afe0ae/google_resumable_media-2.7.2.tar.gz", hash = "sha256:5280aed4629f2b60b847b0d42f9857fd4935c11af266744df33d8074cae92fe0", size = 2163099 } +sdist = { url = "https://files.pythonhosted.org/packages/58/5a/0efdc02665dca14e0837b62c8a1a93132c264bd02054a15abb2218afe0ae/google_resumable_media-2.7.2.tar.gz", hash = "sha256:5280aed4629f2b60b847b0d42f9857fd4935c11af266744df33d8074cae92fe0", size = 2163099, upload-time = "2024-08-07T22:20:38.555Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/82/35/b8d3baf8c46695858cb9d8835a53baa1eeb9906ddaf2f728a5f5b640fd1e/google_resumable_media-2.7.2-py2.py3-none-any.whl", hash = "sha256:3ce7551e9fe6d99e9a126101d2536612bb73486721951e9562fee0f90c6ababa", size = 81251 }, + { url = "https://files.pythonhosted.org/packages/82/35/b8d3baf8c46695858cb9d8835a53baa1eeb9906ddaf2f728a5f5b640fd1e/google_resumable_media-2.7.2-py2.py3-none-any.whl", hash = "sha256:3ce7551e9fe6d99e9a126101d2536612bb73486721951e9562fee0f90c6ababa", size = 81251, upload-time = "2024-08-07T22:20:36.409Z" }, ] [[package]] @@ -2064,9 +2163,9 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "protobuf" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d2/dc/291cebf3c73e108ef8210f19cb83d671691354f4f7dd956445560d778715/googleapis-common-protos-1.63.0.tar.gz", hash = "sha256:17ad01b11d5f1d0171c06d3ba5c04c54474e883b66b949722b4938ee2694ef4e", size = 121646 } +sdist = { url = "https://files.pythonhosted.org/packages/d2/dc/291cebf3c73e108ef8210f19cb83d671691354f4f7dd956445560d778715/googleapis-common-protos-1.63.0.tar.gz", hash = "sha256:17ad01b11d5f1d0171c06d3ba5c04c54474e883b66b949722b4938ee2694ef4e", size = 121646, upload-time = "2024-03-11T12:33:15.765Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/dc/a6/12a0c976140511d8bc8a16ad15793b2aef29ac927baa0786ccb7ddbb6e1c/googleapis_common_protos-1.63.0-py2.py3-none-any.whl", hash = "sha256:ae45f75702f7c08b541f750854a678bd8f534a1a6bace6afe975f1d0a82d6632", size = 229141 }, + { url = "https://files.pythonhosted.org/packages/dc/a6/12a0c976140511d8bc8a16ad15793b2aef29ac927baa0786ccb7ddbb6e1c/googleapis_common_protos-1.63.0-py2.py3-none-any.whl", hash = "sha256:ae45f75702f7c08b541f750854a678bd8f534a1a6bace6afe975f1d0a82d6632", size = 229141, upload-time = "2024-03-11T12:33:14.052Z" }, ] [package.optional-dependencies] @@ -2082,35 +2181,68 @@ dependencies = [ { name = "httpx", extra = ["http2"] }, { name = "pydantic" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/19/9c/62c3241731b59c1c403377abef17b5e3782f6385b0317f6d7083271db501/gotrue-2.11.4.tar.gz", hash = "sha256:a9ced242b16c6d6bedc43bca21bbefea1ba5fb35fcdaad7d529342099d3b1767", size = 35353 } +sdist = { url = "https://files.pythonhosted.org/packages/19/9c/62c3241731b59c1c403377abef17b5e3782f6385b0317f6d7083271db501/gotrue-2.11.4.tar.gz", hash = "sha256:a9ced242b16c6d6bedc43bca21bbefea1ba5fb35fcdaad7d529342099d3b1767", size = 35353, upload-time = "2025-02-20T09:02:37.346Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/47/3a/1a7cac16438f4e5319a0c879416d5e5032c98c3db2874e6e5300b3b475e6/gotrue-2.11.4-py3-none-any.whl", hash = "sha256:712e5018acc00d93cfc6d7bfddc3114eb3c420ab03b945757a8ba38c5fc3caa8", size = 41106, upload-time = "2025-02-20T09:02:34.653Z" }, +] + +[[package]] +name = "gql" +version = "3.5.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "backoff" }, + { name = "graphql-core" }, + { name = "yarl" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/34/ed/44ffd30b06b3afc8274ee2f38c3c1b61fe4740bf03d92083e43d2c17ac77/gql-3.5.3.tar.gz", hash = "sha256:393b8c049d58e0d2f5461b9d738a2b5f904186a40395500b4a84dd092d56e42b", size = 180504, upload-time = "2025-05-20T12:34:08.954Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cb/50/2f4e99b216821ac921dbebf91c644ba95818f5d07857acadee17220221f3/gql-3.5.3-py2.py3-none-any.whl", hash = "sha256:e1fcbde2893fcafdd28114ece87ff47f1cc339a31db271fc4e1d528f5a1d4fbc", size = 74348, upload-time = "2025-05-20T12:34:07.687Z" }, +] + +[package.optional-dependencies] +aiohttp = [ + { name = "aiohttp" }, +] +requests = [ + { name = "requests" }, + { name = "requests-toolbelt" }, +] + +[[package]] +name = "graphql-core" +version = "3.2.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c4/16/7574029da84834349b60ed71614d66ca3afe46e9bf9c7b9562102acb7d4f/graphql_core-3.2.6.tar.gz", hash = "sha256:c08eec22f9e40f0bd61d805907e3b3b1b9a320bc606e23dc145eebca07c8fbab", size = 505353, upload-time = "2025-01-26T16:36:27.374Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/47/3a/1a7cac16438f4e5319a0c879416d5e5032c98c3db2874e6e5300b3b475e6/gotrue-2.11.4-py3-none-any.whl", hash = "sha256:712e5018acc00d93cfc6d7bfddc3114eb3c420ab03b945757a8ba38c5fc3caa8", size = 41106 }, + { url = "https://files.pythonhosted.org/packages/ae/4f/7297663840621022bc73c22d7d9d80dbc78b4db6297f764b545cd5dd462d/graphql_core-3.2.6-py3-none-any.whl", hash = "sha256:78b016718c161a6fb20a7d97bbf107f331cd1afe53e45566c59f776ed7f0b45f", size = 203416, upload-time = "2025-01-26T16:36:24.868Z" }, ] [[package]] name = "greenlet" -version = "3.1.1" +version = "3.2.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/2f/ff/df5fede753cc10f6a5be0931204ea30c35fa2f2ea7a35b25bdaf4fe40e46/greenlet-3.1.1.tar.gz", hash = "sha256:4ce3ac6cdb6adf7946475d7ef31777c26d94bccc377e070a7986bd2d5c515467", size = 186022 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/28/62/1c2665558618553c42922ed47a4e6d6527e2fa3516a8256c2f431c5d0441/greenlet-3.1.1-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:e4d333e558953648ca09d64f13e6d8f0523fa705f51cae3f03b5983489958c70", size = 272479 }, - { url = "https://files.pythonhosted.org/packages/76/9d/421e2d5f07285b6e4e3a676b016ca781f63cfe4a0cd8eaecf3fd6f7a71ae/greenlet-3.1.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:09fc016b73c94e98e29af67ab7b9a879c307c6731a2c9da0db5a7d9b7edd1159", size = 640404 }, - { url = "https://files.pythonhosted.org/packages/e5/de/6e05f5c59262a584e502dd3d261bbdd2c97ab5416cc9c0b91ea38932a901/greenlet-3.1.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d5e975ca70269d66d17dd995dafc06f1b06e8cb1ec1e9ed54c1d1e4a7c4cf26e", size = 652813 }, - { url = "https://files.pythonhosted.org/packages/49/93/d5f93c84241acdea15a8fd329362c2c71c79e1a507c3f142a5d67ea435ae/greenlet-3.1.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3b2813dc3de8c1ee3f924e4d4227999285fd335d1bcc0d2be6dc3f1f6a318ec1", size = 648517 }, - { url = "https://files.pythonhosted.org/packages/15/85/72f77fc02d00470c86a5c982b8daafdf65d38aefbbe441cebff3bf7037fc/greenlet-3.1.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e347b3bfcf985a05e8c0b7d462ba6f15b1ee1c909e2dcad795e49e91b152c383", size = 647831 }, - { url = "https://files.pythonhosted.org/packages/f7/4b/1c9695aa24f808e156c8f4813f685d975ca73c000c2a5056c514c64980f6/greenlet-3.1.1-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9e8f8c9cb53cdac7ba9793c276acd90168f416b9ce36799b9b885790f8ad6c0a", size = 602413 }, - { url = "https://files.pythonhosted.org/packages/76/70/ad6e5b31ef330f03b12559d19fda2606a522d3849cde46b24f223d6d1619/greenlet-3.1.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:62ee94988d6b4722ce0028644418d93a52429e977d742ca2ccbe1c4f4a792511", size = 1129619 }, - { url = "https://files.pythonhosted.org/packages/f4/fb/201e1b932e584066e0f0658b538e73c459b34d44b4bd4034f682423bc801/greenlet-3.1.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:1776fd7f989fc6b8d8c8cb8da1f6b82c5814957264d1f6cf818d475ec2bf6395", size = 1155198 }, - { url = "https://files.pythonhosted.org/packages/12/da/b9ed5e310bb8b89661b80cbcd4db5a067903bbcd7fc854923f5ebb4144f0/greenlet-3.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:48ca08c771c268a768087b408658e216133aecd835c0ded47ce955381105ba39", size = 298930 }, - { url = "https://files.pythonhosted.org/packages/7d/ec/bad1ac26764d26aa1353216fcbfa4670050f66d445448aafa227f8b16e80/greenlet-3.1.1-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:4afe7ea89de619adc868e087b4d2359282058479d7cfb94970adf4b55284574d", size = 274260 }, - { url = "https://files.pythonhosted.org/packages/66/d4/c8c04958870f482459ab5956c2942c4ec35cac7fe245527f1039837c17a9/greenlet-3.1.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f406b22b7c9a9b4f8aa9d2ab13d6ae0ac3e85c9a809bd590ad53fed2bf70dc79", size = 649064 }, - { url = "https://files.pythonhosted.org/packages/51/41/467b12a8c7c1303d20abcca145db2be4e6cd50a951fa30af48b6ec607581/greenlet-3.1.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c3a701fe5a9695b238503ce5bbe8218e03c3bcccf7e204e455e7462d770268aa", size = 663420 }, - { url = "https://files.pythonhosted.org/packages/27/8f/2a93cd9b1e7107d5c7b3b7816eeadcac2ebcaf6d6513df9abaf0334777f6/greenlet-3.1.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2846930c65b47d70b9d178e89c7e1a69c95c1f68ea5aa0a58646b7a96df12441", size = 658035 }, - { url = "https://files.pythonhosted.org/packages/57/5c/7c6f50cb12be092e1dccb2599be5a942c3416dbcfb76efcf54b3f8be4d8d/greenlet-3.1.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:99cfaa2110534e2cf3ba31a7abcac9d328d1d9f1b95beede58294a60348fba36", size = 660105 }, - { url = "https://files.pythonhosted.org/packages/f1/66/033e58a50fd9ec9df00a8671c74f1f3a320564c6415a4ed82a1c651654ba/greenlet-3.1.1-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1443279c19fca463fc33e65ef2a935a5b09bb90f978beab37729e1c3c6c25fe9", size = 613077 }, - { url = "https://files.pythonhosted.org/packages/19/c5/36384a06f748044d06bdd8776e231fadf92fc896bd12cb1c9f5a1bda9578/greenlet-3.1.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b7cede291382a78f7bb5f04a529cb18e068dd29e0fb27376074b6d0317bf4dd0", size = 1135975 }, - { url = "https://files.pythonhosted.org/packages/38/f9/c0a0eb61bdf808d23266ecf1d63309f0e1471f284300ce6dac0ae1231881/greenlet-3.1.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:23f20bb60ae298d7d8656c6ec6db134bca379ecefadb0b19ce6f19d1f232a942", size = 1163955 }, - { url = "https://files.pythonhosted.org/packages/43/21/a5d9df1d21514883333fc86584c07c2b49ba7c602e670b174bd73cfc9c7f/greenlet-3.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:7124e16b4c55d417577c2077be379514321916d5790fa287c9ed6f23bd2ffd01", size = 299655 }, +sdist = { url = "https://files.pythonhosted.org/packages/34/c1/a82edae11d46c0d83481aacaa1e578fea21d94a1ef400afd734d47ad95ad/greenlet-3.2.2.tar.gz", hash = "sha256:ad053d34421a2debba45aa3cc39acf454acbcd025b3fc1a9f8a0dee237abd485", size = 185797, upload-time = "2025-05-09T19:47:35.066Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a3/9f/a47e19261747b562ce88219e5ed8c859d42c6e01e73da6fbfa3f08a7be13/greenlet-3.2.2-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:dcb9cebbf3f62cb1e5afacae90761ccce0effb3adaa32339a0670fe7805d8068", size = 268635, upload-time = "2025-05-09T14:50:39.007Z" }, + { url = "https://files.pythonhosted.org/packages/11/80/a0042b91b66975f82a914d515e81c1944a3023f2ce1ed7a9b22e10b46919/greenlet-3.2.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bf3fc9145141250907730886b031681dfcc0de1c158f3cc51c092223c0f381ce", size = 628786, upload-time = "2025-05-09T15:24:00.692Z" }, + { url = "https://files.pythonhosted.org/packages/38/a2/8336bf1e691013f72a6ebab55da04db81a11f68e82bb691f434909fa1327/greenlet-3.2.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:efcdfb9df109e8a3b475c016f60438fcd4be68cd13a365d42b35914cdab4bb2b", size = 640866, upload-time = "2025-05-09T15:24:48.153Z" }, + { url = "https://files.pythonhosted.org/packages/f8/7e/f2a3a13e424670a5d08826dab7468fa5e403e0fbe0b5f951ff1bc4425b45/greenlet-3.2.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4bd139e4943547ce3a56ef4b8b1b9479f9e40bb47e72cc906f0f66b9d0d5cab3", size = 636752, upload-time = "2025-05-09T15:29:23.182Z" }, + { url = "https://files.pythonhosted.org/packages/fd/5d/ce4a03a36d956dcc29b761283f084eb4a3863401c7cb505f113f73af8774/greenlet-3.2.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:71566302219b17ca354eb274dfd29b8da3c268e41b646f330e324e3967546a74", size = 636028, upload-time = "2025-05-09T14:53:32.854Z" }, + { url = "https://files.pythonhosted.org/packages/4b/29/b130946b57e3ceb039238413790dd3793c5e7b8e14a54968de1fe449a7cf/greenlet-3.2.2-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3091bc45e6b0c73f225374fefa1536cd91b1e987377b12ef5b19129b07d93ebe", size = 583869, upload-time = "2025-05-09T14:53:43.614Z" }, + { url = "https://files.pythonhosted.org/packages/ac/30/9f538dfe7f87b90ecc75e589d20cbd71635531a617a336c386d775725a8b/greenlet-3.2.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:44671c29da26539a5f142257eaba5110f71887c24d40df3ac87f1117df589e0e", size = 1112886, upload-time = "2025-05-09T15:27:01.304Z" }, + { url = "https://files.pythonhosted.org/packages/be/92/4b7deeb1a1e9c32c1b59fdca1cac3175731c23311ddca2ea28a8b6ada91c/greenlet-3.2.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c23ea227847c9dbe0b3910f5c0dd95658b607137614eb821e6cbaecd60d81cc6", size = 1138355, upload-time = "2025-05-09T14:53:58.011Z" }, + { url = "https://files.pythonhosted.org/packages/c5/eb/7551c751a2ea6498907b2fcbe31d7a54b602ba5e8eb9550a9695ca25d25c/greenlet-3.2.2-cp311-cp311-win_amd64.whl", hash = "sha256:0a16fb934fcabfdfacf21d79e6fed81809d8cd97bc1be9d9c89f0e4567143d7b", size = 295437, upload-time = "2025-05-09T15:00:57.733Z" }, + { url = "https://files.pythonhosted.org/packages/2c/a1/88fdc6ce0df6ad361a30ed78d24c86ea32acb2b563f33e39e927b1da9ea0/greenlet-3.2.2-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:df4d1509efd4977e6a844ac96d8be0b9e5aa5d5c77aa27ca9f4d3f92d3fcf330", size = 270413, upload-time = "2025-05-09T14:51:32.455Z" }, + { url = "https://files.pythonhosted.org/packages/a6/2e/6c1caffd65490c68cd9bcec8cb7feb8ac7b27d38ba1fea121fdc1f2331dc/greenlet-3.2.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da956d534a6d1b9841f95ad0f18ace637668f680b1339ca4dcfb2c1837880a0b", size = 637242, upload-time = "2025-05-09T15:24:02.63Z" }, + { url = "https://files.pythonhosted.org/packages/98/28/088af2cedf8823b6b7ab029a5626302af4ca1037cf8b998bed3a8d3cb9e2/greenlet-3.2.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9c7b15fb9b88d9ee07e076f5a683027bc3befd5bb5d25954bb633c385d8b737e", size = 651444, upload-time = "2025-05-09T15:24:49.856Z" }, + { url = "https://files.pythonhosted.org/packages/4a/9f/0116ab876bb0bc7a81eadc21c3f02cd6100dcd25a1cf2a085a130a63a26a/greenlet-3.2.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:752f0e79785e11180ebd2e726c8a88109ded3e2301d40abced2543aa5d164275", size = 646067, upload-time = "2025-05-09T15:29:24.989Z" }, + { url = "https://files.pythonhosted.org/packages/35/17/bb8f9c9580e28a94a9575da847c257953d5eb6e39ca888239183320c1c28/greenlet-3.2.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9ae572c996ae4b5e122331e12bbb971ea49c08cc7c232d1bd43150800a2d6c65", size = 648153, upload-time = "2025-05-09T14:53:34.716Z" }, + { url = "https://files.pythonhosted.org/packages/2c/ee/7f31b6f7021b8df6f7203b53b9cc741b939a2591dcc6d899d8042fcf66f2/greenlet-3.2.2-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:02f5972ff02c9cf615357c17ab713737cccfd0eaf69b951084a9fd43f39833d3", size = 603865, upload-time = "2025-05-09T14:53:45.738Z" }, + { url = "https://files.pythonhosted.org/packages/b5/2d/759fa59323b521c6f223276a4fc3d3719475dc9ae4c44c2fe7fc750f8de0/greenlet-3.2.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:4fefc7aa68b34b9224490dfda2e70ccf2131368493add64b4ef2d372955c207e", size = 1119575, upload-time = "2025-05-09T15:27:04.248Z" }, + { url = "https://files.pythonhosted.org/packages/30/05/356813470060bce0e81c3df63ab8cd1967c1ff6f5189760c1a4734d405ba/greenlet-3.2.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a31ead8411a027c2c4759113cf2bd473690517494f3d6e4bf67064589afcd3c5", size = 1147460, upload-time = "2025-05-09T14:54:00.315Z" }, + { url = "https://files.pythonhosted.org/packages/07/f4/b2a26a309a04fb844c7406a4501331b9400e1dd7dd64d3450472fd47d2e1/greenlet-3.2.2-cp312-cp312-win_amd64.whl", hash = "sha256:b24c7844c0a0afc3ccbeb0b807adeefb7eff2b5599229ecedddcfeb0ef333bec", size = 296239, upload-time = "2025-05-09T14:57:17.633Z" }, ] [[package]] @@ -2122,35 +2254,35 @@ dependencies = [ { name = "grpcio" }, { name = "protobuf" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/b9/4e/8d0ca3b035e41fe0b3f31ebbb638356af720335e5a11154c330169b40777/grpc_google_iam_v1-0.14.2.tar.gz", hash = "sha256:b3e1fc387a1a329e41672197d0ace9de22c78dd7d215048c4c78712073f7bd20", size = 16259 } +sdist = { url = "https://files.pythonhosted.org/packages/b9/4e/8d0ca3b035e41fe0b3f31ebbb638356af720335e5a11154c330169b40777/grpc_google_iam_v1-0.14.2.tar.gz", hash = "sha256:b3e1fc387a1a329e41672197d0ace9de22c78dd7d215048c4c78712073f7bd20", size = 16259, upload-time = "2025-03-17T11:40:23.586Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/66/6f/dd9b178aee7835b96c2e63715aba6516a9d50f6bebbd1cc1d32c82a2a6c3/grpc_google_iam_v1-0.14.2-py3-none-any.whl", hash = "sha256:a3171468459770907926d56a440b2bb643eec1d7ba215f48f3ecece42b4d8351", size = 19242 }, + { url = "https://files.pythonhosted.org/packages/66/6f/dd9b178aee7835b96c2e63715aba6516a9d50f6bebbd1cc1d32c82a2a6c3/grpc_google_iam_v1-0.14.2-py3-none-any.whl", hash = "sha256:a3171468459770907926d56a440b2bb643eec1d7ba215f48f3ecece42b4d8351", size = 19242, upload-time = "2025-03-17T11:40:22.648Z" }, ] [[package]] name = "grpcio" version = "1.67.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/20/53/d9282a66a5db45981499190b77790570617a604a38f3d103d0400974aeb5/grpcio-1.67.1.tar.gz", hash = "sha256:3dc2ed4cabea4dc14d5e708c2b426205956077cc5de419b4d4079315017e9732", size = 12580022 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/59/2c/b60d6ea1f63a20a8d09c6db95c4f9a16497913fb3048ce0990ed81aeeca0/grpcio-1.67.1-cp311-cp311-linux_armv7l.whl", hash = "sha256:7818c0454027ae3384235a65210bbf5464bd715450e30a3d40385453a85a70cb", size = 5119075 }, - { url = "https://files.pythonhosted.org/packages/b3/9a/e1956f7ca582a22dd1f17b9e26fcb8229051b0ce6d33b47227824772feec/grpcio-1.67.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ea33986b70f83844cd00814cee4451055cd8cab36f00ac64a31f5bb09b31919e", size = 11009159 }, - { url = "https://files.pythonhosted.org/packages/43/a8/35fbbba580c4adb1d40d12e244cf9f7c74a379073c0a0ca9d1b5338675a1/grpcio-1.67.1-cp311-cp311-manylinux_2_17_aarch64.whl", hash = "sha256:c7a01337407dd89005527623a4a72c5c8e2894d22bead0895306b23c6695698f", size = 5629476 }, - { url = "https://files.pythonhosted.org/packages/77/c9/864d336e167263d14dfccb4dbfa7fce634d45775609895287189a03f1fc3/grpcio-1.67.1-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:80b866f73224b0634f4312a4674c1be21b2b4afa73cb20953cbbb73a6b36c3cc", size = 6239901 }, - { url = "https://files.pythonhosted.org/packages/f7/1e/0011408ebabf9bd69f4f87cc1515cbfe2094e5a32316f8714a75fd8ddfcb/grpcio-1.67.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f9fff78ba10d4250bfc07a01bd6254a6d87dc67f9627adece85c0b2ed754fa96", size = 5881010 }, - { url = "https://files.pythonhosted.org/packages/b4/7d/fbca85ee9123fb296d4eff8df566f458d738186d0067dec6f0aa2fd79d71/grpcio-1.67.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:8a23cbcc5bb11ea7dc6163078be36c065db68d915c24f5faa4f872c573bb400f", size = 6580706 }, - { url = "https://files.pythonhosted.org/packages/75/7a/766149dcfa2dfa81835bf7df623944c1f636a15fcb9b6138ebe29baf0bc6/grpcio-1.67.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:1a65b503d008f066e994f34f456e0647e5ceb34cfcec5ad180b1b44020ad4970", size = 6161799 }, - { url = "https://files.pythonhosted.org/packages/09/13/5b75ae88810aaea19e846f5380611837de411181df51fd7a7d10cb178dcb/grpcio-1.67.1-cp311-cp311-win32.whl", hash = "sha256:e29ca27bec8e163dca0c98084040edec3bc49afd10f18b412f483cc68c712744", size = 3616330 }, - { url = "https://files.pythonhosted.org/packages/aa/39/38117259613f68f072778c9638a61579c0cfa5678c2558706b10dd1d11d3/grpcio-1.67.1-cp311-cp311-win_amd64.whl", hash = "sha256:786a5b18544622bfb1e25cc08402bd44ea83edfb04b93798d85dca4d1a0b5be5", size = 4354535 }, - { url = "https://files.pythonhosted.org/packages/6e/25/6f95bd18d5f506364379eabc0d5874873cc7dbdaf0757df8d1e82bc07a88/grpcio-1.67.1-cp312-cp312-linux_armv7l.whl", hash = "sha256:267d1745894200e4c604958da5f856da6293f063327cb049a51fe67348e4f953", size = 5089809 }, - { url = "https://files.pythonhosted.org/packages/10/3f/d79e32e5d0354be33a12db2267c66d3cfeff700dd5ccdd09fd44a3ff4fb6/grpcio-1.67.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:85f69fdc1d28ce7cff8de3f9c67db2b0ca9ba4449644488c1e0303c146135ddb", size = 10981985 }, - { url = "https://files.pythonhosted.org/packages/21/f2/36fbc14b3542e3a1c20fb98bd60c4732c55a44e374a4eb68f91f28f14aab/grpcio-1.67.1-cp312-cp312-manylinux_2_17_aarch64.whl", hash = "sha256:f26b0b547eb8d00e195274cdfc63ce64c8fc2d3e2d00b12bf468ece41a0423a0", size = 5588770 }, - { url = "https://files.pythonhosted.org/packages/0d/af/bbc1305df60c4e65de8c12820a942b5e37f9cf684ef5e49a63fbb1476a73/grpcio-1.67.1-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4422581cdc628f77302270ff839a44f4c24fdc57887dc2a45b7e53d8fc2376af", size = 6214476 }, - { url = "https://files.pythonhosted.org/packages/92/cf/1d4c3e93efa93223e06a5c83ac27e32935f998bc368e276ef858b8883154/grpcio-1.67.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1d7616d2ded471231c701489190379e0c311ee0a6c756f3c03e6a62b95a7146e", size = 5850129 }, - { url = "https://files.pythonhosted.org/packages/ae/ca/26195b66cb253ac4d5ef59846e354d335c9581dba891624011da0e95d67b/grpcio-1.67.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:8a00efecde9d6fcc3ab00c13f816313c040a28450e5e25739c24f432fc6d3c75", size = 6568489 }, - { url = "https://files.pythonhosted.org/packages/d1/94/16550ad6b3f13b96f0856ee5dfc2554efac28539ee84a51d7b14526da985/grpcio-1.67.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:699e964923b70f3101393710793289e42845791ea07565654ada0969522d0a38", size = 6149369 }, - { url = "https://files.pythonhosted.org/packages/33/0d/4c3b2587e8ad7f121b597329e6c2620374fccbc2e4e1aa3c73ccc670fde4/grpcio-1.67.1-cp312-cp312-win32.whl", hash = "sha256:4e7b904484a634a0fff132958dabdb10d63e0927398273917da3ee103e8d1f78", size = 3599176 }, - { url = "https://files.pythonhosted.org/packages/7d/36/0c03e2d80db69e2472cf81c6123aa7d14741de7cf790117291a703ae6ae1/grpcio-1.67.1-cp312-cp312-win_amd64.whl", hash = "sha256:5721e66a594a6c4204458004852719b38f3d5522082be9061d6510b455c90afc", size = 4346574 }, +sdist = { url = "https://files.pythonhosted.org/packages/20/53/d9282a66a5db45981499190b77790570617a604a38f3d103d0400974aeb5/grpcio-1.67.1.tar.gz", hash = "sha256:3dc2ed4cabea4dc14d5e708c2b426205956077cc5de419b4d4079315017e9732", size = 12580022, upload-time = "2024-10-29T06:30:07.787Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/59/2c/b60d6ea1f63a20a8d09c6db95c4f9a16497913fb3048ce0990ed81aeeca0/grpcio-1.67.1-cp311-cp311-linux_armv7l.whl", hash = "sha256:7818c0454027ae3384235a65210bbf5464bd715450e30a3d40385453a85a70cb", size = 5119075, upload-time = "2024-10-29T06:24:04.696Z" }, + { url = "https://files.pythonhosted.org/packages/b3/9a/e1956f7ca582a22dd1f17b9e26fcb8229051b0ce6d33b47227824772feec/grpcio-1.67.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ea33986b70f83844cd00814cee4451055cd8cab36f00ac64a31f5bb09b31919e", size = 11009159, upload-time = "2024-10-29T06:24:07.781Z" }, + { url = "https://files.pythonhosted.org/packages/43/a8/35fbbba580c4adb1d40d12e244cf9f7c74a379073c0a0ca9d1b5338675a1/grpcio-1.67.1-cp311-cp311-manylinux_2_17_aarch64.whl", hash = "sha256:c7a01337407dd89005527623a4a72c5c8e2894d22bead0895306b23c6695698f", size = 5629476, upload-time = "2024-10-29T06:24:11.444Z" }, + { url = "https://files.pythonhosted.org/packages/77/c9/864d336e167263d14dfccb4dbfa7fce634d45775609895287189a03f1fc3/grpcio-1.67.1-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:80b866f73224b0634f4312a4674c1be21b2b4afa73cb20953cbbb73a6b36c3cc", size = 6239901, upload-time = "2024-10-29T06:24:14.2Z" }, + { url = "https://files.pythonhosted.org/packages/f7/1e/0011408ebabf9bd69f4f87cc1515cbfe2094e5a32316f8714a75fd8ddfcb/grpcio-1.67.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f9fff78ba10d4250bfc07a01bd6254a6d87dc67f9627adece85c0b2ed754fa96", size = 5881010, upload-time = "2024-10-29T06:24:17.451Z" }, + { url = "https://files.pythonhosted.org/packages/b4/7d/fbca85ee9123fb296d4eff8df566f458d738186d0067dec6f0aa2fd79d71/grpcio-1.67.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:8a23cbcc5bb11ea7dc6163078be36c065db68d915c24f5faa4f872c573bb400f", size = 6580706, upload-time = "2024-10-29T06:24:20.038Z" }, + { url = "https://files.pythonhosted.org/packages/75/7a/766149dcfa2dfa81835bf7df623944c1f636a15fcb9b6138ebe29baf0bc6/grpcio-1.67.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:1a65b503d008f066e994f34f456e0647e5ceb34cfcec5ad180b1b44020ad4970", size = 6161799, upload-time = "2024-10-29T06:24:22.604Z" }, + { url = "https://files.pythonhosted.org/packages/09/13/5b75ae88810aaea19e846f5380611837de411181df51fd7a7d10cb178dcb/grpcio-1.67.1-cp311-cp311-win32.whl", hash = "sha256:e29ca27bec8e163dca0c98084040edec3bc49afd10f18b412f483cc68c712744", size = 3616330, upload-time = "2024-10-29T06:24:25.775Z" }, + { url = "https://files.pythonhosted.org/packages/aa/39/38117259613f68f072778c9638a61579c0cfa5678c2558706b10dd1d11d3/grpcio-1.67.1-cp311-cp311-win_amd64.whl", hash = "sha256:786a5b18544622bfb1e25cc08402bd44ea83edfb04b93798d85dca4d1a0b5be5", size = 4354535, upload-time = "2024-10-29T06:24:28.614Z" }, + { url = "https://files.pythonhosted.org/packages/6e/25/6f95bd18d5f506364379eabc0d5874873cc7dbdaf0757df8d1e82bc07a88/grpcio-1.67.1-cp312-cp312-linux_armv7l.whl", hash = "sha256:267d1745894200e4c604958da5f856da6293f063327cb049a51fe67348e4f953", size = 5089809, upload-time = "2024-10-29T06:24:31.24Z" }, + { url = "https://files.pythonhosted.org/packages/10/3f/d79e32e5d0354be33a12db2267c66d3cfeff700dd5ccdd09fd44a3ff4fb6/grpcio-1.67.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:85f69fdc1d28ce7cff8de3f9c67db2b0ca9ba4449644488c1e0303c146135ddb", size = 10981985, upload-time = "2024-10-29T06:24:34.942Z" }, + { url = "https://files.pythonhosted.org/packages/21/f2/36fbc14b3542e3a1c20fb98bd60c4732c55a44e374a4eb68f91f28f14aab/grpcio-1.67.1-cp312-cp312-manylinux_2_17_aarch64.whl", hash = "sha256:f26b0b547eb8d00e195274cdfc63ce64c8fc2d3e2d00b12bf468ece41a0423a0", size = 5588770, upload-time = "2024-10-29T06:24:38.145Z" }, + { url = "https://files.pythonhosted.org/packages/0d/af/bbc1305df60c4e65de8c12820a942b5e37f9cf684ef5e49a63fbb1476a73/grpcio-1.67.1-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4422581cdc628f77302270ff839a44f4c24fdc57887dc2a45b7e53d8fc2376af", size = 6214476, upload-time = "2024-10-29T06:24:41.006Z" }, + { url = "https://files.pythonhosted.org/packages/92/cf/1d4c3e93efa93223e06a5c83ac27e32935f998bc368e276ef858b8883154/grpcio-1.67.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1d7616d2ded471231c701489190379e0c311ee0a6c756f3c03e6a62b95a7146e", size = 5850129, upload-time = "2024-10-29T06:24:43.553Z" }, + { url = "https://files.pythonhosted.org/packages/ae/ca/26195b66cb253ac4d5ef59846e354d335c9581dba891624011da0e95d67b/grpcio-1.67.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:8a00efecde9d6fcc3ab00c13f816313c040a28450e5e25739c24f432fc6d3c75", size = 6568489, upload-time = "2024-10-29T06:24:46.453Z" }, + { url = "https://files.pythonhosted.org/packages/d1/94/16550ad6b3f13b96f0856ee5dfc2554efac28539ee84a51d7b14526da985/grpcio-1.67.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:699e964923b70f3101393710793289e42845791ea07565654ada0969522d0a38", size = 6149369, upload-time = "2024-10-29T06:24:49.112Z" }, + { url = "https://files.pythonhosted.org/packages/33/0d/4c3b2587e8ad7f121b597329e6c2620374fccbc2e4e1aa3c73ccc670fde4/grpcio-1.67.1-cp312-cp312-win32.whl", hash = "sha256:4e7b904484a634a0fff132958dabdb10d63e0927398273917da3ee103e8d1f78", size = 3599176, upload-time = "2024-10-29T06:24:51.443Z" }, + { url = "https://files.pythonhosted.org/packages/7d/36/0c03e2d80db69e2472cf81c6123aa7d14741de7cf790117291a703ae6ae1/grpcio-1.67.1-cp312-cp312-win_amd64.whl", hash = "sha256:5721e66a594a6c4204458004852719b38f3d5522082be9061d6510b455c90afc", size = 4346574, upload-time = "2024-10-29T06:24:54.587Z" }, ] [[package]] @@ -2162,9 +2294,9 @@ dependencies = [ { name = "grpcio" }, { name = "protobuf" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/7c/d7/013ef01c5a1c2fd0932c27c904934162f69f41ca0f28396d3ffe4d386123/grpcio-status-1.62.3.tar.gz", hash = "sha256:289bdd7b2459794a12cf95dc0cb727bd4a1742c37bd823f760236c937e53a485", size = 13063 } +sdist = { url = "https://files.pythonhosted.org/packages/7c/d7/013ef01c5a1c2fd0932c27c904934162f69f41ca0f28396d3ffe4d386123/grpcio-status-1.62.3.tar.gz", hash = "sha256:289bdd7b2459794a12cf95dc0cb727bd4a1742c37bd823f760236c937e53a485", size = 13063, upload-time = "2024-08-06T00:37:08.003Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/90/40/972271de05f9315c0d69f9f7ebbcadd83bc85322f538637d11bb8c67803d/grpcio_status-1.62.3-py3-none-any.whl", hash = "sha256:f9049b762ba8de6b1086789d8315846e094edac2c50beaf462338b301a8fd4b8", size = 14448 }, + { url = "https://files.pythonhosted.org/packages/90/40/972271de05f9315c0d69f9f7ebbcadd83bc85322f538637d11bb8c67803d/grpcio_status-1.62.3-py3-none-any.whl", hash = "sha256:f9049b762ba8de6b1086789d8315846e094edac2c50beaf462338b301a8fd4b8", size = 14448, upload-time = "2024-08-06T00:30:15.702Z" }, ] [[package]] @@ -2176,24 +2308,24 @@ dependencies = [ { name = "protobuf" }, { name = "setuptools" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/54/fa/b69bd8040eafc09b88bb0ec0fea59e8aacd1a801e688af087cead213b0d0/grpcio-tools-1.62.3.tar.gz", hash = "sha256:7c7136015c3d62c3eef493efabaf9e3380e3e66d24ee8e94c01cb71377f57833", size = 4538520 } +sdist = { url = "https://files.pythonhosted.org/packages/54/fa/b69bd8040eafc09b88bb0ec0fea59e8aacd1a801e688af087cead213b0d0/grpcio-tools-1.62.3.tar.gz", hash = "sha256:7c7136015c3d62c3eef493efabaf9e3380e3e66d24ee8e94c01cb71377f57833", size = 4538520, upload-time = "2024-08-06T00:37:11.035Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/23/52/2dfe0a46b63f5ebcd976570aa5fc62f793d5a8b169e211c6a5aede72b7ae/grpcio_tools-1.62.3-cp311-cp311-macosx_10_10_universal2.whl", hash = "sha256:703f46e0012af83a36082b5f30341113474ed0d91e36640da713355cd0ea5d23", size = 5147623 }, - { url = "https://files.pythonhosted.org/packages/f0/2e/29fdc6c034e058482e054b4a3c2432f84ff2e2765c1342d4f0aa8a5c5b9a/grpcio_tools-1.62.3-cp311-cp311-manylinux_2_17_aarch64.whl", hash = "sha256:7cc83023acd8bc72cf74c2edbe85b52098501d5b74d8377bfa06f3e929803492", size = 2719538 }, - { url = "https://files.pythonhosted.org/packages/f9/60/abe5deba32d9ec2c76cdf1a2f34e404c50787074a2fee6169568986273f1/grpcio_tools-1.62.3-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7ff7d58a45b75df67d25f8f144936a3e44aabd91afec833ee06826bd02b7fbe7", size = 3070964 }, - { url = "https://files.pythonhosted.org/packages/bc/ad/e2b066684c75f8d9a48508cde080a3a36618064b9cadac16d019ca511444/grpcio_tools-1.62.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f2483ea232bd72d98a6dc6d7aefd97e5bc80b15cd909b9e356d6f3e326b6e43", size = 2805003 }, - { url = "https://files.pythonhosted.org/packages/9c/3f/59bf7af786eae3f9d24ee05ce75318b87f541d0950190ecb5ffb776a1a58/grpcio_tools-1.62.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:962c84b4da0f3b14b3cdb10bc3837ebc5f136b67d919aea8d7bb3fd3df39528a", size = 3685154 }, - { url = "https://files.pythonhosted.org/packages/f1/79/4dd62478b91e27084c67b35a2316ce8a967bd8b6cb8d6ed6c86c3a0df7cb/grpcio_tools-1.62.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:8ad0473af5544f89fc5a1ece8676dd03bdf160fb3230f967e05d0f4bf89620e3", size = 3297942 }, - { url = "https://files.pythonhosted.org/packages/b8/cb/86449ecc58bea056b52c0b891f26977afc8c4464d88c738f9648da941a75/grpcio_tools-1.62.3-cp311-cp311-win32.whl", hash = "sha256:db3bc9fa39afc5e4e2767da4459df82b095ef0cab2f257707be06c44a1c2c3e5", size = 910231 }, - { url = "https://files.pythonhosted.org/packages/45/a4/9736215e3945c30ab6843280b0c6e1bff502910156ea2414cd77fbf1738c/grpcio_tools-1.62.3-cp311-cp311-win_amd64.whl", hash = "sha256:e0898d412a434e768a0c7e365acabe13ff1558b767e400936e26b5b6ed1ee51f", size = 1052496 }, - { url = "https://files.pythonhosted.org/packages/2a/a5/d6887eba415ce318ae5005e8dfac3fa74892400b54b6d37b79e8b4f14f5e/grpcio_tools-1.62.3-cp312-cp312-macosx_10_10_universal2.whl", hash = "sha256:d102b9b21c4e1e40af9a2ab3c6d41afba6bd29c0aa50ca013bf85c99cdc44ac5", size = 5147690 }, - { url = "https://files.pythonhosted.org/packages/8a/7c/3cde447a045e83ceb4b570af8afe67ffc86896a2fe7f59594dc8e5d0a645/grpcio_tools-1.62.3-cp312-cp312-manylinux_2_17_aarch64.whl", hash = "sha256:0a52cc9444df978438b8d2332c0ca99000521895229934a59f94f37ed896b133", size = 2720538 }, - { url = "https://files.pythonhosted.org/packages/88/07/f83f2750d44ac4f06c07c37395b9c1383ef5c994745f73c6bfaf767f0944/grpcio_tools-1.62.3-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:141d028bf5762d4a97f981c501da873589df3f7e02f4c1260e1921e565b376fa", size = 3071571 }, - { url = "https://files.pythonhosted.org/packages/37/74/40175897deb61e54aca716bc2e8919155b48f33aafec8043dda9592d8768/grpcio_tools-1.62.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47a5c093ab256dec5714a7a345f8cc89315cb57c298b276fa244f37a0ba507f0", size = 2806207 }, - { url = "https://files.pythonhosted.org/packages/ec/ee/d8de915105a217cbcb9084d684abdc032030dcd887277f2ef167372287fe/grpcio_tools-1.62.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:f6831fdec2b853c9daa3358535c55eed3694325889aa714070528cf8f92d7d6d", size = 3685815 }, - { url = "https://files.pythonhosted.org/packages/fd/d9/4360a6c12be3d7521b0b8c39e5d3801d622fbb81cc2721dbd3eee31e28c8/grpcio_tools-1.62.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:e02d7c1a02e3814c94ba0cfe43d93e872c758bd8fd5c2797f894d0c49b4a1dfc", size = 3298378 }, - { url = "https://files.pythonhosted.org/packages/29/3b/7cdf4a9e5a3e0a35a528b48b111355cd14da601413a4f887aa99b6da468f/grpcio_tools-1.62.3-cp312-cp312-win32.whl", hash = "sha256:b881fd9505a84457e9f7e99362eeedd86497b659030cf57c6f0070df6d9c2b9b", size = 910416 }, - { url = "https://files.pythonhosted.org/packages/6c/66/dd3ec249e44c1cc15e902e783747819ed41ead1336fcba72bf841f72c6e9/grpcio_tools-1.62.3-cp312-cp312-win_amd64.whl", hash = "sha256:11c625eebefd1fd40a228fc8bae385e448c7e32a6ae134e43cf13bbc23f902b7", size = 1052856 }, + { url = "https://files.pythonhosted.org/packages/23/52/2dfe0a46b63f5ebcd976570aa5fc62f793d5a8b169e211c6a5aede72b7ae/grpcio_tools-1.62.3-cp311-cp311-macosx_10_10_universal2.whl", hash = "sha256:703f46e0012af83a36082b5f30341113474ed0d91e36640da713355cd0ea5d23", size = 5147623, upload-time = "2024-08-06T00:30:54.894Z" }, + { url = "https://files.pythonhosted.org/packages/f0/2e/29fdc6c034e058482e054b4a3c2432f84ff2e2765c1342d4f0aa8a5c5b9a/grpcio_tools-1.62.3-cp311-cp311-manylinux_2_17_aarch64.whl", hash = "sha256:7cc83023acd8bc72cf74c2edbe85b52098501d5b74d8377bfa06f3e929803492", size = 2719538, upload-time = "2024-08-06T00:30:57.928Z" }, + { url = "https://files.pythonhosted.org/packages/f9/60/abe5deba32d9ec2c76cdf1a2f34e404c50787074a2fee6169568986273f1/grpcio_tools-1.62.3-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7ff7d58a45b75df67d25f8f144936a3e44aabd91afec833ee06826bd02b7fbe7", size = 3070964, upload-time = "2024-08-06T00:31:00.267Z" }, + { url = "https://files.pythonhosted.org/packages/bc/ad/e2b066684c75f8d9a48508cde080a3a36618064b9cadac16d019ca511444/grpcio_tools-1.62.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f2483ea232bd72d98a6dc6d7aefd97e5bc80b15cd909b9e356d6f3e326b6e43", size = 2805003, upload-time = "2024-08-06T00:31:02.565Z" }, + { url = "https://files.pythonhosted.org/packages/9c/3f/59bf7af786eae3f9d24ee05ce75318b87f541d0950190ecb5ffb776a1a58/grpcio_tools-1.62.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:962c84b4da0f3b14b3cdb10bc3837ebc5f136b67d919aea8d7bb3fd3df39528a", size = 3685154, upload-time = "2024-08-06T00:31:05.339Z" }, + { url = "https://files.pythonhosted.org/packages/f1/79/4dd62478b91e27084c67b35a2316ce8a967bd8b6cb8d6ed6c86c3a0df7cb/grpcio_tools-1.62.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:8ad0473af5544f89fc5a1ece8676dd03bdf160fb3230f967e05d0f4bf89620e3", size = 3297942, upload-time = "2024-08-06T00:31:08.456Z" }, + { url = "https://files.pythonhosted.org/packages/b8/cb/86449ecc58bea056b52c0b891f26977afc8c4464d88c738f9648da941a75/grpcio_tools-1.62.3-cp311-cp311-win32.whl", hash = "sha256:db3bc9fa39afc5e4e2767da4459df82b095ef0cab2f257707be06c44a1c2c3e5", size = 910231, upload-time = "2024-08-06T00:31:11.464Z" }, + { url = "https://files.pythonhosted.org/packages/45/a4/9736215e3945c30ab6843280b0c6e1bff502910156ea2414cd77fbf1738c/grpcio_tools-1.62.3-cp311-cp311-win_amd64.whl", hash = "sha256:e0898d412a434e768a0c7e365acabe13ff1558b767e400936e26b5b6ed1ee51f", size = 1052496, upload-time = "2024-08-06T00:31:13.665Z" }, + { url = "https://files.pythonhosted.org/packages/2a/a5/d6887eba415ce318ae5005e8dfac3fa74892400b54b6d37b79e8b4f14f5e/grpcio_tools-1.62.3-cp312-cp312-macosx_10_10_universal2.whl", hash = "sha256:d102b9b21c4e1e40af9a2ab3c6d41afba6bd29c0aa50ca013bf85c99cdc44ac5", size = 5147690, upload-time = "2024-08-06T00:31:16.436Z" }, + { url = "https://files.pythonhosted.org/packages/8a/7c/3cde447a045e83ceb4b570af8afe67ffc86896a2fe7f59594dc8e5d0a645/grpcio_tools-1.62.3-cp312-cp312-manylinux_2_17_aarch64.whl", hash = "sha256:0a52cc9444df978438b8d2332c0ca99000521895229934a59f94f37ed896b133", size = 2720538, upload-time = "2024-08-06T00:31:18.905Z" }, + { url = "https://files.pythonhosted.org/packages/88/07/f83f2750d44ac4f06c07c37395b9c1383ef5c994745f73c6bfaf767f0944/grpcio_tools-1.62.3-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:141d028bf5762d4a97f981c501da873589df3f7e02f4c1260e1921e565b376fa", size = 3071571, upload-time = "2024-08-06T00:31:21.684Z" }, + { url = "https://files.pythonhosted.org/packages/37/74/40175897deb61e54aca716bc2e8919155b48f33aafec8043dda9592d8768/grpcio_tools-1.62.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47a5c093ab256dec5714a7a345f8cc89315cb57c298b276fa244f37a0ba507f0", size = 2806207, upload-time = "2024-08-06T00:31:24.208Z" }, + { url = "https://files.pythonhosted.org/packages/ec/ee/d8de915105a217cbcb9084d684abdc032030dcd887277f2ef167372287fe/grpcio_tools-1.62.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:f6831fdec2b853c9daa3358535c55eed3694325889aa714070528cf8f92d7d6d", size = 3685815, upload-time = "2024-08-06T00:31:26.917Z" }, + { url = "https://files.pythonhosted.org/packages/fd/d9/4360a6c12be3d7521b0b8c39e5d3801d622fbb81cc2721dbd3eee31e28c8/grpcio_tools-1.62.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:e02d7c1a02e3814c94ba0cfe43d93e872c758bd8fd5c2797f894d0c49b4a1dfc", size = 3298378, upload-time = "2024-08-06T00:31:30.401Z" }, + { url = "https://files.pythonhosted.org/packages/29/3b/7cdf4a9e5a3e0a35a528b48b111355cd14da601413a4f887aa99b6da468f/grpcio_tools-1.62.3-cp312-cp312-win32.whl", hash = "sha256:b881fd9505a84457e9f7e99362eeedd86497b659030cf57c6f0070df6d9c2b9b", size = 910416, upload-time = "2024-08-06T00:31:33.118Z" }, + { url = "https://files.pythonhosted.org/packages/6c/66/dd3ec249e44c1cc15e902e783747819ed41ead1336fcba72bf841f72c6e9/grpcio_tools-1.62.3-cp312-cp312-win_amd64.whl", hash = "sha256:11c625eebefd1fd40a228fc8bae385e448c7e32a6ae134e43cf13bbc23f902b7", size = 1052856, upload-time = "2024-08-06T00:31:36.519Z" }, ] [[package]] @@ -2203,18 +2335,18 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "packaging" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/34/72/9614c465dc206155d93eff0ca20d42e1e35afc533971379482de953521a4/gunicorn-23.0.0.tar.gz", hash = "sha256:f014447a0101dc57e294f6c18ca6b40227a4c90e9bdb586042628030cba004ec", size = 375031 } +sdist = { url = "https://files.pythonhosted.org/packages/34/72/9614c465dc206155d93eff0ca20d42e1e35afc533971379482de953521a4/gunicorn-23.0.0.tar.gz", hash = "sha256:f014447a0101dc57e294f6c18ca6b40227a4c90e9bdb586042628030cba004ec", size = 375031, upload-time = "2024-08-10T20:25:27.378Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/cb/7d/6dac2a6e1eba33ee43f318edbed4ff29151a49b5d37f080aad1e6469bca4/gunicorn-23.0.0-py3-none-any.whl", hash = "sha256:ec400d38950de4dfd418cff8328b2c8faed0edb0d517d3394e457c317908ca4d", size = 85029 }, + { url = "https://files.pythonhosted.org/packages/cb/7d/6dac2a6e1eba33ee43f318edbed4ff29151a49b5d37f080aad1e6469bca4/gunicorn-23.0.0-py3-none-any.whl", hash = "sha256:ec400d38950de4dfd418cff8328b2c8faed0edb0d517d3394e457c317908ca4d", size = 85029, upload-time = "2024-08-10T20:25:24.996Z" }, ] [[package]] name = "h11" -version = "0.14.0" +version = "0.16.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f5/38/3af3d3633a34a3316095b39c8e8fb4853a28a536e55d347bd8d8e9a14b03/h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d", size = 100418 } +sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/95/04/ff642e65ad6b90db43e668d70ffb6736436c7ce41fcc549f4e9472234127/h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761", size = 58259 }, + { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" }, ] [[package]] @@ -2225,56 +2357,71 @@ dependencies = [ { name = "hpack" }, { name = "hyperframe" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/1b/38/d7f80fd13e6582fb8e0df8c9a653dcc02b03ca34f4d72f34869298c5baf8/h2-4.2.0.tar.gz", hash = "sha256:c8a52129695e88b1a0578d8d2cc6842bbd79128ac685463b887ee278126ad01f", size = 2150682 } +sdist = { url = "https://files.pythonhosted.org/packages/1b/38/d7f80fd13e6582fb8e0df8c9a653dcc02b03ca34f4d72f34869298c5baf8/h2-4.2.0.tar.gz", hash = "sha256:c8a52129695e88b1a0578d8d2cc6842bbd79128ac685463b887ee278126ad01f", size = 2150682, upload-time = "2025-02-02T07:43:51.815Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d0/9e/984486f2d0a0bd2b024bf4bc1c62688fcafa9e61991f041fb0e2def4a982/h2-4.2.0-py3-none-any.whl", hash = "sha256:479a53ad425bb29af087f3458a61d30780bc818e4ebcf01f0b536ba916462ed0", size = 60957, upload-time = "2025-02-01T11:02:26.481Z" }, +] + +[[package]] +name = "hf-xet" +version = "1.1.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/95/be/58f20728a5b445f8b064e74f0618897b3439f5ef90934da1916b9dfac76f/hf_xet-1.1.2.tar.gz", hash = "sha256:3712d6d4819d3976a1c18e36db9f503e296283f9363af818f50703506ed63da3", size = 467009, upload-time = "2025-05-16T20:44:34.944Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d0/9e/984486f2d0a0bd2b024bf4bc1c62688fcafa9e61991f041fb0e2def4a982/h2-4.2.0-py3-none-any.whl", hash = "sha256:479a53ad425bb29af087f3458a61d30780bc818e4ebcf01f0b536ba916462ed0", size = 60957 }, + { url = "https://files.pythonhosted.org/packages/45/ae/f1a63f75d9886f18a80220ba31a1c7b9c4752f03aae452f358f538c6a991/hf_xet-1.1.2-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:dfd1873fd648488c70735cb60f7728512bca0e459e61fcd107069143cd798469", size = 2642559, upload-time = "2025-05-16T20:44:30.217Z" }, + { url = "https://files.pythonhosted.org/packages/50/ab/d2c83ae18f1015d926defd5bfbe94c62d15e93f900e6a192e318ee947105/hf_xet-1.1.2-cp37-abi3-macosx_11_0_arm64.whl", hash = "sha256:29b584983b2d977c44157d9241dcf0fd50acde0b7bff8897fe4386912330090d", size = 2541360, upload-time = "2025-05-16T20:44:29.056Z" }, + { url = "https://files.pythonhosted.org/packages/9f/a7/693dc9f34f979e30a378125e2150a0b2d8d166e6d83ce3950eeb81e560aa/hf_xet-1.1.2-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b29ac84298147fe9164cc55ad994ba47399f90b5d045b0b803b99cf5f06d8ec", size = 5183081, upload-time = "2025-05-16T20:44:27.505Z" }, + { url = "https://files.pythonhosted.org/packages/3d/23/c48607883f692a36c0a7735f47f98bad32dbe459a32d1568c0f21576985d/hf_xet-1.1.2-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:d921ba32615676e436a0d15e162331abc9ed43d440916b1d836dc27ce1546173", size = 5356100, upload-time = "2025-05-16T20:44:25.681Z" }, + { url = "https://files.pythonhosted.org/packages/eb/5b/b2316c7f1076da0582b52ea228f68bea95e243c388440d1dc80297c9d813/hf_xet-1.1.2-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:d9b03c34e13c44893ab6e8fea18ee8d2a6878c15328dd3aabedbdd83ee9f2ed3", size = 5647688, upload-time = "2025-05-16T20:44:31.867Z" }, + { url = "https://files.pythonhosted.org/packages/2c/98/e6995f0fa579929da7795c961f403f4ee84af36c625963f52741d56f242c/hf_xet-1.1.2-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:01b18608955b3d826307d37da8bd38b28a46cd2d9908b3a3655d1363274f941a", size = 5322627, upload-time = "2025-05-16T20:44:33.677Z" }, + { url = "https://files.pythonhosted.org/packages/59/40/8f1d5a44a64d8bf9e3c19576e789f716af54875b46daae65426714e75db1/hf_xet-1.1.2-cp37-abi3-win_amd64.whl", hash = "sha256:3562902c81299b09f3582ddfb324400c6a901a2f3bc854f83556495755f4954c", size = 2739542, upload-time = "2025-05-16T20:44:36.287Z" }, ] [[package]] name = "hiredis" -version = "3.1.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/38/e5/789cfa8993ced0061a6ef7ea758302ef5cf3439629bf0d39c85a6ede4641/hiredis-3.1.0.tar.gz", hash = "sha256:51d40ac3611091020d7dea6b05ed62cb152bff595fa4f931e7b6479d777acf7c", size = 87616 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/7c/85/9f738bab9f446e40a3a29aff0aa7766568b2680407e862667eaa3ec78bfe/hiredis-3.1.0-cp311-cp311-macosx_10_15_universal2.whl", hash = "sha256:c339ff4b4739b2a40da463763dd566129762f72926bca611ad9a457a9fe64abd", size = 81205 }, - { url = "https://files.pythonhosted.org/packages/ad/9c/c64ddce9768c3a95797db10f85ed80f80389693b558801a0256e7c8ea3db/hiredis-3.1.0-cp311-cp311-macosx_10_15_x86_64.whl", hash = "sha256:0ffa2552f704a45954627697a378fc2f559004e53055b82f00daf30bd4305330", size = 44479 }, - { url = "https://files.pythonhosted.org/packages/65/68/b0d0909f86b01bb7be738be306dc536431f2af90a42155a2fafa05d528b9/hiredis-3.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9acf7f0e7106f631cd618eb60ec9bbd6e43045addd5310f66ba1177209567e59", size = 42422 }, - { url = "https://files.pythonhosted.org/packages/20/3a/625227d3c26ee69ef0f5881c2e329f19d1d5fe6a880a0b5f7eaf2a1ae6ab/hiredis-3.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ea4f5ecf9dbea93c827486f59c606684c3496ea71c7ba9a8131932780696e61a", size = 166230 }, - { url = "https://files.pythonhosted.org/packages/b9/e1/c14f3c66c42f5746cd54156584dcf60540a9063f351e101e99fd074e80ae/hiredis-3.1.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:39efab176fca3d5111075f6ba56cd864f18db46d858289d39360c5672e0e5c3e", size = 177251 }, - { url = "https://files.pythonhosted.org/packages/1d/f4/a1d6972feb3be634ae7cdf719a56d5c7a8334f4202a05935b9c1b53d5ef6/hiredis-3.1.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1110eae007f30e70a058d743e369c24430327cd01fd97d99519d6794a58dd587", size = 166696 }, - { url = "https://files.pythonhosted.org/packages/87/6f/630581e3c62a4651cb914da1619ddeb7b07f182e74748277244df914c107/hiredis-3.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9b390f63191bcccbb6044d4c118acdf4fa55f38e5658ac4cfd5a33a6f0c07659", size = 166463 }, - { url = "https://files.pythonhosted.org/packages/fd/7b/bcf5562fa50cdce19169d48bb3bc25690c27fde321f147b68781140c9d5d/hiredis-3.1.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:72a98ccc7b8ec9ce0100ecf59f45f05d2023606e8e3676b07a316d1c1c364072", size = 162461 }, - { url = "https://files.pythonhosted.org/packages/f3/bd/902a6ad2832f6a517bc13b2fe30ee1f45714c4922faa6eb61c0113314dbc/hiredis-3.1.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:7c76e751fd1e2f221dec09cdc24040ee486886e943d5d7ffc256e8cf15c75e51", size = 160376 }, - { url = "https://files.pythonhosted.org/packages/12/07/2f4be5e827d5c7d59061f2dfc882ceceb60eb9a263e8eebfbc0093b9c75d/hiredis-3.1.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:7d3880f213b6f14e9c69ce52beffd1748eecc8669698c4782761887273b6e1bd", size = 159601 }, - { url = "https://files.pythonhosted.org/packages/2b/5e/ee606c694ac086ba28753b02d842868118830bcb1fb47e25091484677bec/hiredis-3.1.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:87c2b3fe7e7c96eba376506a76e11514e07e848f737b254e0973e4b5c3a491e9", size = 171404 }, - { url = "https://files.pythonhosted.org/packages/c3/1a/c2afd5ebb556ad06fe8ab99d1917709e3b0c4ee07f503ca31dab8d66ef1e/hiredis-3.1.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:d3cfb4089e96f8f8ee9554da93148a9261aa6612ad2cc202c1a494c7b712e31f", size = 163846 }, - { url = "https://files.pythonhosted.org/packages/89/79/e1f0097a53110622c00c51f747f3edec69e24b74539ff23f68085dc547b4/hiredis-3.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4f12018e5c5f866a1c3f7017cb2d88e5c6f9440df2281e48865a2b6c40f247f4", size = 161469 }, - { url = "https://files.pythonhosted.org/packages/aa/ca/531e287fc5c066d9f39bbc3a6a50ac34c84425c06bf2001af4bd2337037a/hiredis-3.1.0-cp311-cp311-win32.whl", hash = "sha256:107b66ce977bb2dff8f2239e68344360a75d05fed3d9fa0570ac4d3020ce2396", size = 20086 }, - { url = "https://files.pythonhosted.org/packages/b1/e1/c555f03a189624ed600118d39ab775e5d507e521a61db1a1dfa1c20f3d02/hiredis-3.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:8f1240bde53d3d1676f0aba61b3661560dc9a681cae24d9de33e650864029aa4", size = 21915 }, - { url = "https://files.pythonhosted.org/packages/cc/64/9f9c1648853cd34e52b2af04c26cebb7f086cb4cd8ce056fecedd7664be9/hiredis-3.1.0-cp312-cp312-macosx_10_15_universal2.whl", hash = "sha256:f7c7f89e0bc4246115754e2eda078a111282f6d6ecc6fb458557b724fe6f2aac", size = 81304 }, - { url = "https://files.pythonhosted.org/packages/42/18/f70f8366c4abcbb830480d72968502192e422ebd60b7ca5f7739872e78cd/hiredis-3.1.0-cp312-cp312-macosx_10_15_x86_64.whl", hash = "sha256:3dbf9163296fa45fbddcfc4c5900f10e9ddadda37117dbfb641e327e536b53e0", size = 44551 }, - { url = "https://files.pythonhosted.org/packages/a8/a0/bf584a34a8b8e7194c3386700113cd7380a585c3e37b57b45bcf036a3305/hiredis-3.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:af46a4be0e82df470f68f35316fa16cd1e134d1c5092fc1082e1aad64cce716d", size = 42471 }, - { url = "https://files.pythonhosted.org/packages/97/90/a709dad5fcfa6a3d0480709fd9e24d1e0ba70cbe4b853a1fe63cf7026207/hiredis-3.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bc63d698c43aea500a84d8b083f830c03808b6cf3933ae4d35a27f0a3d881652", size = 168205 }, - { url = "https://files.pythonhosted.org/packages/14/29/33f943cc874d4cc6269d472b2c8ebb7385008fbde192aa5108d617d99504/hiredis-3.1.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:676b3d88674134bfaaf70dac181d1790b0f33b3187bfb9da9221e17e0e624f83", size = 179055 }, - { url = "https://files.pythonhosted.org/packages/2b/b2/a1315d474ec36c89e68ac8a3a258431b6f266af7bc4a31265a9527e494df/hiredis-3.1.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:aed10d9df1e2fb0011db2713ac64497462e9c2c0208b648c97569da772b959ca", size = 168484 }, - { url = "https://files.pythonhosted.org/packages/a1/4f/14aca28a24463b92274464000691610eb41a9afab1e16a7a739be496f274/hiredis-3.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b5bd8adfe8742e331a94cccd782bffea251fa70d9a709e71f4510f50794d700", size = 169050 }, - { url = "https://files.pythonhosted.org/packages/77/8d/e5aa6857a70c0e3ca423973ea27065fa3cf2567d25cc397b649a1d45043e/hiredis-3.1.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9fc4e35b4afb0af6da55495dd0742ad32ab88150428a6ecdbb3085cbd60714e8", size = 164624 }, - { url = "https://files.pythonhosted.org/packages/62/5d/c167de0a8c841cb4ea0e25a8145bbdb7e33b5028eaf905cd0901381f0a83/hiredis-3.1.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:89b83e76eb00ab0464e7b0752a3ffcb02626e742e9509bc141424a9c3202e8dc", size = 162461 }, - { url = "https://files.pythonhosted.org/packages/70/b8/fa7e9ae73237999a5c7eb9f59e6c2198ed65eca5cad948b85e2c82c12cc2/hiredis-3.1.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:98ebf08c907836b70a8f40e030df8ab6f174dc7f6fa765251d813e89f14069d8", size = 161292 }, - { url = "https://files.pythonhosted.org/packages/04/af/6b6db2d29e2455e97cbf7e19bae0ef1a6e5b61c08d42377a3511ef9cc3bb/hiredis-3.1.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:6c840b9cec086328f2ee2cfee0038b5d6bbb514bac7b5e579da6e346eaac056c", size = 173465 }, - { url = "https://files.pythonhosted.org/packages/dc/50/c49d53832d71e1fdb1fe7c91a99b2d47043655cb0d535437264dccc19e2e/hiredis-3.1.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:c5c44e9fa6f4462d0330cb5f5d46fa652512fc86b41d4d1974d0356f263e9105", size = 165818 }, - { url = "https://files.pythonhosted.org/packages/5f/47/81992b4b27b59152abf7e279c4adba7a5a0e1d99ccbee674a82c6e65b9bf/hiredis-3.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e665b14ab50aa175cfa306fcb00fffd4e3ff02ceb36ca6a4df00b1246d6a73c4", size = 163871 }, - { url = "https://files.pythonhosted.org/packages/dd/f6/1ee81c373a2087557c6020bf201b4d27d6aec173c8414c3d06900e91d9bd/hiredis-3.1.0-cp312-cp312-win32.whl", hash = "sha256:bd33db977ac7af97e8d035ffadb163b00546be22e5f1297b2123f5f9bf0f8a21", size = 20206 }, - { url = "https://files.pythonhosted.org/packages/b7/67/46d5a8d44812c6293c8088d642e473b0dd9e12478ef539eb4a77df643450/hiredis-3.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:37aed4aa9348600145e2d019c7be27855e503ecc4906c6976ff2f3b52e3d5d97", size = 21997 }, +version = "3.2.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f7/08/24b72f425b75e1de7442fb1740f69ca66d5820b9f9c0e2511ff9aadab3b7/hiredis-3.2.1.tar.gz", hash = "sha256:5a5f64479bf04dd829fe7029fad0ea043eac4023abc6e946668cbbec3493a78d", size = 89096, upload-time = "2025-05-23T11:41:57.227Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/48/84/2ea9636f2ba0811d9eb3bebbbfa84f488238180ddab70c9cb7fa13419d78/hiredis-3.2.1-cp311-cp311-macosx_10_15_universal2.whl", hash = "sha256:e4ae0be44cab5e74e6e4c4a93d04784629a45e781ff483b136cc9e1b9c23975c", size = 82425, upload-time = "2025-05-23T11:39:54.135Z" }, + { url = "https://files.pythonhosted.org/packages/fc/24/b9ebf766a99998fda3975937afa4912e98de9d7f8d0b83f48096bdd961c1/hiredis-3.2.1-cp311-cp311-macosx_10_15_x86_64.whl", hash = "sha256:24647e84c9f552934eb60b7f3d2116f8b64a7020361da9369e558935ca45914d", size = 45231, upload-time = "2025-05-23T11:39:55.455Z" }, + { url = "https://files.pythonhosted.org/packages/68/4c/c009b4d9abeb964d607f0987561892d1589907f770b9e5617552b34a4a4d/hiredis-3.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6fb3e92d1172da8decc5f836bf8b528c0fc9b6d449f1353e79ceeb9dc1801132", size = 43240, upload-time = "2025-05-23T11:39:57.8Z" }, + { url = "https://files.pythonhosted.org/packages/e9/83/d53f3ae9e4ac51b8a35afb7ccd68db871396ed1d7c8ba02ce2c30de0cf17/hiredis-3.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:38ba7a32e51e518b6b3e470142e52ed2674558e04d7d73d86eb19ebcb37d7d40", size = 169624, upload-time = "2025-05-23T11:40:00.055Z" }, + { url = "https://files.pythonhosted.org/packages/91/2f/f9f091526e22a45385d45f3870204dc78aee365b6fe32e679e65674da6a7/hiredis-3.2.1-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4fc632be73174891d6bb71480247e57b2fd8f572059f0a1153e4d0339e919779", size = 165799, upload-time = "2025-05-23T11:40:01.194Z" }, + { url = "https://files.pythonhosted.org/packages/1c/cc/e561274438cdb19794f0638136a5a99a9ca19affcb42679b12a78016b8ad/hiredis-3.2.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f03e6839ff21379ad3c195e0700fc9c209e7f344946dea0f8a6d7b5137a2a141", size = 180612, upload-time = "2025-05-23T11:40:02.385Z" }, + { url = "https://files.pythonhosted.org/packages/83/ba/a8a989f465191d55672e57aea2a331bfa3a74b5cbc6f590031c9e11f7491/hiredis-3.2.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:99983873e37c71bb71deb544670ff4f9d6920dab272aaf52365606d87a4d6c73", size = 169934, upload-time = "2025-05-23T11:40:03.524Z" }, + { url = "https://files.pythonhosted.org/packages/52/5f/1148e965df1c67b17bdcaef199f54aec3def0955d19660a39c6ee10a6f55/hiredis-3.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ffd982c419f48e3a57f592678c72474429465bb4bfc96472ec805f5d836523f0", size = 170074, upload-time = "2025-05-23T11:40:04.618Z" }, + { url = "https://files.pythonhosted.org/packages/43/5e/e6846ad159a938b539fb8d472e2e68cb6758d7c9454ea0520211f335ea72/hiredis-3.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:bc993f4aa4abc029347f309e722f122e05a3b8a0c279ae612849b5cc9dc69f2d", size = 164158, upload-time = "2025-05-23T11:40:05.653Z" }, + { url = "https://files.pythonhosted.org/packages/0a/a1/5891e0615f0993f194c1b51a65aaac063b0db318a70df001b28e49f0579d/hiredis-3.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:dde790d420081f18b5949227649ccb3ed991459df33279419a25fcae7f97cd92", size = 162591, upload-time = "2025-05-23T11:40:07.041Z" }, + { url = "https://files.pythonhosted.org/packages/d4/da/8bce52ca81716f53c1014f689aea4c170ba6411e6848f81a1bed1fc375eb/hiredis-3.2.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:b0c8cae7edbef860afcf3177b705aef43e10b5628f14d5baf0ec69668247d08d", size = 174808, upload-time = "2025-05-23T11:40:09.146Z" }, + { url = "https://files.pythonhosted.org/packages/84/91/fc1ef444ed4dc432b5da9b48e9bd23266c703528db7be19e2b608d67ba06/hiredis-3.2.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:e8a90eaca7e1ce7f175584f07a2cdbbcab13f4863f9f355d7895c4d28805f65b", size = 167060, upload-time = "2025-05-23T11:40:10.757Z" }, + { url = "https://files.pythonhosted.org/packages/66/ad/beebf73a5455f232b97e00564d1e8ad095d4c6e18858c60c6cfdd893ac1e/hiredis-3.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:476031958fa44e245e803827e0787d49740daa4de708fe514370293ce519893a", size = 164833, upload-time = "2025-05-23T11:40:12.001Z" }, + { url = "https://files.pythonhosted.org/packages/75/79/a9591bdc0148c0fbdf54cf6f3d449932d3b3b8779e87f33fa100a5a8088f/hiredis-3.2.1-cp311-cp311-win32.whl", hash = "sha256:eb3f5df2a9593b4b4b676dce3cea53b9c6969fc372875188589ddf2bafc7f624", size = 20402, upload-time = "2025-05-23T11:40:13.216Z" }, + { url = "https://files.pythonhosted.org/packages/9f/05/c93cc6fab31e3c01b671126c82f44372fb211facb8bd4571fd372f50898d/hiredis-3.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:1402e763d8a9fdfcc103bbf8b2913971c0a3f7b8a73deacbda3dfe5f3a9d1e0b", size = 22085, upload-time = "2025-05-23T11:40:14.19Z" }, + { url = "https://files.pythonhosted.org/packages/60/a1/6da1578a22df1926497f7a3f6a3d2408fe1d1559f762c1640af5762a8eb6/hiredis-3.2.1-cp312-cp312-macosx_10_15_universal2.whl", hash = "sha256:3742d8b17e73c198cabeab11da35f2e2a81999d406f52c6275234592256bf8e8", size = 82627, upload-time = "2025-05-23T11:40:15.362Z" }, + { url = "https://files.pythonhosted.org/packages/6c/b1/1056558ca8dc330be5bb25162fe5f268fee71571c9a535153df9f871a073/hiredis-3.2.1-cp312-cp312-macosx_10_15_x86_64.whl", hash = "sha256:9c2f3176fb617a79f6cccf22cb7d2715e590acb534af6a82b41f8196ad59375d", size = 45404, upload-time = "2025-05-23T11:40:16.72Z" }, + { url = "https://files.pythonhosted.org/packages/58/4f/13d1fa1a6b02a99e9fed8f546396f2d598c3613c98e6c399a3284fa65361/hiredis-3.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a8bd46189c7fa46174e02670dc44dfecb60f5bd4b67ed88cb050d8f1fd842f09", size = 43299, upload-time = "2025-05-23T11:40:17.697Z" }, + { url = "https://files.pythonhosted.org/packages/c0/25/ddfac123ba5a32eb1f0b40ba1b2ec98a599287f7439def8856c3c7e5dd0d/hiredis-3.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f86ee4488c8575b58139cdfdddeae17f91e9a893ffee20260822add443592e2f", size = 172194, upload-time = "2025-05-23T11:40:19.143Z" }, + { url = "https://files.pythonhosted.org/packages/2c/1e/443a3703ce570b631ca43494094fbaeb051578a0ebe4bfcefde351e1ba25/hiredis-3.2.1-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3717832f4a557b2fe7060b9d4a7900e5de287a15595e398c3f04df69019ca69d", size = 168429, upload-time = "2025-05-23T11:40:20.329Z" }, + { url = "https://files.pythonhosted.org/packages/3b/d6/0d8c6c706ed79b2298c001b5458c055615e3166533dcee3900e821a18a3e/hiredis-3.2.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e5cb12c21fb9e2403d28c4e6a38120164973342d34d08120f2d7009b66785644", size = 182967, upload-time = "2025-05-23T11:40:21.921Z" }, + { url = "https://files.pythonhosted.org/packages/da/68/da8dd231fbce858b5a20ab7d7bf558912cd125f08bac4c778865ef5fe2c2/hiredis-3.2.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:080fda1510bbd389af91f919c11a4f2aa4d92f0684afa4709236faa084a42cac", size = 172495, upload-time = "2025-05-23T11:40:23.105Z" }, + { url = "https://files.pythonhosted.org/packages/65/25/83a31420535e2778662caa95533d5c997011fa6a88331f0cdb22afea9ec3/hiredis-3.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1252e10a1f3273d1c6bf2021e461652c2e11b05b83e0915d6eb540ec7539afe2", size = 173142, upload-time = "2025-05-23T11:40:24.24Z" }, + { url = "https://files.pythonhosted.org/packages/41/d7/cb907348889eb75e2aa2e6b63e065b611459e0f21fe1e371a968e13f0d55/hiredis-3.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d9e320e99ab7d2a30dc91ff6f745ba38d39b23f43d345cdee9881329d7b511d6", size = 166433, upload-time = "2025-05-23T11:40:25.287Z" }, + { url = "https://files.pythonhosted.org/packages/01/5d/7cbc69d82af7b29a95723d50f5261555ba3d024bfbdc414bdc3d23c0defb/hiredis-3.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:641668f385f16550fdd6fdc109b0af6988b94ba2acc06770a5e06a16e88f320c", size = 164883, upload-time = "2025-05-23T11:40:26.454Z" }, + { url = "https://files.pythonhosted.org/packages/f9/00/f995b1296b1d7e0247651347aa230f3225a9800e504fdf553cf7cd001cf7/hiredis-3.2.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:1e1f44208c39d6c345ff451f82f21e9eeda6fe9af4ac65972cc3eeb58d41f7cb", size = 177262, upload-time = "2025-05-23T11:40:27.576Z" }, + { url = "https://files.pythonhosted.org/packages/c5/f3/723a67d729e94764ce9e0d73fa5f72a0f87d3ce3c98c9a0b27cbf001cc79/hiredis-3.2.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:f882a0d6415fffe1ffcb09e6281d0ba8b1ece470e866612bbb24425bf76cf397", size = 169619, upload-time = "2025-05-23T11:40:29.671Z" }, + { url = "https://files.pythonhosted.org/packages/45/58/f69028df00fb1b223e221403f3be2059ae86031e7885f955d26236bdfc17/hiredis-3.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b4e78719a0730ebffe335528531d154bc8867a246418f74ecd88adbc4d938c49", size = 167303, upload-time = "2025-05-23T11:40:30.902Z" }, + { url = "https://files.pythonhosted.org/packages/2b/7d/567411e65cce76cf265a9a4f837fd2ebc564bef6368dd42ac03f7a517c0a/hiredis-3.2.1-cp312-cp312-win32.whl", hash = "sha256:33c4604d9f79a13b84da79950a8255433fca7edaf292bbd3364fd620864ed7b2", size = 20551, upload-time = "2025-05-23T11:40:32.69Z" }, + { url = "https://files.pythonhosted.org/packages/90/74/b4c291eb4a4a874b3690ff9fc311a65d5292072556421b11b1d786e3e1d0/hiredis-3.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:7b9749375bf9d171aab8813694f379f2cff0330d7424000f5e92890ad4932dc9", size = 22128, upload-time = "2025-05-23T11:40:33.686Z" }, ] [[package]] name = "hpack" version = "4.1.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/2c/48/71de9ed269fdae9c8057e5a4c0aa7402e8bb16f2c6e90b3aa53327b113f8/hpack-4.1.0.tar.gz", hash = "sha256:ec5eca154f7056aa06f196a557655c5b009b382873ac8d1e66e79e87535f1dca", size = 51276 } +sdist = { url = "https://files.pythonhosted.org/packages/2c/48/71de9ed269fdae9c8057e5a4c0aa7402e8bb16f2c6e90b3aa53327b113f8/hpack-4.1.0.tar.gz", hash = "sha256:ec5eca154f7056aa06f196a557655c5b009b382873ac8d1e66e79e87535f1dca", size = 51276, upload-time = "2025-01-22T21:44:58.347Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/07/c6/80c95b1b2b94682a72cbdbfb85b81ae2daffa4291fbfa1b1464502ede10d/hpack-4.1.0-py3-none-any.whl", hash = "sha256:157ac792668d995c657d93111f46b4535ed114f0c9c8d672271bbec7eae1b496", size = 34357 }, + { url = "https://files.pythonhosted.org/packages/07/c6/80c95b1b2b94682a72cbdbfb85b81ae2daffa4291fbfa1b1464502ede10d/hpack-4.1.0-py3-none-any.whl", hash = "sha256:157ac792668d995c657d93111f46b4535ed114f0c9c8d672271bbec7eae1b496", size = 34357, upload-time = "2025-01-22T21:44:56.92Z" }, ] [[package]] @@ -2285,22 +2432,22 @@ dependencies = [ { name = "six" }, { name = "webencodings" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ac/b6/b55c3f49042f1df3dcd422b7f224f939892ee94f22abcf503a9b7339eaf2/html5lib-1.1.tar.gz", hash = "sha256:b2e5b40261e20f354d198eae92afc10d750afb487ed5e50f9c4eaf07c184146f", size = 272215 } +sdist = { url = "https://files.pythonhosted.org/packages/ac/b6/b55c3f49042f1df3dcd422b7f224f939892ee94f22abcf503a9b7339eaf2/html5lib-1.1.tar.gz", hash = "sha256:b2e5b40261e20f354d198eae92afc10d750afb487ed5e50f9c4eaf07c184146f", size = 272215, upload-time = "2020-06-22T23:32:38.834Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/6c/dd/a834df6482147d48e225a49515aabc28974ad5a4ca3215c18a882565b028/html5lib-1.1-py2.py3-none-any.whl", hash = "sha256:0d78f8fde1c230e99fe37986a60526d7049ed4bf8a9fadbad5f00e22e58e041d", size = 112173 }, + { url = "https://files.pythonhosted.org/packages/6c/dd/a834df6482147d48e225a49515aabc28974ad5a4ca3215c18a882565b028/html5lib-1.1-py2.py3-none-any.whl", hash = "sha256:0d78f8fde1c230e99fe37986a60526d7049ed4bf8a9fadbad5f00e22e58e041d", size = 112173, upload-time = "2020-06-22T23:32:36.781Z" }, ] [[package]] name = "httpcore" -version = "1.0.7" +version = "1.0.9" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "certifi" }, { name = "h11" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/6a/41/d7d0a89eb493922c37d343b607bc1b5da7f5be7e383740b4753ad8943e90/httpcore-1.0.7.tar.gz", hash = "sha256:8551cb62a169ec7162ac7be8d4817d561f60e08eaa485234898414bb5a8a0b4c", size = 85196 } +sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484, upload-time = "2025-04-24T22:06:22.219Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/87/f5/72347bc88306acb359581ac4d52f23c0ef445b57157adedb9aee0cd689d2/httpcore-1.0.7-py3-none-any.whl", hash = "sha256:a3fff8f43dc260d5bd363d9f9cf1830fa3a458b332856f34282de498ed420edd", size = 78551 }, + { url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" }, ] [[package]] @@ -2310,31 +2457,31 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyparsing" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/3d/ad/2371116b22d616c194aa25ec410c9c6c37f23599dcd590502b74db197584/httplib2-0.22.0.tar.gz", hash = "sha256:d7a10bc5ef5ab08322488bde8c726eeee5c8618723fdb399597ec58f3d82df81", size = 351116 } +sdist = { url = "https://files.pythonhosted.org/packages/3d/ad/2371116b22d616c194aa25ec410c9c6c37f23599dcd590502b74db197584/httplib2-0.22.0.tar.gz", hash = "sha256:d7a10bc5ef5ab08322488bde8c726eeee5c8618723fdb399597ec58f3d82df81", size = 351116, upload-time = "2023-03-21T22:29:37.214Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a8/6c/d2fbdaaa5959339d53ba38e94c123e4e84b8fbc4b84beb0e70d7c1608486/httplib2-0.22.0-py3-none-any.whl", hash = "sha256:14ae0a53c1ba8f3d37e9e27cf37eabb0fb9980f435ba405d546948b009dd64dc", size = 96854 }, + { url = "https://files.pythonhosted.org/packages/a8/6c/d2fbdaaa5959339d53ba38e94c123e4e84b8fbc4b84beb0e70d7c1608486/httplib2-0.22.0-py3-none-any.whl", hash = "sha256:14ae0a53c1ba8f3d37e9e27cf37eabb0fb9980f435ba405d546948b009dd64dc", size = 96854, upload-time = "2023-03-21T22:29:35.683Z" }, ] [[package]] name = "httptools" version = "0.6.4" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a7/9a/ce5e1f7e131522e6d3426e8e7a490b3a01f39a6696602e1c4f33f9e94277/httptools-0.6.4.tar.gz", hash = "sha256:4e93eee4add6493b59a5c514da98c939b244fce4a0d8879cd3f466562f4b7d5c", size = 240639 } +sdist = { url = "https://files.pythonhosted.org/packages/a7/9a/ce5e1f7e131522e6d3426e8e7a490b3a01f39a6696602e1c4f33f9e94277/httptools-0.6.4.tar.gz", hash = "sha256:4e93eee4add6493b59a5c514da98c939b244fce4a0d8879cd3f466562f4b7d5c", size = 240639, upload-time = "2024-10-16T19:45:08.902Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/7b/26/bb526d4d14c2774fe07113ca1db7255737ffbb119315839af2065abfdac3/httptools-0.6.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:f47f8ed67cc0ff862b84a1189831d1d33c963fb3ce1ee0c65d3b0cbe7b711069", size = 199029 }, - { url = "https://files.pythonhosted.org/packages/a6/17/3e0d3e9b901c732987a45f4f94d4e2c62b89a041d93db89eafb262afd8d5/httptools-0.6.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0614154d5454c21b6410fdf5262b4a3ddb0f53f1e1721cfd59d55f32138c578a", size = 103492 }, - { url = "https://files.pythonhosted.org/packages/b7/24/0fe235d7b69c42423c7698d086d4db96475f9b50b6ad26a718ef27a0bce6/httptools-0.6.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f8787367fbdfccae38e35abf7641dafc5310310a5987b689f4c32cc8cc3ee975", size = 462891 }, - { url = "https://files.pythonhosted.org/packages/b1/2f/205d1f2a190b72da6ffb5f41a3736c26d6fa7871101212b15e9b5cd8f61d/httptools-0.6.4-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:40b0f7fe4fd38e6a507bdb751db0379df1e99120c65fbdc8ee6c1d044897a636", size = 459788 }, - { url = "https://files.pythonhosted.org/packages/6e/4c/d09ce0eff09057a206a74575ae8f1e1e2f0364d20e2442224f9e6612c8b9/httptools-0.6.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:40a5ec98d3f49904b9fe36827dcf1aadfef3b89e2bd05b0e35e94f97c2b14721", size = 433214 }, - { url = "https://files.pythonhosted.org/packages/3e/d2/84c9e23edbccc4a4c6f96a1b8d99dfd2350289e94f00e9ccc7aadde26fb5/httptools-0.6.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:dacdd3d10ea1b4ca9df97a0a303cbacafc04b5cd375fa98732678151643d4988", size = 434120 }, - { url = "https://files.pythonhosted.org/packages/d0/46/4d8e7ba9581416de1c425b8264e2cadd201eb709ec1584c381f3e98f51c1/httptools-0.6.4-cp311-cp311-win_amd64.whl", hash = "sha256:288cd628406cc53f9a541cfaf06041b4c71d751856bab45e3702191f931ccd17", size = 88565 }, - { url = "https://files.pythonhosted.org/packages/bb/0e/d0b71465c66b9185f90a091ab36389a7352985fe857e352801c39d6127c8/httptools-0.6.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:df017d6c780287d5c80601dafa31f17bddb170232d85c066604d8558683711a2", size = 200683 }, - { url = "https://files.pythonhosted.org/packages/e2/b8/412a9bb28d0a8988de3296e01efa0bd62068b33856cdda47fe1b5e890954/httptools-0.6.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:85071a1e8c2d051b507161f6c3e26155b5c790e4e28d7f236422dbacc2a9cc44", size = 104337 }, - { url = "https://files.pythonhosted.org/packages/9b/01/6fb20be3196ffdc8eeec4e653bc2a275eca7f36634c86302242c4fbb2760/httptools-0.6.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69422b7f458c5af875922cdb5bd586cc1f1033295aa9ff63ee196a87519ac8e1", size = 508796 }, - { url = "https://files.pythonhosted.org/packages/f7/d8/b644c44acc1368938317d76ac991c9bba1166311880bcc0ac297cb9d6bd7/httptools-0.6.4-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:16e603a3bff50db08cd578d54f07032ca1631450ceb972c2f834c2b860c28ea2", size = 510837 }, - { url = "https://files.pythonhosted.org/packages/52/d8/254d16a31d543073a0e57f1c329ca7378d8924e7e292eda72d0064987486/httptools-0.6.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ec4f178901fa1834d4a060320d2f3abc5c9e39766953d038f1458cb885f47e81", size = 485289 }, - { url = "https://files.pythonhosted.org/packages/5f/3c/4aee161b4b7a971660b8be71a92c24d6c64372c1ab3ae7f366b3680df20f/httptools-0.6.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f9eb89ecf8b290f2e293325c646a211ff1c2493222798bb80a530c5e7502494f", size = 489779 }, - { url = "https://files.pythonhosted.org/packages/12/b7/5cae71a8868e555f3f67a50ee7f673ce36eac970f029c0c5e9d584352961/httptools-0.6.4-cp312-cp312-win_amd64.whl", hash = "sha256:db78cb9ca56b59b016e64b6031eda5653be0589dba2b1b43453f6e8b405a0970", size = 88634 }, + { url = "https://files.pythonhosted.org/packages/7b/26/bb526d4d14c2774fe07113ca1db7255737ffbb119315839af2065abfdac3/httptools-0.6.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:f47f8ed67cc0ff862b84a1189831d1d33c963fb3ce1ee0c65d3b0cbe7b711069", size = 199029, upload-time = "2024-10-16T19:44:18.427Z" }, + { url = "https://files.pythonhosted.org/packages/a6/17/3e0d3e9b901c732987a45f4f94d4e2c62b89a041d93db89eafb262afd8d5/httptools-0.6.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0614154d5454c21b6410fdf5262b4a3ddb0f53f1e1721cfd59d55f32138c578a", size = 103492, upload-time = "2024-10-16T19:44:19.515Z" }, + { url = "https://files.pythonhosted.org/packages/b7/24/0fe235d7b69c42423c7698d086d4db96475f9b50b6ad26a718ef27a0bce6/httptools-0.6.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f8787367fbdfccae38e35abf7641dafc5310310a5987b689f4c32cc8cc3ee975", size = 462891, upload-time = "2024-10-16T19:44:21.067Z" }, + { url = "https://files.pythonhosted.org/packages/b1/2f/205d1f2a190b72da6ffb5f41a3736c26d6fa7871101212b15e9b5cd8f61d/httptools-0.6.4-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:40b0f7fe4fd38e6a507bdb751db0379df1e99120c65fbdc8ee6c1d044897a636", size = 459788, upload-time = "2024-10-16T19:44:22.958Z" }, + { url = "https://files.pythonhosted.org/packages/6e/4c/d09ce0eff09057a206a74575ae8f1e1e2f0364d20e2442224f9e6612c8b9/httptools-0.6.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:40a5ec98d3f49904b9fe36827dcf1aadfef3b89e2bd05b0e35e94f97c2b14721", size = 433214, upload-time = "2024-10-16T19:44:24.513Z" }, + { url = "https://files.pythonhosted.org/packages/3e/d2/84c9e23edbccc4a4c6f96a1b8d99dfd2350289e94f00e9ccc7aadde26fb5/httptools-0.6.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:dacdd3d10ea1b4ca9df97a0a303cbacafc04b5cd375fa98732678151643d4988", size = 434120, upload-time = "2024-10-16T19:44:26.295Z" }, + { url = "https://files.pythonhosted.org/packages/d0/46/4d8e7ba9581416de1c425b8264e2cadd201eb709ec1584c381f3e98f51c1/httptools-0.6.4-cp311-cp311-win_amd64.whl", hash = "sha256:288cd628406cc53f9a541cfaf06041b4c71d751856bab45e3702191f931ccd17", size = 88565, upload-time = "2024-10-16T19:44:29.188Z" }, + { url = "https://files.pythonhosted.org/packages/bb/0e/d0b71465c66b9185f90a091ab36389a7352985fe857e352801c39d6127c8/httptools-0.6.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:df017d6c780287d5c80601dafa31f17bddb170232d85c066604d8558683711a2", size = 200683, upload-time = "2024-10-16T19:44:30.175Z" }, + { url = "https://files.pythonhosted.org/packages/e2/b8/412a9bb28d0a8988de3296e01efa0bd62068b33856cdda47fe1b5e890954/httptools-0.6.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:85071a1e8c2d051b507161f6c3e26155b5c790e4e28d7f236422dbacc2a9cc44", size = 104337, upload-time = "2024-10-16T19:44:31.786Z" }, + { url = "https://files.pythonhosted.org/packages/9b/01/6fb20be3196ffdc8eeec4e653bc2a275eca7f36634c86302242c4fbb2760/httptools-0.6.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69422b7f458c5af875922cdb5bd586cc1f1033295aa9ff63ee196a87519ac8e1", size = 508796, upload-time = "2024-10-16T19:44:32.825Z" }, + { url = "https://files.pythonhosted.org/packages/f7/d8/b644c44acc1368938317d76ac991c9bba1166311880bcc0ac297cb9d6bd7/httptools-0.6.4-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:16e603a3bff50db08cd578d54f07032ca1631450ceb972c2f834c2b860c28ea2", size = 510837, upload-time = "2024-10-16T19:44:33.974Z" }, + { url = "https://files.pythonhosted.org/packages/52/d8/254d16a31d543073a0e57f1c329ca7378d8924e7e292eda72d0064987486/httptools-0.6.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ec4f178901fa1834d4a060320d2f3abc5c9e39766953d038f1458cb885f47e81", size = 485289, upload-time = "2024-10-16T19:44:35.111Z" }, + { url = "https://files.pythonhosted.org/packages/5f/3c/4aee161b4b7a971660b8be71a92c24d6c64372c1ab3ae7f366b3680df20f/httptools-0.6.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f9eb89ecf8b290f2e293325c646a211ff1c2493222798bb80a530c5e7502494f", size = 489779, upload-time = "2024-10-16T19:44:36.253Z" }, + { url = "https://files.pythonhosted.org/packages/12/b7/5cae71a8868e555f3f67a50ee7f673ce36eac970f029c0c5e9d584352961/httptools-0.6.4-cp312-cp312-win_amd64.whl", hash = "sha256:db78cb9ca56b59b016e64b6031eda5653be0589dba2b1b43453f6e8b405a0970", size = 88634, upload-time = "2024-10-16T19:44:37.357Z" }, ] [[package]] @@ -2348,9 +2495,9 @@ dependencies = [ { name = "idna" }, { name = "sniffio" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/78/82/08f8c936781f67d9e6b9eeb8a0c8b4e406136ea4c3d1f89a5db71d42e0e6/httpx-0.27.2.tar.gz", hash = "sha256:f7c2be1d2f3c3c3160d441802406b206c2b76f5947b11115e6df10c6c65e66c2", size = 144189 } +sdist = { url = "https://files.pythonhosted.org/packages/78/82/08f8c936781f67d9e6b9eeb8a0c8b4e406136ea4c3d1f89a5db71d42e0e6/httpx-0.27.2.tar.gz", hash = "sha256:f7c2be1d2f3c3c3160d441802406b206c2b76f5947b11115e6df10c6c65e66c2", size = 144189, upload-time = "2024-08-27T12:54:01.334Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/56/95/9377bcb415797e44274b51d46e3249eba641711cf3348050f76ee7b15ffc/httpx-0.27.2-py3-none-any.whl", hash = "sha256:7bb2708e112d8fdd7829cd4243970f0c223274051cb35ee80c03301ee29a3df0", size = 76395 }, + { url = "https://files.pythonhosted.org/packages/56/95/9377bcb415797e44274b51d46e3249eba641711cf3348050f76ee7b15ffc/httpx-0.27.2-py3-none-any.whl", hash = "sha256:7bb2708e112d8fdd7829cd4243970f0c223274051cb35ee80c03301ee29a3df0", size = 76395, upload-time = "2024-08-27T12:53:59.653Z" }, ] [package.optional-dependencies] @@ -2363,20 +2510,21 @@ socks = [ [[package]] name = "huggingface-hub" -version = "0.30.2" +version = "0.32.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "filelock" }, { name = "fsspec" }, + { name = "hf-xet", marker = "platform_machine == 'aarch64' or platform_machine == 'amd64' or platform_machine == 'arm64' or platform_machine == 'x86_64'" }, { name = "packaging" }, { name = "pyyaml" }, { name = "requests" }, { name = "tqdm" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/df/22/8eb91736b1dcb83d879bd49050a09df29a57cc5cd9f38e48a4b1c45ee890/huggingface_hub-0.30.2.tar.gz", hash = "sha256:9a7897c5b6fd9dad3168a794a8998d6378210f5b9688d0dfc180b1a228dc2466", size = 400868 } +sdist = { url = "https://files.pythonhosted.org/packages/59/74/c4961b31e0f142a032ea24f477c3a7524dfabfd8126398a968b3cc6bf804/huggingface_hub-0.32.3.tar.gz", hash = "sha256:752c889ebf3a63cbd39803f6d87ccc135a463bbcb36abfa2faff0ccbf1cec087", size = 424525, upload-time = "2025-05-30T08:23:56.042Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/93/27/1fb384a841e9661faad1c31cbfa62864f59632e876df5d795234da51c395/huggingface_hub-0.30.2-py3-none-any.whl", hash = "sha256:68ff05969927058cfa41df4f2155d4bb48f5f54f719dd0390103eefa9b191e28", size = 481433 }, + { url = "https://files.pythonhosted.org/packages/df/dc/4f4d8080cbce7a38c1d0f1ba4932f9134480b9761af8ef4c65d49254b2bd/huggingface_hub-0.32.3-py3-none-any.whl", hash = "sha256:e46f7ea7fe2b5e5f67cc4e37eb201140091946a314d7c2b134a9673dadd80b6a", size = 512094, upload-time = "2025-05-30T08:23:54.091Z" }, ] [[package]] @@ -2386,27 +2534,27 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyreadline3", marker = "sys_platform == 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/cc/3f/2c29224acb2e2df4d2046e4c73ee2662023c58ff5b113c4c1adac0886c43/humanfriendly-10.0.tar.gz", hash = "sha256:6b0b831ce8f15f7300721aa49829fc4e83921a9a301cc7f606be6686a2288ddc", size = 360702 } +sdist = { url = "https://files.pythonhosted.org/packages/cc/3f/2c29224acb2e2df4d2046e4c73ee2662023c58ff5b113c4c1adac0886c43/humanfriendly-10.0.tar.gz", hash = "sha256:6b0b831ce8f15f7300721aa49829fc4e83921a9a301cc7f606be6686a2288ddc", size = 360702, upload-time = "2021-09-17T21:40:43.31Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f0/0f/310fb31e39e2d734ccaa2c0fb981ee41f7bd5056ce9bc29b2248bd569169/humanfriendly-10.0-py2.py3-none-any.whl", hash = "sha256:1697e1a8a8f550fd43c2865cd84542fc175a61dcb779b6fee18cf6b6ccba1477", size = 86794 }, + { url = "https://files.pythonhosted.org/packages/f0/0f/310fb31e39e2d734ccaa2c0fb981ee41f7bd5056ce9bc29b2248bd569169/humanfriendly-10.0-py2.py3-none-any.whl", hash = "sha256:1697e1a8a8f550fd43c2865cd84542fc175a61dcb779b6fee18cf6b6ccba1477", size = 86794, upload-time = "2021-09-17T21:40:39.897Z" }, ] [[package]] name = "hyperframe" version = "6.1.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/02/e7/94f8232d4a74cc99514c13a9f995811485a6903d48e5d952771ef6322e30/hyperframe-6.1.0.tar.gz", hash = "sha256:f630908a00854a7adeabd6382b43923a4c4cd4b821fcb527e6ab9e15382a3b08", size = 26566 } +sdist = { url = "https://files.pythonhosted.org/packages/02/e7/94f8232d4a74cc99514c13a9f995811485a6903d48e5d952771ef6322e30/hyperframe-6.1.0.tar.gz", hash = "sha256:f630908a00854a7adeabd6382b43923a4c4cd4b821fcb527e6ab9e15382a3b08", size = 26566, upload-time = "2025-01-22T21:41:49.302Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/48/30/47d0bf6072f7252e6521f3447ccfa40b421b6824517f82854703d0f5a98b/hyperframe-6.1.0-py3-none-any.whl", hash = "sha256:b03380493a519fce58ea5af42e4a42317bf9bd425596f7a0835ffce80f1a42e5", size = 13007 }, + { url = "https://files.pythonhosted.org/packages/48/30/47d0bf6072f7252e6521f3447ccfa40b421b6824517f82854703d0f5a98b/hyperframe-6.1.0-py3-none-any.whl", hash = "sha256:b03380493a519fce58ea5af42e4a42317bf9bd425596f7a0835ffce80f1a42e5", size = 13007, upload-time = "2025-01-22T21:41:47.295Z" }, ] [[package]] name = "idna" version = "3.10" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490 } +sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490, upload-time = "2024-09-15T18:07:39.745Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442 }, + { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442, upload-time = "2024-09-15T18:07:37.964Z" }, ] [[package]] @@ -2416,52 +2564,52 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "zipp" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c0/bd/fa8ce65b0a7d4b6d143ec23b0f5fd3f7ab80121078c465bc02baeaab22dc/importlib_metadata-8.4.0.tar.gz", hash = "sha256:9a547d3bc3608b025f93d403fdd1aae741c24fbb8314df4b155675742ce303c5", size = 54320 } +sdist = { url = "https://files.pythonhosted.org/packages/c0/bd/fa8ce65b0a7d4b6d143ec23b0f5fd3f7ab80121078c465bc02baeaab22dc/importlib_metadata-8.4.0.tar.gz", hash = "sha256:9a547d3bc3608b025f93d403fdd1aae741c24fbb8314df4b155675742ce303c5", size = 54320, upload-time = "2024-08-20T17:11:42.348Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c0/14/362d31bf1076b21e1bcdcb0dc61944822ff263937b804a79231df2774d28/importlib_metadata-8.4.0-py3-none-any.whl", hash = "sha256:66f342cc6ac9818fc6ff340576acd24d65ba0b3efabb2b4ac08b598965a4a2f1", size = 26269 }, + { url = "https://files.pythonhosted.org/packages/c0/14/362d31bf1076b21e1bcdcb0dc61944822ff263937b804a79231df2774d28/importlib_metadata-8.4.0-py3-none-any.whl", hash = "sha256:66f342cc6ac9818fc6ff340576acd24d65ba0b3efabb2b4ac08b598965a4a2f1", size = 26269, upload-time = "2024-08-20T17:11:41.102Z" }, ] [[package]] name = "importlib-resources" version = "6.5.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/cf/8c/f834fbf984f691b4f7ff60f50b514cc3de5cc08abfc3295564dd89c5e2e7/importlib_resources-6.5.2.tar.gz", hash = "sha256:185f87adef5bcc288449d98fb4fba07cea78bc036455dd44c5fc4a2fe78fed2c", size = 44693 } +sdist = { url = "https://files.pythonhosted.org/packages/cf/8c/f834fbf984f691b4f7ff60f50b514cc3de5cc08abfc3295564dd89c5e2e7/importlib_resources-6.5.2.tar.gz", hash = "sha256:185f87adef5bcc288449d98fb4fba07cea78bc036455dd44c5fc4a2fe78fed2c", size = 44693, upload-time = "2025-01-03T18:51:56.698Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a4/ed/1f1afb2e9e7f38a545d628f864d562a5ae64fe6f7a10e28ffb9b185b4e89/importlib_resources-6.5.2-py3-none-any.whl", hash = "sha256:789cfdc3ed28c78b67a06acb8126751ced69a3d5f79c095a98298cd8a760ccec", size = 37461 }, + { url = "https://files.pythonhosted.org/packages/a4/ed/1f1afb2e9e7f38a545d628f864d562a5ae64fe6f7a10e28ffb9b185b4e89/importlib_resources-6.5.2-py3-none-any.whl", hash = "sha256:789cfdc3ed28c78b67a06acb8126751ced69a3d5f79c095a98298cd8a760ccec", size = 37461, upload-time = "2025-01-03T18:51:54.306Z" }, ] [[package]] name = "iniconfig" version = "2.1.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f2/97/ebf4da567aa6827c909642694d71c9fcf53e5b504f2d96afea02718862f3/iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7", size = 4793 } +sdist = { url = "https://files.pythonhosted.org/packages/f2/97/ebf4da567aa6827c909642694d71c9fcf53e5b504f2d96afea02718862f3/iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7", size = 4793, upload-time = "2025-03-19T20:09:59.721Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760", size = 6050 }, + { url = "https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760", size = 6050, upload-time = "2025-03-19T20:10:01.071Z" }, ] [[package]] name = "isodate" version = "0.7.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/54/4d/e940025e2ce31a8ce1202635910747e5a87cc3a6a6bb2d00973375014749/isodate-0.7.2.tar.gz", hash = "sha256:4cd1aa0f43ca76f4a6c6c0292a85f40b35ec2e43e315b59f06e6d32171a953e6", size = 29705 } +sdist = { url = "https://files.pythonhosted.org/packages/54/4d/e940025e2ce31a8ce1202635910747e5a87cc3a6a6bb2d00973375014749/isodate-0.7.2.tar.gz", hash = "sha256:4cd1aa0f43ca76f4a6c6c0292a85f40b35ec2e43e315b59f06e6d32171a953e6", size = 29705, upload-time = "2024-10-08T23:04:11.5Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/15/aa/0aca39a37d3c7eb941ba736ede56d689e7be91cab5d9ca846bde3999eba6/isodate-0.7.2-py3-none-any.whl", hash = "sha256:28009937d8031054830160fce6d409ed342816b543597cece116d966c6d99e15", size = 22320 }, + { url = "https://files.pythonhosted.org/packages/15/aa/0aca39a37d3c7eb941ba736ede56d689e7be91cab5d9ca846bde3999eba6/isodate-0.7.2-py3-none-any.whl", hash = "sha256:28009937d8031054830160fce6d409ed342816b543597cece116d966c6d99e15", size = 22320, upload-time = "2024-10-08T23:04:09.501Z" }, ] [[package]] name = "itsdangerous" version = "2.2.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/9c/cb/8ac0172223afbccb63986cc25049b154ecfb5e85932587206f42317be31d/itsdangerous-2.2.0.tar.gz", hash = "sha256:e0050c0b7da1eea53ffaf149c0cfbb5c6e2e2b69c4bef22c81fa6eb73e5f6173", size = 54410 } +sdist = { url = "https://files.pythonhosted.org/packages/9c/cb/8ac0172223afbccb63986cc25049b154ecfb5e85932587206f42317be31d/itsdangerous-2.2.0.tar.gz", hash = "sha256:e0050c0b7da1eea53ffaf149c0cfbb5c6e2e2b69c4bef22c81fa6eb73e5f6173", size = 54410, upload-time = "2024-04-16T21:28:15.614Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/04/96/92447566d16df59b2a776c0fb82dbc4d9e07cd95062562af01e408583fc4/itsdangerous-2.2.0-py3-none-any.whl", hash = "sha256:c6242fc49e35958c8b15141343aa660db5fc54d4f13a1db01a3f5891b98700ef", size = 16234 }, + { url = "https://files.pythonhosted.org/packages/04/96/92447566d16df59b2a776c0fb82dbc4d9e07cd95062562af01e408583fc4/itsdangerous-2.2.0-py3-none-any.whl", hash = "sha256:c6242fc49e35958c8b15141343aa660db5fc54d4f13a1db01a3f5891b98700ef", size = 16234, upload-time = "2024-04-16T21:28:14.499Z" }, ] [[package]] name = "jieba" version = "0.42.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/c6/cb/18eeb235f833b726522d7ebed54f2278ce28ba9438e3135ab0278d9792a2/jieba-0.42.1.tar.gz", hash = "sha256:055ca12f62674fafed09427f176506079bc135638a14e23e25be909131928db2", size = 19214172 } +sdist = { url = "https://files.pythonhosted.org/packages/c6/cb/18eeb235f833b726522d7ebed54f2278ce28ba9438e3135ab0278d9792a2/jieba-0.42.1.tar.gz", hash = "sha256:055ca12f62674fafed09427f176506079bc135638a14e23e25be909131928db2", size = 19214172, upload-time = "2020-01-20T14:27:23.5Z" } [[package]] name = "jinja2" @@ -2470,82 +2618,73 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "markupsafe" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115 } +sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115, upload-time = "2025-03-05T20:05:02.478Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899 }, + { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z" }, ] [[package]] name = "jiter" -version = "0.9.0" +version = "0.10.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/1e/c2/e4562507f52f0af7036da125bb699602ead37a2332af0788f8e0a3417f36/jiter-0.9.0.tar.gz", hash = "sha256:aadba0964deb424daa24492abc3d229c60c4a31bfee205aedbf1acc7639d7893", size = 162604 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/23/44/e241a043f114299254e44d7e777ead311da400517f179665e59611ab0ee4/jiter-0.9.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:6c4d99c71508912a7e556d631768dcdef43648a93660670986916b297f1c54af", size = 314654 }, - { url = "https://files.pythonhosted.org/packages/fb/1b/a7e5e42db9fa262baaa9489d8d14ca93f8663e7f164ed5e9acc9f467fc00/jiter-0.9.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8f60fb8ce7df529812bf6c625635a19d27f30806885139e367af93f6e734ef58", size = 320909 }, - { url = "https://files.pythonhosted.org/packages/60/bf/8ebdfce77bc04b81abf2ea316e9c03b4a866a7d739cf355eae4d6fd9f6fe/jiter-0.9.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:51c4e1a4f8ea84d98b7b98912aa4290ac3d1eabfde8e3c34541fae30e9d1f08b", size = 341733 }, - { url = "https://files.pythonhosted.org/packages/a8/4e/754ebce77cff9ab34d1d0fa0fe98f5d42590fd33622509a3ba6ec37ff466/jiter-0.9.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5f4c677c424dc76684fea3e7285a7a2a7493424bea89ac441045e6a1fb1d7b3b", size = 365097 }, - { url = "https://files.pythonhosted.org/packages/32/2c/6019587e6f5844c612ae18ca892f4cd7b3d8bbf49461ed29e384a0f13d98/jiter-0.9.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2221176dfec87f3470b21e6abca056e6b04ce9bff72315cb0b243ca9e835a4b5", size = 406603 }, - { url = "https://files.pythonhosted.org/packages/da/e9/c9e6546c817ab75a1a7dab6dcc698e62e375e1017113e8e983fccbd56115/jiter-0.9.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3c7adb66f899ffa25e3c92bfcb593391ee1947dbdd6a9a970e0d7e713237d572", size = 396625 }, - { url = "https://files.pythonhosted.org/packages/be/bd/976b458add04271ebb5a255e992bd008546ea04bb4dcadc042a16279b4b4/jiter-0.9.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c98d27330fdfb77913c1097a7aab07f38ff2259048949f499c9901700789ac15", size = 351832 }, - { url = "https://files.pythonhosted.org/packages/07/51/fe59e307aaebec9265dbad44d9d4381d030947e47b0f23531579b9a7c2df/jiter-0.9.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:eda3f8cc74df66892b1d06b5d41a71670c22d95a1ca2cbab73654745ce9d0419", size = 384590 }, - { url = "https://files.pythonhosted.org/packages/db/55/5dcd2693794d8e6f4889389ff66ef3be557a77f8aeeca8973a97a7c00557/jiter-0.9.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:dd5ab5ddc11418dce28343123644a100f487eaccf1de27a459ab36d6cca31043", size = 520690 }, - { url = "https://files.pythonhosted.org/packages/54/d5/9f51dc90985e9eb251fbbb747ab2b13b26601f16c595a7b8baba964043bd/jiter-0.9.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:42f8a68a69f047b310319ef8e2f52fdb2e7976fb3313ef27df495cf77bcad965", size = 512649 }, - { url = "https://files.pythonhosted.org/packages/a6/e5/4e385945179bcf128fa10ad8dca9053d717cbe09e258110e39045c881fe5/jiter-0.9.0-cp311-cp311-win32.whl", hash = "sha256:a25519efb78a42254d59326ee417d6f5161b06f5da827d94cf521fed961b1ff2", size = 206920 }, - { url = "https://files.pythonhosted.org/packages/4c/47/5e0b94c603d8e54dd1faab439b40b832c277d3b90743e7835879ab663757/jiter-0.9.0-cp311-cp311-win_amd64.whl", hash = "sha256:923b54afdd697dfd00d368b7ccad008cccfeb1efb4e621f32860c75e9f25edbd", size = 210119 }, - { url = "https://files.pythonhosted.org/packages/af/d7/c55086103d6f29b694ec79156242304adf521577530d9031317ce5338c59/jiter-0.9.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:7b46249cfd6c48da28f89eb0be3f52d6fdb40ab88e2c66804f546674e539ec11", size = 309203 }, - { url = "https://files.pythonhosted.org/packages/b0/01/f775dfee50beb420adfd6baf58d1c4d437de41c9b666ddf127c065e5a488/jiter-0.9.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:609cf3c78852f1189894383cf0b0b977665f54cb38788e3e6b941fa6d982c00e", size = 319678 }, - { url = "https://files.pythonhosted.org/packages/ab/b8/09b73a793714726893e5d46d5c534a63709261af3d24444ad07885ce87cb/jiter-0.9.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d726a3890a54561e55a9c5faea1f7655eda7f105bd165067575ace6e65f80bb2", size = 341816 }, - { url = "https://files.pythonhosted.org/packages/35/6f/b8f89ec5398b2b0d344257138182cc090302854ed63ed9c9051e9c673441/jiter-0.9.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2e89dc075c1fef8fa9be219e249f14040270dbc507df4215c324a1839522ea75", size = 364152 }, - { url = "https://files.pythonhosted.org/packages/9b/ca/978cc3183113b8e4484cc7e210a9ad3c6614396e7abd5407ea8aa1458eef/jiter-0.9.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:04e8ffa3c353b1bc4134f96f167a2082494351e42888dfcf06e944f2729cbe1d", size = 406991 }, - { url = "https://files.pythonhosted.org/packages/13/3a/72861883e11a36d6aa314b4922125f6ae90bdccc225cd96d24cc78a66385/jiter-0.9.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:203f28a72a05ae0e129b3ed1f75f56bc419d5f91dfacd057519a8bd137b00c42", size = 395824 }, - { url = "https://files.pythonhosted.org/packages/87/67/22728a86ef53589c3720225778f7c5fdb617080e3deaed58b04789418212/jiter-0.9.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fca1a02ad60ec30bb230f65bc01f611c8608b02d269f998bc29cca8619a919dc", size = 351318 }, - { url = "https://files.pythonhosted.org/packages/69/b9/f39728e2e2007276806d7a6609cda7fac44ffa28ca0d02c49a4f397cc0d9/jiter-0.9.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:237e5cee4d5d2659aaf91bbf8ec45052cc217d9446070699441a91b386ae27dc", size = 384591 }, - { url = "https://files.pythonhosted.org/packages/eb/8f/8a708bc7fd87b8a5d861f1c118a995eccbe6d672fe10c9753e67362d0dd0/jiter-0.9.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:528b6b71745e7326eed73c53d4aa57e2a522242320b6f7d65b9c5af83cf49b6e", size = 520746 }, - { url = "https://files.pythonhosted.org/packages/95/1e/65680c7488bd2365dbd2980adaf63c562d3d41d3faac192ebc7ef5b4ae25/jiter-0.9.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:9f48e86b57bc711eb5acdfd12b6cb580a59cc9a993f6e7dcb6d8b50522dcd50d", size = 512754 }, - { url = "https://files.pythonhosted.org/packages/78/f3/fdc43547a9ee6e93c837685da704fb6da7dba311fc022e2766d5277dfde5/jiter-0.9.0-cp312-cp312-win32.whl", hash = "sha256:699edfde481e191d81f9cf6d2211debbfe4bd92f06410e7637dffb8dd5dfde06", size = 207075 }, - { url = "https://files.pythonhosted.org/packages/cd/9d/742b289016d155f49028fe1bfbeb935c9bf0ffeefdf77daf4a63a42bb72b/jiter-0.9.0-cp312-cp312-win_amd64.whl", hash = "sha256:099500d07b43f61d8bd780466d429c45a7b25411b334c60ca875fa775f68ccb0", size = 207999 }, +sdist = { url = "https://files.pythonhosted.org/packages/ee/9d/ae7ddb4b8ab3fb1b51faf4deb36cb48a4fbbd7cb36bad6a5fca4741306f7/jiter-0.10.0.tar.gz", hash = "sha256:07a7142c38aacc85194391108dc91b5b57093c978a9932bd86a36862759d9500", size = 162759, upload-time = "2025-05-18T19:04:59.73Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1b/dd/6cefc6bd68b1c3c979cecfa7029ab582b57690a31cd2f346c4d0ce7951b6/jiter-0.10.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:3bebe0c558e19902c96e99217e0b8e8b17d570906e72ed8a87170bc290b1e978", size = 317473, upload-time = "2025-05-18T19:03:25.942Z" }, + { url = "https://files.pythonhosted.org/packages/be/cf/fc33f5159ce132be1d8dd57251a1ec7a631c7df4bd11e1cd198308c6ae32/jiter-0.10.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:558cc7e44fd8e507a236bee6a02fa17199ba752874400a0ca6cd6e2196cdb7dc", size = 321971, upload-time = "2025-05-18T19:03:27.255Z" }, + { url = "https://files.pythonhosted.org/packages/68/a4/da3f150cf1d51f6c472616fb7650429c7ce053e0c962b41b68557fdf6379/jiter-0.10.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4d613e4b379a07d7c8453c5712ce7014e86c6ac93d990a0b8e7377e18505e98d", size = 345574, upload-time = "2025-05-18T19:03:28.63Z" }, + { url = "https://files.pythonhosted.org/packages/84/34/6e8d412e60ff06b186040e77da5f83bc158e9735759fcae65b37d681f28b/jiter-0.10.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f62cf8ba0618eda841b9bf61797f21c5ebd15a7a1e19daab76e4e4b498d515b2", size = 371028, upload-time = "2025-05-18T19:03:30.292Z" }, + { url = "https://files.pythonhosted.org/packages/fb/d9/9ee86173aae4576c35a2f50ae930d2ccb4c4c236f6cb9353267aa1d626b7/jiter-0.10.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:919d139cdfa8ae8945112398511cb7fca58a77382617d279556b344867a37e61", size = 491083, upload-time = "2025-05-18T19:03:31.654Z" }, + { url = "https://files.pythonhosted.org/packages/d9/2c/f955de55e74771493ac9e188b0f731524c6a995dffdcb8c255b89c6fb74b/jiter-0.10.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:13ddbc6ae311175a3b03bd8994881bc4635c923754932918e18da841632349db", size = 388821, upload-time = "2025-05-18T19:03:33.184Z" }, + { url = "https://files.pythonhosted.org/packages/81/5a/0e73541b6edd3f4aada586c24e50626c7815c561a7ba337d6a7eb0a915b4/jiter-0.10.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4c440ea003ad10927a30521a9062ce10b5479592e8a70da27f21eeb457b4a9c5", size = 352174, upload-time = "2025-05-18T19:03:34.965Z" }, + { url = "https://files.pythonhosted.org/packages/1c/c0/61eeec33b8c75b31cae42be14d44f9e6fe3ac15a4e58010256ac3abf3638/jiter-0.10.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:dc347c87944983481e138dea467c0551080c86b9d21de6ea9306efb12ca8f606", size = 391869, upload-time = "2025-05-18T19:03:36.436Z" }, + { url = "https://files.pythonhosted.org/packages/41/22/5beb5ee4ad4ef7d86f5ea5b4509f680a20706c4a7659e74344777efb7739/jiter-0.10.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:13252b58c1f4d8c5b63ab103c03d909e8e1e7842d302473f482915d95fefd605", size = 523741, upload-time = "2025-05-18T19:03:38.168Z" }, + { url = "https://files.pythonhosted.org/packages/ea/10/768e8818538e5817c637b0df52e54366ec4cebc3346108a4457ea7a98f32/jiter-0.10.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:7d1bbf3c465de4a24ab12fb7766a0003f6f9bce48b8b6a886158c4d569452dc5", size = 514527, upload-time = "2025-05-18T19:03:39.577Z" }, + { url = "https://files.pythonhosted.org/packages/73/6d/29b7c2dc76ce93cbedabfd842fc9096d01a0550c52692dfc33d3cc889815/jiter-0.10.0-cp311-cp311-win32.whl", hash = "sha256:db16e4848b7e826edca4ccdd5b145939758dadf0dc06e7007ad0e9cfb5928ae7", size = 210765, upload-time = "2025-05-18T19:03:41.271Z" }, + { url = "https://files.pythonhosted.org/packages/c2/c9/d394706deb4c660137caf13e33d05a031d734eb99c051142e039d8ceb794/jiter-0.10.0-cp311-cp311-win_amd64.whl", hash = "sha256:9c9c1d5f10e18909e993f9641f12fe1c77b3e9b533ee94ffa970acc14ded3812", size = 209234, upload-time = "2025-05-18T19:03:42.918Z" }, + { url = "https://files.pythonhosted.org/packages/6d/b5/348b3313c58f5fbfb2194eb4d07e46a35748ba6e5b3b3046143f3040bafa/jiter-0.10.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:1e274728e4a5345a6dde2d343c8da018b9d4bd4350f5a472fa91f66fda44911b", size = 312262, upload-time = "2025-05-18T19:03:44.637Z" }, + { url = "https://files.pythonhosted.org/packages/9c/4a/6a2397096162b21645162825f058d1709a02965606e537e3304b02742e9b/jiter-0.10.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7202ae396446c988cb2a5feb33a543ab2165b786ac97f53b59aafb803fef0744", size = 320124, upload-time = "2025-05-18T19:03:46.341Z" }, + { url = "https://files.pythonhosted.org/packages/2a/85/1ce02cade7516b726dd88f59a4ee46914bf79d1676d1228ef2002ed2f1c9/jiter-0.10.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:23ba7722d6748b6920ed02a8f1726fb4b33e0fd2f3f621816a8b486c66410ab2", size = 345330, upload-time = "2025-05-18T19:03:47.596Z" }, + { url = "https://files.pythonhosted.org/packages/75/d0/bb6b4f209a77190ce10ea8d7e50bf3725fc16d3372d0a9f11985a2b23eff/jiter-0.10.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:371eab43c0a288537d30e1f0b193bc4eca90439fc08a022dd83e5e07500ed026", size = 369670, upload-time = "2025-05-18T19:03:49.334Z" }, + { url = "https://files.pythonhosted.org/packages/a0/f5/a61787da9b8847a601e6827fbc42ecb12be2c925ced3252c8ffcb56afcaf/jiter-0.10.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6c675736059020365cebc845a820214765162728b51ab1e03a1b7b3abb70f74c", size = 489057, upload-time = "2025-05-18T19:03:50.66Z" }, + { url = "https://files.pythonhosted.org/packages/12/e4/6f906272810a7b21406c760a53aadbe52e99ee070fc5c0cb191e316de30b/jiter-0.10.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0c5867d40ab716e4684858e4887489685968a47e3ba222e44cde6e4a2154f959", size = 389372, upload-time = "2025-05-18T19:03:51.98Z" }, + { url = "https://files.pythonhosted.org/packages/e2/ba/77013b0b8ba904bf3762f11e0129b8928bff7f978a81838dfcc958ad5728/jiter-0.10.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:395bb9a26111b60141757d874d27fdea01b17e8fac958b91c20128ba8f4acc8a", size = 352038, upload-time = "2025-05-18T19:03:53.703Z" }, + { url = "https://files.pythonhosted.org/packages/67/27/c62568e3ccb03368dbcc44a1ef3a423cb86778a4389e995125d3d1aaa0a4/jiter-0.10.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6842184aed5cdb07e0c7e20e5bdcfafe33515ee1741a6835353bb45fe5d1bd95", size = 391538, upload-time = "2025-05-18T19:03:55.046Z" }, + { url = "https://files.pythonhosted.org/packages/c0/72/0d6b7e31fc17a8fdce76164884edef0698ba556b8eb0af9546ae1a06b91d/jiter-0.10.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:62755d1bcea9876770d4df713d82606c8c1a3dca88ff39046b85a048566d56ea", size = 523557, upload-time = "2025-05-18T19:03:56.386Z" }, + { url = "https://files.pythonhosted.org/packages/2f/09/bc1661fbbcbeb6244bd2904ff3a06f340aa77a2b94e5a7373fd165960ea3/jiter-0.10.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:533efbce2cacec78d5ba73a41756beff8431dfa1694b6346ce7af3a12c42202b", size = 514202, upload-time = "2025-05-18T19:03:57.675Z" }, + { url = "https://files.pythonhosted.org/packages/1b/84/5a5d5400e9d4d54b8004c9673bbe4403928a00d28529ff35b19e9d176b19/jiter-0.10.0-cp312-cp312-win32.whl", hash = "sha256:8be921f0cadd245e981b964dfbcd6fd4bc4e254cdc069490416dd7a2632ecc01", size = 211781, upload-time = "2025-05-18T19:03:59.025Z" }, + { url = "https://files.pythonhosted.org/packages/9b/52/7ec47455e26f2d6e5f2ea4951a0652c06e5b995c291f723973ae9e724a65/jiter-0.10.0-cp312-cp312-win_amd64.whl", hash = "sha256:a7c7d785ae9dda68c2678532a5a1581347e9c15362ae9f6e68f3fdbfb64f2e49", size = 206176, upload-time = "2025-05-18T19:04:00.305Z" }, ] [[package]] name = "jmespath" version = "0.10.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/3c/56/3f325b1eef9791759784aa5046a8f6a1aff8f7c898a2e34506771d3b99d8/jmespath-0.10.0.tar.gz", hash = "sha256:b85d0567b8666149a93172712e68920734333c0ce7e89b78b3e987f71e5ed4f9", size = 21607 } +sdist = { url = "https://files.pythonhosted.org/packages/3c/56/3f325b1eef9791759784aa5046a8f6a1aff8f7c898a2e34506771d3b99d8/jmespath-0.10.0.tar.gz", hash = "sha256:b85d0567b8666149a93172712e68920734333c0ce7e89b78b3e987f71e5ed4f9", size = 21607, upload-time = "2020-05-12T22:03:47.267Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/07/cb/5f001272b6faeb23c1c9e0acc04d48eaaf5c862c17709d20e3469c6e0139/jmespath-0.10.0-py2.py3-none-any.whl", hash = "sha256:cdf6525904cc597730141d61b36f2e4b8ecc257c420fa2f4549bac2c2d0cb72f", size = 24489 }, + { url = "https://files.pythonhosted.org/packages/07/cb/5f001272b6faeb23c1c9e0acc04d48eaaf5c862c17709d20e3469c6e0139/jmespath-0.10.0-py2.py3-none-any.whl", hash = "sha256:cdf6525904cc597730141d61b36f2e4b8ecc257c420fa2f4549bac2c2d0cb72f", size = 24489, upload-time = "2020-05-12T22:03:45.643Z" }, ] [[package]] name = "joblib" -version = "1.4.2" +version = "1.5.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/64/33/60135848598c076ce4b231e1b1895170f45fbcaeaa2c9d5e38b04db70c35/joblib-1.4.2.tar.gz", hash = "sha256:2382c5816b2636fbd20a09e0f4e9dad4736765fdfb7dca582943b9c1366b3f0e", size = 2116621 } +sdist = { url = "https://files.pythonhosted.org/packages/dc/fe/0f5a938c54105553436dbff7a61dc4fed4b1b2c98852f8833beaf4d5968f/joblib-1.5.1.tar.gz", hash = "sha256:f4f86e351f39fe3d0d32a9f2c3d8af1ee4cec285aafcb27003dda5205576b444", size = 330475, upload-time = "2025-05-23T12:04:37.097Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/91/29/df4b9b42f2be0b623cbd5e2140cafcaa2bef0759a00b7b70104dcfe2fb51/joblib-1.4.2-py3-none-any.whl", hash = "sha256:06d478d5674cbc267e7496a410ee875abd68e4340feff4490bcb7afb88060ae6", size = 301817 }, + { url = "https://files.pythonhosted.org/packages/7d/4f/1195bbac8e0c2acc5f740661631d8d750dc38d4a32b23ee5df3cde6f4e0d/joblib-1.5.1-py3-none-any.whl", hash = "sha256:4719a31f054c7d766948dcd83e9613686b27114f190f717cec7eaa2084f8a74a", size = 307746, upload-time = "2025-05-23T12:04:35.124Z" }, ] [[package]] name = "json-repair" -version = "0.41.1" +version = "0.46.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/6d/6a/6c7a75a10da6dc807b582f2449034da1ed74415e8899746bdfff97109012/json_repair-0.41.1.tar.gz", hash = "sha256:bba404b0888c84a6b86ecc02ec43b71b673cfee463baf6da94e079c55b136565", size = 31208 } +sdist = { url = "https://files.pythonhosted.org/packages/79/5a/5c14f14735438eb27fd4eb9bb4eb273c996c0d1d959182382cbc618f99dd/json_repair-0.46.0.tar.gz", hash = "sha256:abc751162baf8e384685558acba978478e833c1207be31468d9babfaf8029ab6", size = 33321, upload-time = "2025-05-22T06:22:07.305Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/10/5c/abd7495c934d9af5c263c2245ae30cfaa716c3c0cf027b2b8fa686ee7bd4/json_repair-0.41.1-py3-none-any.whl", hash = "sha256:0e181fd43a696887881fe19fed23422a54b3e4c558b6ff27a86a8c3ddde9ae79", size = 21578 }, -] - -[[package]] -name = "jsonpath-python" -version = "1.0.6" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b5/49/e582e50b0c54c1b47e714241c4a4767bf28758bf90212248aea8e1ce8516/jsonpath-python-1.0.6.tar.gz", hash = "sha256:dd5be4a72d8a2995c3f583cf82bf3cd1a9544cfdabf2d22595b67aff07349666", size = 18121 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/16/8a/d63959f4eff03893a00e6e63592e3a9f15b9266ed8e0275ab77f8c7dbc94/jsonpath_python-1.0.6-py3-none-any.whl", hash = "sha256:1e3b78df579f5efc23565293612decee04214609208a2335884b3ee3f786b575", size = 7552 }, + { url = "https://files.pythonhosted.org/packages/33/ea/8ca71883f10acf4449720b86dd85cea51d7a71bec2558d5275ae0dcc66e4/json_repair-0.46.0-py3-none-any.whl", hash = "sha256:54d6a9889fba0846b80befb2b1aca619103ad3ed74612fb3fedd965a4a3b1653", size = 22255, upload-time = "2025-05-22T06:22:06.206Z" }, ] [[package]] name = "jsonschema" -version = "4.23.0" +version = "4.24.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "attrs" }, @@ -2553,35 +2692,36 @@ dependencies = [ { name = "referencing" }, { name = "rpds-py" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/38/2e/03362ee4034a4c917f697890ccd4aec0800ccf9ded7f511971c75451deec/jsonschema-4.23.0.tar.gz", hash = "sha256:d71497fef26351a33265337fa77ffeb82423f3ea21283cd9467bb03999266bc4", size = 325778 } +sdist = { url = "https://files.pythonhosted.org/packages/bf/d3/1cf5326b923a53515d8f3a2cd442e6d7e94fcc444716e879ea70a0ce3177/jsonschema-4.24.0.tar.gz", hash = "sha256:0b4e8069eb12aedfa881333004bccaec24ecef5a8a6a4b6df142b2cc9599d196", size = 353480, upload-time = "2025-05-26T18:48:10.459Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/69/4a/4f9dbeb84e8850557c02365a0eee0649abe5eb1d84af92a25731c6c0f922/jsonschema-4.23.0-py3-none-any.whl", hash = "sha256:fbadb6f8b144a8f8cf9f0b89ba94501d143e50411a1278633f56a7acf7fd5566", size = 88462 }, + { url = "https://files.pythonhosted.org/packages/a2/3d/023389198f69c722d039351050738d6755376c8fd343e91dc493ea485905/jsonschema-4.24.0-py3-none-any.whl", hash = "sha256:a462455f19f5faf404a7902952b6f0e3ce868f3ee09a359b05eca6673bd8412d", size = 88709, upload-time = "2025-05-26T18:48:08.417Z" }, ] [[package]] name = "jsonschema-specifications" -version = "2024.10.1" +version = "2025.4.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "referencing" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/10/db/58f950c996c793472e336ff3655b13fbcf1e3b359dcf52dcf3ed3b52c352/jsonschema_specifications-2024.10.1.tar.gz", hash = "sha256:0f38b83639958ce1152d02a7f062902c41c8fd20d558b0c34344292d417ae272", size = 15561 } +sdist = { url = "https://files.pythonhosted.org/packages/bf/ce/46fbd9c8119cfc3581ee5643ea49464d168028cfb5caff5fc0596d0cf914/jsonschema_specifications-2025.4.1.tar.gz", hash = "sha256:630159c9f4dbea161a6a2205c3011cc4f18ff381b189fff48bb39b9bf26ae608", size = 15513, upload-time = "2025-04-23T12:34:07.418Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d1/0f/8910b19ac0670a0f80ce1008e5e751c4a57e14d2c4c13a482aa6079fa9d6/jsonschema_specifications-2024.10.1-py3-none-any.whl", hash = "sha256:a09a0680616357d9a0ecf05c12ad234479f549239d0f5b55f3deea67475da9bf", size = 18459 }, + { url = "https://files.pythonhosted.org/packages/01/0e/b27cdbaccf30b890c40ed1da9fd4a3593a5cf94dae54fb34f8a4b74fcd3f/jsonschema_specifications-2025.4.1-py3-none-any.whl", hash = "sha256:4653bffbd6584f7de83a67e0d620ef16900b390ddc7939d56684d6c81e33f1af", size = 18437, upload-time = "2025-04-23T12:34:05.422Z" }, ] [[package]] name = "kombu" -version = "5.5.2" +version = "5.5.4" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "amqp" }, + { name = "packaging" }, { name = "tzdata" }, { name = "vine" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c8/12/7a340f48920f30d6febb65d0c4aca70ed01b29e116131152977df78a9a39/kombu-5.5.2.tar.gz", hash = "sha256:2dd27ec84fd843a4e0a7187424313f87514b344812cb98c25daddafbb6a7ff0e", size = 461522 } +sdist = { url = "https://files.pythonhosted.org/packages/0f/d3/5ff936d8319ac86b9c409f1501b07c426e6ad41966fedace9ef1b966e23f/kombu-5.5.4.tar.gz", hash = "sha256:886600168275ebeada93b888e831352fe578168342f0d1d5833d88ba0d847363", size = 461992, upload-time = "2025-06-01T10:19:22.281Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/af/ba/939f3db0fca87715c883e42cc93045347d61a9d519c270a38e54a06db6e1/kombu-5.5.2-py3-none-any.whl", hash = "sha256:40f3674ed19603b8a771b6c74de126dbf8879755a0337caac6602faa82d539cd", size = 209763 }, + { url = "https://files.pythonhosted.org/packages/ef/70/a07dcf4f62598c8ad579df241af55ced65bed76e42e45d3c368a6d82dbc1/kombu-5.5.4-py3-none-any.whl", hash = "sha256:a12ed0557c238897d8e518f1d1fdf84bd1516c5e305af2dacd85c2015115feb8", size = 210034, upload-time = "2025-06-01T10:19:20.436Z" }, ] [[package]] @@ -2601,9 +2741,9 @@ dependencies = [ { name = "urllib3" }, { name = "websocket-client" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/b7/e8/0598f0e8b4af37cd9b10d8b87386cf3173cb8045d834ab5f6ec347a758b3/kubernetes-32.0.1.tar.gz", hash = "sha256:42f43d49abd437ada79a79a16bd48a604d3471a117a8347e87db693f2ba0ba28", size = 946691 } +sdist = { url = "https://files.pythonhosted.org/packages/b7/e8/0598f0e8b4af37cd9b10d8b87386cf3173cb8045d834ab5f6ec347a758b3/kubernetes-32.0.1.tar.gz", hash = "sha256:42f43d49abd437ada79a79a16bd48a604d3471a117a8347e87db693f2ba0ba28", size = 946691, upload-time = "2025-02-18T21:06:34.148Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/08/10/9f8af3e6f569685ce3af7faab51c8dd9d93b9c38eba339ca31c746119447/kubernetes-32.0.1-py2.py3-none-any.whl", hash = "sha256:35282ab8493b938b08ab5526c7ce66588232df00ef5e1dbe88a419107dc10998", size = 1988070 }, + { url = "https://files.pythonhosted.org/packages/08/10/9f8af3e6f569685ce3af7faab51c8dd9d93b9c38eba339ca31c746119447/kubernetes-32.0.1-py2.py3-none-any.whl", hash = "sha256:35282ab8493b938b08ab5526c7ce66588232df00ef5e1dbe88a419107dc10998", size = 1988070, upload-time = "2025-02-18T21:06:31.391Z" }, ] [[package]] @@ -2613,7 +2753,7 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "six" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/0e/72/a3add0e4eec4eb9e2569554f7c70f4a3c27712f40e3284d483e88094cc0e/langdetect-1.0.9.tar.gz", hash = "sha256:cbc1fef89f8d062739774bd51eda3da3274006b3661d199c2655f6b3f6d605a0", size = 981474 } +sdist = { url = "https://files.pythonhosted.org/packages/0e/72/a3add0e4eec4eb9e2569554f7c70f4a3c27712f40e3284d483e88094cc0e/langdetect-1.0.9.tar.gz", hash = "sha256:cbc1fef89f8d062739774bd51eda3da3274006b3661d199c2655f6b3f6d605a0", size = 981474, upload-time = "2021-05-07T07:54:13.562Z" } [[package]] name = "langfuse" @@ -2628,9 +2768,9 @@ dependencies = [ { name = "pydantic" }, { name = "wrapt" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/3c/e9/22c9c05d877ab85da6d9008aaa7360f2a9ad58787a8e36e00b1b5be9a990/langfuse-2.51.5.tar.gz", hash = "sha256:55bc37b5c5d3ae133c1a95db09117cfb3117add110ba02ebbf2ce45ac4395c5b", size = 117574 } +sdist = { url = "https://files.pythonhosted.org/packages/3c/e9/22c9c05d877ab85da6d9008aaa7360f2a9ad58787a8e36e00b1b5be9a990/langfuse-2.51.5.tar.gz", hash = "sha256:55bc37b5c5d3ae133c1a95db09117cfb3117add110ba02ebbf2ce45ac4395c5b", size = 117574, upload-time = "2024-10-09T00:59:15.016Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/03/f7/242a13ca094c78464b7d4df77dfe7d4c44ed77b15fed3d2e3486afa5d2e1/langfuse-2.51.5-py3-none-any.whl", hash = "sha256:b95401ca710ef94b521afa6541933b6f93d7cfd4a97523c8fc75bca4d6d219fb", size = 214281 }, + { url = "https://files.pythonhosted.org/packages/03/f7/242a13ca094c78464b7d4df77dfe7d4c44ed77b15fed3d2e3486afa5d2e1/langfuse-2.51.5-py3-none-any.whl", hash = "sha256:b95401ca710ef94b521afa6541933b6f93d7cfd4a97523c8fc75bca4d6d219fb", size = 214281, upload-time = "2024-10-09T00:59:12.596Z" }, ] [[package]] @@ -2644,9 +2784,9 @@ dependencies = [ { name = "requests" }, { name = "requests-toolbelt" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/6c/56/201dd94d492ae47c1bf9b50cacc1985113dc2288d8f15857e1f4a6818376/langsmith-0.1.147.tar.gz", hash = "sha256:2e933220318a4e73034657103b3b1a3a6109cc5db3566a7e8e03be8d6d7def7a", size = 300453 } +sdist = { url = "https://files.pythonhosted.org/packages/6c/56/201dd94d492ae47c1bf9b50cacc1985113dc2288d8f15857e1f4a6818376/langsmith-0.1.147.tar.gz", hash = "sha256:2e933220318a4e73034657103b3b1a3a6109cc5db3566a7e8e03be8d6d7def7a", size = 300453, upload-time = "2024-11-27T17:32:41.297Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/de/f0/63b06b99b730b9954f8709f6f7d9b8d076fa0a973e472efe278089bde42b/langsmith-0.1.147-py3-none-any.whl", hash = "sha256:7166fc23b965ccf839d64945a78e9f1157757add228b086141eb03a60d699a15", size = 311812 }, + { url = "https://files.pythonhosted.org/packages/de/f0/63b06b99b730b9954f8709f6f7d9b8d076fa0a973e472efe278089bde42b/langsmith-0.1.147-py3-none-any.whl", hash = "sha256:7166fc23b965ccf839d64945a78e9f1157757add228b086141eb03a60d699a15", size = 311812, upload-time = "2024-11-27T17:32:39.569Z" }, ] [[package]] @@ -2656,44 +2796,44 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "rapidfuzz" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/7e/b3/b5f8011483ba9083a0bc74c4d58705e9cf465fbe55c948a1b1357d0a2aa8/levenshtein-0.27.1.tar.gz", hash = "sha256:3e18b73564cfc846eec94dd13fab6cb006b5d2e0cc56bad1fd7d5585881302e3", size = 382571 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/22/84/110136e740655779aceb0da2399977362f21b2dbf3ea3646557f9c2237c4/levenshtein-0.27.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2e6f1760108319a108dceb2f02bc7cdb78807ad1f9c673c95eaa1d0fe5dfcaae", size = 174555 }, - { url = "https://files.pythonhosted.org/packages/19/5b/176d96959f5c5969f356d8856f8e20d2e72f7e4879f6d1cda8e5c2ac2614/levenshtein-0.27.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c4ed8400d94ab348099395e050b8ed9dd6a5d6b5b9e75e78b2b3d0b5f5b10f38", size = 156286 }, - { url = "https://files.pythonhosted.org/packages/2a/2d/a75abaafc8a46b0dc52ab14dc96708989a31799a02a4914f9210c3415f04/levenshtein-0.27.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7826efe51be8ff58bc44a633e022fdd4b9fc07396375a6dbc4945a3bffc7bf8f", size = 152413 }, - { url = "https://files.pythonhosted.org/packages/9a/5f/533f4adf964b10817a1d0ecca978b3542b3b9915c96172d20162afe18bed/levenshtein-0.27.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ff5afb78719659d353055863c7cb31599fbea6865c0890b2d840ee40214b3ddb", size = 184236 }, - { url = "https://files.pythonhosted.org/packages/02/79/e698623795e36e0d166a3aa1eac6fe1e446cac3a5c456664a95c351571d1/levenshtein-0.27.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:201dafd5c004cd52018560cf3213da799534d130cf0e4db839b51f3f06771de0", size = 185502 }, - { url = "https://files.pythonhosted.org/packages/ac/94/76b64762f4af6e20bbab79713c4c48783240e6e502b2f52e5037ddda688a/levenshtein-0.27.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e5ddd59f3cfaec216811ee67544779d9e2d6ed33f79337492a248245d6379e3d", size = 161749 }, - { url = "https://files.pythonhosted.org/packages/56/d0/d10eff9224c94a478078a469aaeb43471fdeddad035f443091224c7544b8/levenshtein-0.27.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6afc241d27ecf5b921063b796812c55b0115423ca6fa4827aa4b1581643d0a65", size = 246686 }, - { url = "https://files.pythonhosted.org/packages/b2/8a/ebbeff74461da3230d00e8a8197480a2ea1a9bbb7dbc273214d7ea3896cb/levenshtein-0.27.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:ee2e766277cceb8ca9e584ea03b8dc064449ba588d3e24c1923e4b07576db574", size = 1116616 }, - { url = "https://files.pythonhosted.org/packages/1d/9b/e7323684f833ede13113fba818c3afe665a78b47d720afdeb2e530c1ecb3/levenshtein-0.27.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:920b23d6109453913ce78ec451bc402ff19d020ee8be4722e9d11192ec2fac6f", size = 1401483 }, - { url = "https://files.pythonhosted.org/packages/ef/1d/9b6ab30ff086a33492d6f7de86a07050b15862ccf0d9feeccfbe26af52d8/levenshtein-0.27.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:560d7edba126e2eea3ac3f2f12e7bd8bc9c6904089d12b5b23b6dfa98810b209", size = 1225805 }, - { url = "https://files.pythonhosted.org/packages/1b/07/ae2f31e87ff65ba4857e25192646f1f3c8cca83c2ac1c27e551215b7e1b6/levenshtein-0.27.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:8d5362b6c7aa4896dc0cb1e7470a4ad3c06124e0af055dda30d81d3c5549346b", size = 1419860 }, - { url = "https://files.pythonhosted.org/packages/43/d2/dfcc5c22c07bab9be99f3f47a907be583bcd37bfd2eec57a205e59671019/levenshtein-0.27.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:65ba880815b0f80a80a293aeebac0fab8069d03ad2d6f967a886063458f9d7a1", size = 1188823 }, - { url = "https://files.pythonhosted.org/packages/8b/96/713335623f8ab50eba0627c8685618dc3a985aedaaea9f492986b9443551/levenshtein-0.27.1-cp311-cp311-win32.whl", hash = "sha256:fcc08effe77fec0bc5b0f6f10ff20b9802b961c4a69047b5499f383119ddbe24", size = 88156 }, - { url = "https://files.pythonhosted.org/packages/aa/ae/444d6e8ba9a35379a56926716f18bb2e77c6cf69e5324521fbe6885f14f6/levenshtein-0.27.1-cp311-cp311-win_amd64.whl", hash = "sha256:0ed402d8902be7df212ac598fc189f9b2d520817fdbc6a05e2ce44f7f3ef6857", size = 100399 }, - { url = "https://files.pythonhosted.org/packages/80/c0/ff226897a238a2deb2ca2c00d658755a1aa01884b0ddc8f5d406cb5f2b0d/levenshtein-0.27.1-cp311-cp311-win_arm64.whl", hash = "sha256:7fdaab29af81a8eb981043737f42450efca64b9761ca29385487b29c506da5b5", size = 88033 }, - { url = "https://files.pythonhosted.org/packages/0d/73/84a7126b9e6441c2547f1fbfd65f3c15c387d1fc04e0dd1d025a12107771/levenshtein-0.27.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:25fb540d8c55d1dc7bdc59b7de518ea5ed9df92eb2077e74bcb9bb6de7b06f69", size = 173953 }, - { url = "https://files.pythonhosted.org/packages/8f/5c/06c01870c0cf336f9f29397bbfbfbbfd3a59918868716e7bb15828e89367/levenshtein-0.27.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f09cfab6387e9c908c7b37961c045e8e10eb9b7ec4a700367f8e080ee803a562", size = 156399 }, - { url = "https://files.pythonhosted.org/packages/c7/4a/c1d3f27ec8b3fff5a96617251bf3f61c67972869ac0a0419558fc3e2cbe6/levenshtein-0.27.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dafa29c0e616f322b574e0b2aeb5b1ff2f8d9a1a6550f22321f3bd9bb81036e3", size = 151061 }, - { url = "https://files.pythonhosted.org/packages/4d/8f/2521081e9a265891edf46aa30e1b59c1f347a452aed4c33baafbec5216fa/levenshtein-0.27.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:be7a7642ea64392fa1e6ef7968c2e50ef2152c60948f95d0793361ed97cf8a6f", size = 183119 }, - { url = "https://files.pythonhosted.org/packages/1f/a0/a63e3bce6376127596d04be7f57e672d2f3d5f540265b1e30b9dd9b3c5a9/levenshtein-0.27.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:060b48c45ed54bcea9582ce79c6365b20a1a7473767e0b3d6be712fa3a22929c", size = 185352 }, - { url = "https://files.pythonhosted.org/packages/17/8c/8352e992063952b38fb61d49bad8d193a4a713e7eeceb3ae74b719d7863d/levenshtein-0.27.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:712f562c5e64dd0398d3570fe99f8fbb88acec7cc431f101cb66c9d22d74c542", size = 159879 }, - { url = "https://files.pythonhosted.org/packages/69/b4/564866e2038acf47c3de3e9292fc7fc7cc18d2593fedb04f001c22ac6e15/levenshtein-0.27.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a6141ad65cab49aa4527a3342d76c30c48adb2393b6cdfeca65caae8d25cb4b8", size = 245005 }, - { url = "https://files.pythonhosted.org/packages/ba/f9/7367f87e3a6eed282f3654ec61a174b4d1b78a7a73f2cecb91f0ab675153/levenshtein-0.27.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:799b8d73cda3265331116f62932f553804eae16c706ceb35aaf16fc2a704791b", size = 1116865 }, - { url = "https://files.pythonhosted.org/packages/f5/02/b5b3bfb4b4cd430e9d110bad2466200d51c6061dae7c5a64e36047c8c831/levenshtein-0.27.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:ec99871d98e517e1cc4a15659c62d6ea63ee5a2d72c5ddbebd7bae8b9e2670c8", size = 1401723 }, - { url = "https://files.pythonhosted.org/packages/ef/69/b93bccd093b3f06a99e67e11ebd6e100324735dc2834958ba5852a1b9fed/levenshtein-0.27.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:8799164e1f83588dbdde07f728ea80796ea72196ea23484d78d891470241b222", size = 1226276 }, - { url = "https://files.pythonhosted.org/packages/ab/32/37dd1bc5ce866c136716619e6f7081d7078d7dd1c1da7025603dcfd9cf5f/levenshtein-0.27.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:583943813898326516ab451a83f734c6f07488cda5c361676150d3e3e8b47927", size = 1420132 }, - { url = "https://files.pythonhosted.org/packages/4b/08/f3bc828dd9f0f8433b26f37c4fceab303186ad7b9b70819f2ccb493d99fc/levenshtein-0.27.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5bb22956af44bb4eade93546bf95be610c8939b9a9d4d28b2dfa94abf454fed7", size = 1189144 }, - { url = "https://files.pythonhosted.org/packages/2d/54/5ecd89066cf579223d504abe3ac37ba11f63b01a19fd12591083acc00eb6/levenshtein-0.27.1-cp312-cp312-win32.whl", hash = "sha256:d9099ed1bcfa7ccc5540e8ad27b5dc6f23d16addcbe21fdd82af6440f4ed2b6d", size = 88279 }, - { url = "https://files.pythonhosted.org/packages/53/79/4f8fabcc5aca9305b494d1d6c7a98482e90a855e0050ae9ff5d7bf4ab2c6/levenshtein-0.27.1-cp312-cp312-win_amd64.whl", hash = "sha256:7f071ecdb50aa6c15fd8ae5bcb67e9da46ba1df7bba7c6bf6803a54c7a41fd96", size = 100659 }, - { url = "https://files.pythonhosted.org/packages/cb/81/f8e4c0f571c2aac2e0c56a6e0e41b679937a2b7013e79415e4aef555cff0/levenshtein-0.27.1-cp312-cp312-win_arm64.whl", hash = "sha256:83b9033a984ccace7703f35b688f3907d55490182fd39b33a8e434d7b2e249e6", size = 88168 }, - { url = "https://files.pythonhosted.org/packages/7d/44/c5955d0b6830925559b00617d80c9f6e03a9b00c451835ee4da7010e71cd/levenshtein-0.27.1-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:909b7b6bce27a4ec90576c9a9bd9af5a41308dfecf364b410e80b58038277bbe", size = 170533 }, - { url = "https://files.pythonhosted.org/packages/e7/3f/858572d68b33e13a9c154b99f153317efe68381bf63cc4e986e820935fc3/levenshtein-0.27.1-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:d193a7f97b8c6a350e36ec58e41a627c06fa4157c3ce4b2b11d90cfc3c2ebb8f", size = 153119 }, - { url = "https://files.pythonhosted.org/packages/d1/60/2bd8d001ea4eb53ca16faa7a649d56005ba22b1bcc2a4f1617ab27ed7e48/levenshtein-0.27.1-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:614be316e3c06118705fae1f717f9072d35108e5fd4e66a7dd0e80356135340b", size = 149576 }, - { url = "https://files.pythonhosted.org/packages/e4/db/0580797e1e4ac26cf67761a235b29b49f62d2b175dbbc609882f2aecd4e4/levenshtein-0.27.1-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:31fc0a5bb070722bdabb6f7e14955a294a4a968c68202d294699817f21545d22", size = 157445 }, - { url = "https://files.pythonhosted.org/packages/f4/de/9c171c96d1f15c900086d7212b5543a85539e767689fc4933d14048ba1ec/levenshtein-0.27.1-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9415aa5257227af543be65768a80c7a75e266c3c818468ce6914812f88f9c3df", size = 243141 }, - { url = "https://files.pythonhosted.org/packages/dc/1e/408fd10217eac0e43aea0604be22b4851a09e03d761d44d4ea12089dd70e/levenshtein-0.27.1-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:7987ef006a3cf56a4532bd4c90c2d3b7b4ca9ad3bf8ae1ee5713c4a3bdfda913", size = 98045 }, +sdist = { url = "https://files.pythonhosted.org/packages/7e/b3/b5f8011483ba9083a0bc74c4d58705e9cf465fbe55c948a1b1357d0a2aa8/levenshtein-0.27.1.tar.gz", hash = "sha256:3e18b73564cfc846eec94dd13fab6cb006b5d2e0cc56bad1fd7d5585881302e3", size = 382571, upload-time = "2025-03-02T19:44:56.148Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/22/84/110136e740655779aceb0da2399977362f21b2dbf3ea3646557f9c2237c4/levenshtein-0.27.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2e6f1760108319a108dceb2f02bc7cdb78807ad1f9c673c95eaa1d0fe5dfcaae", size = 174555, upload-time = "2025-03-02T19:42:51.781Z" }, + { url = "https://files.pythonhosted.org/packages/19/5b/176d96959f5c5969f356d8856f8e20d2e72f7e4879f6d1cda8e5c2ac2614/levenshtein-0.27.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c4ed8400d94ab348099395e050b8ed9dd6a5d6b5b9e75e78b2b3d0b5f5b10f38", size = 156286, upload-time = "2025-03-02T19:42:53.106Z" }, + { url = "https://files.pythonhosted.org/packages/2a/2d/a75abaafc8a46b0dc52ab14dc96708989a31799a02a4914f9210c3415f04/levenshtein-0.27.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7826efe51be8ff58bc44a633e022fdd4b9fc07396375a6dbc4945a3bffc7bf8f", size = 152413, upload-time = "2025-03-02T19:42:55.129Z" }, + { url = "https://files.pythonhosted.org/packages/9a/5f/533f4adf964b10817a1d0ecca978b3542b3b9915c96172d20162afe18bed/levenshtein-0.27.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ff5afb78719659d353055863c7cb31599fbea6865c0890b2d840ee40214b3ddb", size = 184236, upload-time = "2025-03-02T19:42:56.427Z" }, + { url = "https://files.pythonhosted.org/packages/02/79/e698623795e36e0d166a3aa1eac6fe1e446cac3a5c456664a95c351571d1/levenshtein-0.27.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:201dafd5c004cd52018560cf3213da799534d130cf0e4db839b51f3f06771de0", size = 185502, upload-time = "2025-03-02T19:42:57.596Z" }, + { url = "https://files.pythonhosted.org/packages/ac/94/76b64762f4af6e20bbab79713c4c48783240e6e502b2f52e5037ddda688a/levenshtein-0.27.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e5ddd59f3cfaec216811ee67544779d9e2d6ed33f79337492a248245d6379e3d", size = 161749, upload-time = "2025-03-02T19:42:59.222Z" }, + { url = "https://files.pythonhosted.org/packages/56/d0/d10eff9224c94a478078a469aaeb43471fdeddad035f443091224c7544b8/levenshtein-0.27.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6afc241d27ecf5b921063b796812c55b0115423ca6fa4827aa4b1581643d0a65", size = 246686, upload-time = "2025-03-02T19:43:00.454Z" }, + { url = "https://files.pythonhosted.org/packages/b2/8a/ebbeff74461da3230d00e8a8197480a2ea1a9bbb7dbc273214d7ea3896cb/levenshtein-0.27.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:ee2e766277cceb8ca9e584ea03b8dc064449ba588d3e24c1923e4b07576db574", size = 1116616, upload-time = "2025-03-02T19:43:02.431Z" }, + { url = "https://files.pythonhosted.org/packages/1d/9b/e7323684f833ede13113fba818c3afe665a78b47d720afdeb2e530c1ecb3/levenshtein-0.27.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:920b23d6109453913ce78ec451bc402ff19d020ee8be4722e9d11192ec2fac6f", size = 1401483, upload-time = "2025-03-02T19:43:04.62Z" }, + { url = "https://files.pythonhosted.org/packages/ef/1d/9b6ab30ff086a33492d6f7de86a07050b15862ccf0d9feeccfbe26af52d8/levenshtein-0.27.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:560d7edba126e2eea3ac3f2f12e7bd8bc9c6904089d12b5b23b6dfa98810b209", size = 1225805, upload-time = "2025-03-02T19:43:06.734Z" }, + { url = "https://files.pythonhosted.org/packages/1b/07/ae2f31e87ff65ba4857e25192646f1f3c8cca83c2ac1c27e551215b7e1b6/levenshtein-0.27.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:8d5362b6c7aa4896dc0cb1e7470a4ad3c06124e0af055dda30d81d3c5549346b", size = 1419860, upload-time = "2025-03-02T19:43:08.084Z" }, + { url = "https://files.pythonhosted.org/packages/43/d2/dfcc5c22c07bab9be99f3f47a907be583bcd37bfd2eec57a205e59671019/levenshtein-0.27.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:65ba880815b0f80a80a293aeebac0fab8069d03ad2d6f967a886063458f9d7a1", size = 1188823, upload-time = "2025-03-02T19:43:09.592Z" }, + { url = "https://files.pythonhosted.org/packages/8b/96/713335623f8ab50eba0627c8685618dc3a985aedaaea9f492986b9443551/levenshtein-0.27.1-cp311-cp311-win32.whl", hash = "sha256:fcc08effe77fec0bc5b0f6f10ff20b9802b961c4a69047b5499f383119ddbe24", size = 88156, upload-time = "2025-03-02T19:43:11.442Z" }, + { url = "https://files.pythonhosted.org/packages/aa/ae/444d6e8ba9a35379a56926716f18bb2e77c6cf69e5324521fbe6885f14f6/levenshtein-0.27.1-cp311-cp311-win_amd64.whl", hash = "sha256:0ed402d8902be7df212ac598fc189f9b2d520817fdbc6a05e2ce44f7f3ef6857", size = 100399, upload-time = "2025-03-02T19:43:13.066Z" }, + { url = "https://files.pythonhosted.org/packages/80/c0/ff226897a238a2deb2ca2c00d658755a1aa01884b0ddc8f5d406cb5f2b0d/levenshtein-0.27.1-cp311-cp311-win_arm64.whl", hash = "sha256:7fdaab29af81a8eb981043737f42450efca64b9761ca29385487b29c506da5b5", size = 88033, upload-time = "2025-03-02T19:43:14.211Z" }, + { url = "https://files.pythonhosted.org/packages/0d/73/84a7126b9e6441c2547f1fbfd65f3c15c387d1fc04e0dd1d025a12107771/levenshtein-0.27.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:25fb540d8c55d1dc7bdc59b7de518ea5ed9df92eb2077e74bcb9bb6de7b06f69", size = 173953, upload-time = "2025-03-02T19:43:16.029Z" }, + { url = "https://files.pythonhosted.org/packages/8f/5c/06c01870c0cf336f9f29397bbfbfbbfd3a59918868716e7bb15828e89367/levenshtein-0.27.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f09cfab6387e9c908c7b37961c045e8e10eb9b7ec4a700367f8e080ee803a562", size = 156399, upload-time = "2025-03-02T19:43:17.233Z" }, + { url = "https://files.pythonhosted.org/packages/c7/4a/c1d3f27ec8b3fff5a96617251bf3f61c67972869ac0a0419558fc3e2cbe6/levenshtein-0.27.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dafa29c0e616f322b574e0b2aeb5b1ff2f8d9a1a6550f22321f3bd9bb81036e3", size = 151061, upload-time = "2025-03-02T19:43:18.414Z" }, + { url = "https://files.pythonhosted.org/packages/4d/8f/2521081e9a265891edf46aa30e1b59c1f347a452aed4c33baafbec5216fa/levenshtein-0.27.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:be7a7642ea64392fa1e6ef7968c2e50ef2152c60948f95d0793361ed97cf8a6f", size = 183119, upload-time = "2025-03-02T19:43:19.975Z" }, + { url = "https://files.pythonhosted.org/packages/1f/a0/a63e3bce6376127596d04be7f57e672d2f3d5f540265b1e30b9dd9b3c5a9/levenshtein-0.27.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:060b48c45ed54bcea9582ce79c6365b20a1a7473767e0b3d6be712fa3a22929c", size = 185352, upload-time = "2025-03-02T19:43:21.424Z" }, + { url = "https://files.pythonhosted.org/packages/17/8c/8352e992063952b38fb61d49bad8d193a4a713e7eeceb3ae74b719d7863d/levenshtein-0.27.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:712f562c5e64dd0398d3570fe99f8fbb88acec7cc431f101cb66c9d22d74c542", size = 159879, upload-time = "2025-03-02T19:43:22.792Z" }, + { url = "https://files.pythonhosted.org/packages/69/b4/564866e2038acf47c3de3e9292fc7fc7cc18d2593fedb04f001c22ac6e15/levenshtein-0.27.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a6141ad65cab49aa4527a3342d76c30c48adb2393b6cdfeca65caae8d25cb4b8", size = 245005, upload-time = "2025-03-02T19:43:24.069Z" }, + { url = "https://files.pythonhosted.org/packages/ba/f9/7367f87e3a6eed282f3654ec61a174b4d1b78a7a73f2cecb91f0ab675153/levenshtein-0.27.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:799b8d73cda3265331116f62932f553804eae16c706ceb35aaf16fc2a704791b", size = 1116865, upload-time = "2025-03-02T19:43:25.4Z" }, + { url = "https://files.pythonhosted.org/packages/f5/02/b5b3bfb4b4cd430e9d110bad2466200d51c6061dae7c5a64e36047c8c831/levenshtein-0.27.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:ec99871d98e517e1cc4a15659c62d6ea63ee5a2d72c5ddbebd7bae8b9e2670c8", size = 1401723, upload-time = "2025-03-02T19:43:28.099Z" }, + { url = "https://files.pythonhosted.org/packages/ef/69/b93bccd093b3f06a99e67e11ebd6e100324735dc2834958ba5852a1b9fed/levenshtein-0.27.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:8799164e1f83588dbdde07f728ea80796ea72196ea23484d78d891470241b222", size = 1226276, upload-time = "2025-03-02T19:43:30.192Z" }, + { url = "https://files.pythonhosted.org/packages/ab/32/37dd1bc5ce866c136716619e6f7081d7078d7dd1c1da7025603dcfd9cf5f/levenshtein-0.27.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:583943813898326516ab451a83f734c6f07488cda5c361676150d3e3e8b47927", size = 1420132, upload-time = "2025-03-02T19:43:33.322Z" }, + { url = "https://files.pythonhosted.org/packages/4b/08/f3bc828dd9f0f8433b26f37c4fceab303186ad7b9b70819f2ccb493d99fc/levenshtein-0.27.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5bb22956af44bb4eade93546bf95be610c8939b9a9d4d28b2dfa94abf454fed7", size = 1189144, upload-time = "2025-03-02T19:43:34.814Z" }, + { url = "https://files.pythonhosted.org/packages/2d/54/5ecd89066cf579223d504abe3ac37ba11f63b01a19fd12591083acc00eb6/levenshtein-0.27.1-cp312-cp312-win32.whl", hash = "sha256:d9099ed1bcfa7ccc5540e8ad27b5dc6f23d16addcbe21fdd82af6440f4ed2b6d", size = 88279, upload-time = "2025-03-02T19:43:38.86Z" }, + { url = "https://files.pythonhosted.org/packages/53/79/4f8fabcc5aca9305b494d1d6c7a98482e90a855e0050ae9ff5d7bf4ab2c6/levenshtein-0.27.1-cp312-cp312-win_amd64.whl", hash = "sha256:7f071ecdb50aa6c15fd8ae5bcb67e9da46ba1df7bba7c6bf6803a54c7a41fd96", size = 100659, upload-time = "2025-03-02T19:43:40.082Z" }, + { url = "https://files.pythonhosted.org/packages/cb/81/f8e4c0f571c2aac2e0c56a6e0e41b679937a2b7013e79415e4aef555cff0/levenshtein-0.27.1-cp312-cp312-win_arm64.whl", hash = "sha256:83b9033a984ccace7703f35b688f3907d55490182fd39b33a8e434d7b2e249e6", size = 88168, upload-time = "2025-03-02T19:43:41.42Z" }, + { url = "https://files.pythonhosted.org/packages/7d/44/c5955d0b6830925559b00617d80c9f6e03a9b00c451835ee4da7010e71cd/levenshtein-0.27.1-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:909b7b6bce27a4ec90576c9a9bd9af5a41308dfecf364b410e80b58038277bbe", size = 170533, upload-time = "2025-03-02T19:44:38.096Z" }, + { url = "https://files.pythonhosted.org/packages/e7/3f/858572d68b33e13a9c154b99f153317efe68381bf63cc4e986e820935fc3/levenshtein-0.27.1-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:d193a7f97b8c6a350e36ec58e41a627c06fa4157c3ce4b2b11d90cfc3c2ebb8f", size = 153119, upload-time = "2025-03-02T19:44:39.388Z" }, + { url = "https://files.pythonhosted.org/packages/d1/60/2bd8d001ea4eb53ca16faa7a649d56005ba22b1bcc2a4f1617ab27ed7e48/levenshtein-0.27.1-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:614be316e3c06118705fae1f717f9072d35108e5fd4e66a7dd0e80356135340b", size = 149576, upload-time = "2025-03-02T19:44:40.617Z" }, + { url = "https://files.pythonhosted.org/packages/e4/db/0580797e1e4ac26cf67761a235b29b49f62d2b175dbbc609882f2aecd4e4/levenshtein-0.27.1-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:31fc0a5bb070722bdabb6f7e14955a294a4a968c68202d294699817f21545d22", size = 157445, upload-time = "2025-03-02T19:44:41.901Z" }, + { url = "https://files.pythonhosted.org/packages/f4/de/9c171c96d1f15c900086d7212b5543a85539e767689fc4933d14048ba1ec/levenshtein-0.27.1-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9415aa5257227af543be65768a80c7a75e266c3c818468ce6914812f88f9c3df", size = 243141, upload-time = "2025-03-02T19:44:43.228Z" }, + { url = "https://files.pythonhosted.org/packages/dc/1e/408fd10217eac0e43aea0604be22b4851a09e03d761d44d4ea12089dd70e/levenshtein-0.27.1-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:7987ef006a3cf56a4532bd4c90c2d3b7b4ca9ad3bf8ae1ee5713c4a3bdfda913", size = 98045, upload-time = "2025-03-02T19:44:44.527Z" }, ] [[package]] @@ -2713,102 +2853,102 @@ dependencies = [ { name = "tiktoken" }, { name = "tokenizers" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/5c/7a/6c1994a239abd1b335001a46ae47fa055a24c493b6de19a9fa1872187fe9/litellm-1.63.7.tar.gz", hash = "sha256:2fbd7236d5e5379eee18556857ed62a5ed49f4f09e03ff33cf15932306b984f1", size = 6598034 } +sdist = { url = "https://files.pythonhosted.org/packages/5c/7a/6c1994a239abd1b335001a46ae47fa055a24c493b6de19a9fa1872187fe9/litellm-1.63.7.tar.gz", hash = "sha256:2fbd7236d5e5379eee18556857ed62a5ed49f4f09e03ff33cf15932306b984f1", size = 6598034, upload-time = "2025-03-12T19:26:40.915Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/1e/44/255c7ecb8b6f3f730a37422736509c21cb1bf4da66cc060d872005bda9f5/litellm-1.63.7-py3-none-any.whl", hash = "sha256:fbdee39a894506c68f158c6b4e0079f9e9c023441fff7215e7b8e42162dba0a7", size = 6909807 }, + { url = "https://files.pythonhosted.org/packages/1e/44/255c7ecb8b6f3f730a37422736509c21cb1bf4da66cc060d872005bda9f5/litellm-1.63.7-py3-none-any.whl", hash = "sha256:fbdee39a894506c68f158c6b4e0079f9e9c023441fff7215e7b8e42162dba0a7", size = 6909807, upload-time = "2025-03-12T19:26:37.788Z" }, ] [[package]] name = "llvmlite" version = "0.44.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/89/6a/95a3d3610d5c75293d5dbbb2a76480d5d4eeba641557b69fe90af6c5b84e/llvmlite-0.44.0.tar.gz", hash = "sha256:07667d66a5d150abed9157ab6c0b9393c9356f229784a4385c02f99e94fc94d4", size = 171880 } +sdist = { url = "https://files.pythonhosted.org/packages/89/6a/95a3d3610d5c75293d5dbbb2a76480d5d4eeba641557b69fe90af6c5b84e/llvmlite-0.44.0.tar.gz", hash = "sha256:07667d66a5d150abed9157ab6c0b9393c9356f229784a4385c02f99e94fc94d4", size = 171880, upload-time = "2025-01-20T11:14:41.342Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b5/e2/86b245397052386595ad726f9742e5223d7aea999b18c518a50e96c3aca4/llvmlite-0.44.0-cp311-cp311-macosx_10_14_x86_64.whl", hash = "sha256:eed7d5f29136bda63b6d7804c279e2b72e08c952b7c5df61f45db408e0ee52f3", size = 28132305 }, - { url = "https://files.pythonhosted.org/packages/ff/ec/506902dc6870249fbe2466d9cf66d531265d0f3a1157213c8f986250c033/llvmlite-0.44.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ace564d9fa44bb91eb6e6d8e7754977783c68e90a471ea7ce913bff30bd62427", size = 26201090 }, - { url = "https://files.pythonhosted.org/packages/99/fe/d030f1849ebb1f394bb3f7adad5e729b634fb100515594aca25c354ffc62/llvmlite-0.44.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c5d22c3bfc842668168a786af4205ec8e3ad29fb1bc03fd11fd48460d0df64c1", size = 42361858 }, - { url = "https://files.pythonhosted.org/packages/d7/7a/ce6174664b9077fc673d172e4c888cb0b128e707e306bc33fff8c2035f0d/llvmlite-0.44.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f01a394e9c9b7b1d4e63c327b096d10f6f0ed149ef53d38a09b3749dcf8c9610", size = 41184200 }, - { url = "https://files.pythonhosted.org/packages/5f/c6/258801143975a6d09a373f2641237992496e15567b907a4d401839d671b8/llvmlite-0.44.0-cp311-cp311-win_amd64.whl", hash = "sha256:d8489634d43c20cd0ad71330dde1d5bc7b9966937a263ff1ec1cebb90dc50955", size = 30331193 }, - { url = "https://files.pythonhosted.org/packages/15/86/e3c3195b92e6e492458f16d233e58a1a812aa2bfbef9bdd0fbafcec85c60/llvmlite-0.44.0-cp312-cp312-macosx_10_14_x86_64.whl", hash = "sha256:1d671a56acf725bf1b531d5ef76b86660a5ab8ef19bb6a46064a705c6ca80aad", size = 28132297 }, - { url = "https://files.pythonhosted.org/packages/d6/53/373b6b8be67b9221d12b24125fd0ec56b1078b660eeae266ec388a6ac9a0/llvmlite-0.44.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5f79a728e0435493611c9f405168682bb75ffd1fbe6fc360733b850c80a026db", size = 26201105 }, - { url = "https://files.pythonhosted.org/packages/cb/da/8341fd3056419441286c8e26bf436923021005ece0bff5f41906476ae514/llvmlite-0.44.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0143a5ef336da14deaa8ec26c5449ad5b6a2b564df82fcef4be040b9cacfea9", size = 42361901 }, - { url = "https://files.pythonhosted.org/packages/53/ad/d79349dc07b8a395a99153d7ce8b01d6fcdc9f8231355a5df55ded649b61/llvmlite-0.44.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d752f89e31b66db6f8da06df8b39f9b91e78c5feea1bf9e8c1fba1d1c24c065d", size = 41184247 }, - { url = "https://files.pythonhosted.org/packages/e2/3b/a9a17366af80127bd09decbe2a54d8974b6d8b274b39bf47fbaedeec6307/llvmlite-0.44.0-cp312-cp312-win_amd64.whl", hash = "sha256:eae7e2d4ca8f88f89d315b48c6b741dcb925d6a1042da694aa16ab3dd4cbd3a1", size = 30332380 }, + { url = "https://files.pythonhosted.org/packages/b5/e2/86b245397052386595ad726f9742e5223d7aea999b18c518a50e96c3aca4/llvmlite-0.44.0-cp311-cp311-macosx_10_14_x86_64.whl", hash = "sha256:eed7d5f29136bda63b6d7804c279e2b72e08c952b7c5df61f45db408e0ee52f3", size = 28132305, upload-time = "2025-01-20T11:12:53.936Z" }, + { url = "https://files.pythonhosted.org/packages/ff/ec/506902dc6870249fbe2466d9cf66d531265d0f3a1157213c8f986250c033/llvmlite-0.44.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ace564d9fa44bb91eb6e6d8e7754977783c68e90a471ea7ce913bff30bd62427", size = 26201090, upload-time = "2025-01-20T11:12:59.847Z" }, + { url = "https://files.pythonhosted.org/packages/99/fe/d030f1849ebb1f394bb3f7adad5e729b634fb100515594aca25c354ffc62/llvmlite-0.44.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c5d22c3bfc842668168a786af4205ec8e3ad29fb1bc03fd11fd48460d0df64c1", size = 42361858, upload-time = "2025-01-20T11:13:07.623Z" }, + { url = "https://files.pythonhosted.org/packages/d7/7a/ce6174664b9077fc673d172e4c888cb0b128e707e306bc33fff8c2035f0d/llvmlite-0.44.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f01a394e9c9b7b1d4e63c327b096d10f6f0ed149ef53d38a09b3749dcf8c9610", size = 41184200, upload-time = "2025-01-20T11:13:20.058Z" }, + { url = "https://files.pythonhosted.org/packages/5f/c6/258801143975a6d09a373f2641237992496e15567b907a4d401839d671b8/llvmlite-0.44.0-cp311-cp311-win_amd64.whl", hash = "sha256:d8489634d43c20cd0ad71330dde1d5bc7b9966937a263ff1ec1cebb90dc50955", size = 30331193, upload-time = "2025-01-20T11:13:26.976Z" }, + { url = "https://files.pythonhosted.org/packages/15/86/e3c3195b92e6e492458f16d233e58a1a812aa2bfbef9bdd0fbafcec85c60/llvmlite-0.44.0-cp312-cp312-macosx_10_14_x86_64.whl", hash = "sha256:1d671a56acf725bf1b531d5ef76b86660a5ab8ef19bb6a46064a705c6ca80aad", size = 28132297, upload-time = "2025-01-20T11:13:32.57Z" }, + { url = "https://files.pythonhosted.org/packages/d6/53/373b6b8be67b9221d12b24125fd0ec56b1078b660eeae266ec388a6ac9a0/llvmlite-0.44.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5f79a728e0435493611c9f405168682bb75ffd1fbe6fc360733b850c80a026db", size = 26201105, upload-time = "2025-01-20T11:13:38.744Z" }, + { url = "https://files.pythonhosted.org/packages/cb/da/8341fd3056419441286c8e26bf436923021005ece0bff5f41906476ae514/llvmlite-0.44.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0143a5ef336da14deaa8ec26c5449ad5b6a2b564df82fcef4be040b9cacfea9", size = 42361901, upload-time = "2025-01-20T11:13:46.711Z" }, + { url = "https://files.pythonhosted.org/packages/53/ad/d79349dc07b8a395a99153d7ce8b01d6fcdc9f8231355a5df55ded649b61/llvmlite-0.44.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d752f89e31b66db6f8da06df8b39f9b91e78c5feea1bf9e8c1fba1d1c24c065d", size = 41184247, upload-time = "2025-01-20T11:13:56.159Z" }, + { url = "https://files.pythonhosted.org/packages/e2/3b/a9a17366af80127bd09decbe2a54d8974b6d8b274b39bf47fbaedeec6307/llvmlite-0.44.0-cp312-cp312-win_amd64.whl", hash = "sha256:eae7e2d4ca8f88f89d315b48c6b741dcb925d6a1042da694aa16ab3dd4cbd3a1", size = 30332380, upload-time = "2025-01-20T11:14:02.442Z" }, ] [[package]] name = "lxml" -version = "5.3.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/80/61/d3dc048cd6c7be6fe45b80cedcbdd4326ba4d550375f266d9f4246d0f4bc/lxml-5.3.2.tar.gz", hash = "sha256:773947d0ed809ddad824b7b14467e1a481b8976e87278ac4a730c2f7c7fcddc1", size = 3679948 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/84/b8/2b727f5a90902f7cc5548349f563b60911ca05f3b92e35dfa751349f265f/lxml-5.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9d61a7d0d208ace43986a92b111e035881c4ed45b1f5b7a270070acae8b0bfb4", size = 8163457 }, - { url = "https://files.pythonhosted.org/packages/91/84/23135b2dc72b3440d68c8f39ace2bb00fe78e3a2255f7c74f7e76f22498e/lxml-5.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:856dfd7eda0b75c29ac80a31a6411ca12209183e866c33faf46e77ace3ce8a79", size = 4433445 }, - { url = "https://files.pythonhosted.org/packages/c9/1c/6900ade2294488f80598af7b3229669562166384bb10bf4c915342a2f288/lxml-5.3.2-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7a01679e4aad0727bedd4c9407d4d65978e920f0200107ceeffd4b019bd48529", size = 5029603 }, - { url = "https://files.pythonhosted.org/packages/2f/e9/31dbe5deaccf0d33ec279cf400306ad4b32dfd1a0fee1fca40c5e90678fe/lxml-5.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b6b37b4c3acb8472d191816d4582379f64d81cecbdce1a668601745c963ca5cc", size = 4771236 }, - { url = "https://files.pythonhosted.org/packages/68/41/c3412392884130af3415af2e89a2007e00b2a782be6fb848a95b598a114c/lxml-5.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3df5a54e7b7c31755383f126d3a84e12a4e0333db4679462ef1165d702517477", size = 5369815 }, - { url = "https://files.pythonhosted.org/packages/34/0a/ba0309fd5f990ea0cc05aba2bea225ef1bcb07ecbf6c323c6b119fc46e7f/lxml-5.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c09a40f28dcded933dc16217d6a092be0cc49ae25811d3b8e937c8060647c353", size = 4843663 }, - { url = "https://files.pythonhosted.org/packages/b6/c6/663b5d87d51d00d4386a2d52742a62daa486c5dc6872a443409d9aeafece/lxml-5.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a1ef20f1851ccfbe6c5a04c67ec1ce49da16ba993fdbabdce87a92926e505412", size = 4918028 }, - { url = "https://files.pythonhosted.org/packages/75/5f/f6a72ccbe05cf83341d4b6ad162ed9e1f1ffbd12f1c4b8bc8ae413392282/lxml-5.3.2-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:f79a63289dbaba964eb29ed3c103b7911f2dce28c36fe87c36a114e6bd21d7ad", size = 4792005 }, - { url = "https://files.pythonhosted.org/packages/37/7b/8abd5b332252239ffd28df5842ee4e5bf56e1c613c323586c21ccf5af634/lxml-5.3.2-cp311-cp311-manylinux_2_28_ppc64le.whl", hash = "sha256:75a72697d95f27ae00e75086aed629f117e816387b74a2f2da6ef382b460b710", size = 5405363 }, - { url = "https://files.pythonhosted.org/packages/5a/79/549b7ec92b8d9feb13869c1b385a0749d7ccfe5590d1e60f11add9cdd580/lxml-5.3.2-cp311-cp311-manylinux_2_28_s390x.whl", hash = "sha256:b9b00c9ee1cc3a76f1f16e94a23c344e0b6e5c10bec7f94cf2d820ce303b8c01", size = 4932915 }, - { url = "https://files.pythonhosted.org/packages/57/eb/4fa626d0bac8b4f2aa1d0e6a86232db030fd0f462386daf339e4a0ee352b/lxml-5.3.2-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:77cbcab50cbe8c857c6ba5f37f9a3976499c60eada1bf6d38f88311373d7b4bc", size = 4983473 }, - { url = "https://files.pythonhosted.org/packages/1b/c8/79d61d13cbb361c2c45fbe7c8bd00ea6a23b3e64bc506264d2856c60d702/lxml-5.3.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:29424058f072a24622a0a15357bca63d796954758248a72da6d512f9bd9a4493", size = 4855284 }, - { url = "https://files.pythonhosted.org/packages/80/16/9f84e1ef03a13136ab4f9482c9adaaad425c68b47556b9d3192a782e5d37/lxml-5.3.2-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:7d82737a8afe69a7c80ef31d7626075cc7d6e2267f16bf68af2c764b45ed68ab", size = 5458355 }, - { url = "https://files.pythonhosted.org/packages/aa/6d/f62860451bb4683e87636e49effb76d499773337928e53356c1712ccec24/lxml-5.3.2-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:95473d1d50a5d9fcdb9321fdc0ca6e1edc164dce4c7da13616247d27f3d21e31", size = 5300051 }, - { url = "https://files.pythonhosted.org/packages/3f/5f/3b6c4acec17f9a57ea8bb89a658a70621db3fb86ea588e7703b6819d9b03/lxml-5.3.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:2162068f6da83613f8b2a32ca105e37a564afd0d7009b0b25834d47693ce3538", size = 5033481 }, - { url = "https://files.pythonhosted.org/packages/79/bd/3c4dd7d903bb9981f4876c61ef2ff5d5473e409ef61dc7337ac207b91920/lxml-5.3.2-cp311-cp311-win32.whl", hash = "sha256:f8695752cf5d639b4e981afe6c99e060621362c416058effd5c704bede9cb5d1", size = 3474266 }, - { url = "https://files.pythonhosted.org/packages/1f/ea/9311fa1ef75b7d601c89600fc612838ee77ad3d426184941cba9cf62641f/lxml-5.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:d1a94cbb4ee64af3ab386c2d63d6d9e9cf2e256ac0fd30f33ef0a3c88f575174", size = 3815230 }, - { url = "https://files.pythonhosted.org/packages/0d/7e/c749257a7fabc712c4df57927b0f703507f316e9f2c7e3219f8f76d36145/lxml-5.3.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:16b3897691ec0316a1aa3c6585f61c8b7978475587c5b16fc1d2c28d283dc1b0", size = 8193212 }, - { url = "https://files.pythonhosted.org/packages/a8/50/17e985ba162c9f1ca119f4445004b58f9e5ef559ded599b16755e9bfa260/lxml-5.3.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:a8d4b34a0eeaf6e73169dcfd653c8d47f25f09d806c010daf074fba2db5e2d3f", size = 4451439 }, - { url = "https://files.pythonhosted.org/packages/c2/b5/4960ba0fcca6ce394ed4a2f89ee13083e7fcbe9641a91166e8e9792fedb1/lxml-5.3.2-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9cd7a959396da425022e1e4214895b5cfe7de7035a043bcc2d11303792b67554", size = 5052146 }, - { url = "https://files.pythonhosted.org/packages/5f/d1/184b04481a5d1f5758916de087430752a7b229bddbd6c1d23405078c72bd/lxml-5.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cac5eaeec3549c5df7f8f97a5a6db6963b91639389cdd735d5a806370847732b", size = 4789082 }, - { url = "https://files.pythonhosted.org/packages/7d/75/1a19749d373e9a3d08861addccdf50c92b628c67074b22b8f3c61997cf5a/lxml-5.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:29b5f7d77334877c2146e7bb8b94e4df980325fab0a8af4d524e5d43cd6f789d", size = 5312300 }, - { url = "https://files.pythonhosted.org/packages/fb/00/9d165d4060d3f347e63b219fcea5c6a3f9193e9e2868c6801e18e5379725/lxml-5.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:13f3495cfec24e3d63fffd342cc8141355d1d26ee766ad388775f5c8c5ec3932", size = 4836655 }, - { url = "https://files.pythonhosted.org/packages/b8/e9/06720a33cc155966448a19677f079100517b6629a872382d22ebd25e48aa/lxml-5.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e70ad4c9658beeff99856926fd3ee5fde8b519b92c693f856007177c36eb2e30", size = 4961795 }, - { url = "https://files.pythonhosted.org/packages/2d/57/4540efab2673de2904746b37ef7f74385329afd4643ed92abcc9ec6e00ca/lxml-5.3.2-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:507085365783abd7879fa0a6fa55eddf4bdd06591b17a2418403bb3aff8a267d", size = 4779791 }, - { url = "https://files.pythonhosted.org/packages/99/ad/6056edf6c9f4fa1d41e6fbdae52c733a4a257fd0d7feccfa26ae051bb46f/lxml-5.3.2-cp312-cp312-manylinux_2_28_ppc64le.whl", hash = "sha256:5bb304f67cbf5dfa07edad904732782cbf693286b9cd85af27059c5779131050", size = 5346807 }, - { url = "https://files.pythonhosted.org/packages/a1/fa/5be91fc91a18f3f705ea5533bc2210b25d738c6b615bf1c91e71a9b2f26b/lxml-5.3.2-cp312-cp312-manylinux_2_28_s390x.whl", hash = "sha256:3d84f5c093645c21c29a4e972b84cb7cf682f707f8706484a5a0c7ff13d7a988", size = 4909213 }, - { url = "https://files.pythonhosted.org/packages/f3/74/71bb96a3b5ae36b74e0402f4fa319df5559a8538577f8c57c50f1b57dc15/lxml-5.3.2-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:bdc13911db524bd63f37b0103af014b7161427ada41f1b0b3c9b5b5a9c1ca927", size = 4987694 }, - { url = "https://files.pythonhosted.org/packages/08/c2/3953a68b0861b2f97234b1838769269478ccf872d8ea7a26e911238220ad/lxml-5.3.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1ec944539543f66ebc060ae180d47e86aca0188bda9cbfadff47d86b0dc057dc", size = 4862865 }, - { url = "https://files.pythonhosted.org/packages/e0/9a/52e48f7cfd5a5e61f44a77e679880580dfb4f077af52d6ed5dd97e3356fe/lxml-5.3.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:59d437cc8a7f838282df5a199cf26f97ef08f1c0fbec6e84bd6f5cc2b7913f6e", size = 5423383 }, - { url = "https://files.pythonhosted.org/packages/17/67/42fe1d489e4dcc0b264bef361aef0b929fbb2b5378702471a3043bc6982c/lxml-5.3.2-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:0e275961adbd32e15672e14e0cc976a982075208224ce06d149c92cb43db5b93", size = 5286864 }, - { url = "https://files.pythonhosted.org/packages/29/e4/03b1d040ee3aaf2bd4e1c2061de2eae1178fe9a460d3efc1ea7ef66f6011/lxml-5.3.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:038aeb6937aa404480c2966b7f26f1440a14005cb0702078c173c028eca72c31", size = 5056819 }, - { url = "https://files.pythonhosted.org/packages/83/b3/e2ec8a6378e4d87da3af9de7c862bcea7ca624fc1a74b794180c82e30123/lxml-5.3.2-cp312-cp312-win32.whl", hash = "sha256:3c2c8d0fa3277147bff180e3590be67597e17d365ce94beb2efa3138a2131f71", size = 3486177 }, - { url = "https://files.pythonhosted.org/packages/d5/8a/6a08254b0bab2da9573735725caab8302a2a1c9b3818533b41568ca489be/lxml-5.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:77809fcd97dfda3f399102db1794f7280737b69830cd5c961ac87b3c5c05662d", size = 3817134 }, +version = "5.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/76/3d/14e82fc7c8fb1b7761f7e748fd47e2ec8276d137b6acfe5a4bb73853e08f/lxml-5.4.0.tar.gz", hash = "sha256:d12832e1dbea4be280b22fd0ea7c9b87f0d8fc51ba06e92dc62d52f804f78ebd", size = 3679479, upload-time = "2025-04-23T01:50:29.322Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/81/2d/67693cc8a605a12e5975380d7ff83020dcc759351b5a066e1cced04f797b/lxml-5.4.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:98a3912194c079ef37e716ed228ae0dcb960992100461b704aea4e93af6b0bb9", size = 8083240, upload-time = "2025-04-23T01:45:18.566Z" }, + { url = "https://files.pythonhosted.org/packages/73/53/b5a05ab300a808b72e848efd152fe9c022c0181b0a70b8bca1199f1bed26/lxml-5.4.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0ea0252b51d296a75f6118ed0d8696888e7403408ad42345d7dfd0d1e93309a7", size = 4387685, upload-time = "2025-04-23T01:45:21.387Z" }, + { url = "https://files.pythonhosted.org/packages/d8/cb/1a3879c5f512bdcd32995c301886fe082b2edd83c87d41b6d42d89b4ea4d/lxml-5.4.0-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b92b69441d1bd39f4940f9eadfa417a25862242ca2c396b406f9272ef09cdcaa", size = 4991164, upload-time = "2025-04-23T01:45:23.849Z" }, + { url = "https://files.pythonhosted.org/packages/f9/94/bbc66e42559f9d04857071e3b3d0c9abd88579367fd2588a4042f641f57e/lxml-5.4.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:20e16c08254b9b6466526bc1828d9370ee6c0d60a4b64836bc3ac2917d1e16df", size = 4746206, upload-time = "2025-04-23T01:45:26.361Z" }, + { url = "https://files.pythonhosted.org/packages/66/95/34b0679bee435da2d7cae895731700e519a8dfcab499c21662ebe671603e/lxml-5.4.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7605c1c32c3d6e8c990dd28a0970a3cbbf1429d5b92279e37fda05fb0c92190e", size = 5342144, upload-time = "2025-04-23T01:45:28.939Z" }, + { url = "https://files.pythonhosted.org/packages/e0/5d/abfcc6ab2fa0be72b2ba938abdae1f7cad4c632f8d552683ea295d55adfb/lxml-5.4.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ecf4c4b83f1ab3d5a7ace10bafcb6f11df6156857a3c418244cef41ca9fa3e44", size = 4825124, upload-time = "2025-04-23T01:45:31.361Z" }, + { url = "https://files.pythonhosted.org/packages/5a/78/6bd33186c8863b36e084f294fc0a5e5eefe77af95f0663ef33809cc1c8aa/lxml-5.4.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0cef4feae82709eed352cd7e97ae062ef6ae9c7b5dbe3663f104cd2c0e8d94ba", size = 4876520, upload-time = "2025-04-23T01:45:34.191Z" }, + { url = "https://files.pythonhosted.org/packages/3b/74/4d7ad4839bd0fc64e3d12da74fc9a193febb0fae0ba6ebd5149d4c23176a/lxml-5.4.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:df53330a3bff250f10472ce96a9af28628ff1f4efc51ccba351a8820bca2a8ba", size = 4765016, upload-time = "2025-04-23T01:45:36.7Z" }, + { url = "https://files.pythonhosted.org/packages/24/0d/0a98ed1f2471911dadfc541003ac6dd6879fc87b15e1143743ca20f3e973/lxml-5.4.0-cp311-cp311-manylinux_2_28_ppc64le.whl", hash = "sha256:aefe1a7cb852fa61150fcb21a8c8fcea7b58c4cb11fbe59c97a0a4b31cae3c8c", size = 5362884, upload-time = "2025-04-23T01:45:39.291Z" }, + { url = "https://files.pythonhosted.org/packages/48/de/d4f7e4c39740a6610f0f6959052b547478107967362e8424e1163ec37ae8/lxml-5.4.0-cp311-cp311-manylinux_2_28_s390x.whl", hash = "sha256:ef5a7178fcc73b7d8c07229e89f8eb45b2908a9238eb90dcfc46571ccf0383b8", size = 4902690, upload-time = "2025-04-23T01:45:42.386Z" }, + { url = "https://files.pythonhosted.org/packages/07/8c/61763abd242af84f355ca4ef1ee096d3c1b7514819564cce70fd18c22e9a/lxml-5.4.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:d2ed1b3cb9ff1c10e6e8b00941bb2e5bb568b307bfc6b17dffbbe8be5eecba86", size = 4944418, upload-time = "2025-04-23T01:45:46.051Z" }, + { url = "https://files.pythonhosted.org/packages/f9/c5/6d7e3b63e7e282619193961a570c0a4c8a57fe820f07ca3fe2f6bd86608a/lxml-5.4.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:72ac9762a9f8ce74c9eed4a4e74306f2f18613a6b71fa065495a67ac227b3056", size = 4827092, upload-time = "2025-04-23T01:45:48.943Z" }, + { url = "https://files.pythonhosted.org/packages/71/4a/e60a306df54680b103348545706a98a7514a42c8b4fbfdcaa608567bb065/lxml-5.4.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:f5cb182f6396706dc6cc1896dd02b1c889d644c081b0cdec38747573db88a7d7", size = 5418231, upload-time = "2025-04-23T01:45:51.481Z" }, + { url = "https://files.pythonhosted.org/packages/27/f2/9754aacd6016c930875854f08ac4b192a47fe19565f776a64004aa167521/lxml-5.4.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:3a3178b4873df8ef9457a4875703488eb1622632a9cee6d76464b60e90adbfcd", size = 5261798, upload-time = "2025-04-23T01:45:54.146Z" }, + { url = "https://files.pythonhosted.org/packages/38/a2/0c49ec6941428b1bd4f280650d7b11a0f91ace9db7de32eb7aa23bcb39ff/lxml-5.4.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:e094ec83694b59d263802ed03a8384594fcce477ce484b0cbcd0008a211ca751", size = 4988195, upload-time = "2025-04-23T01:45:56.685Z" }, + { url = "https://files.pythonhosted.org/packages/7a/75/87a3963a08eafc46a86c1131c6e28a4de103ba30b5ae903114177352a3d7/lxml-5.4.0-cp311-cp311-win32.whl", hash = "sha256:4329422de653cdb2b72afa39b0aa04252fca9071550044904b2e7036d9d97fe4", size = 3474243, upload-time = "2025-04-23T01:45:58.863Z" }, + { url = "https://files.pythonhosted.org/packages/fa/f9/1f0964c4f6c2be861c50db380c554fb8befbea98c6404744ce243a3c87ef/lxml-5.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:fd3be6481ef54b8cfd0e1e953323b7aa9d9789b94842d0e5b142ef4bb7999539", size = 3815197, upload-time = "2025-04-23T01:46:01.096Z" }, + { url = "https://files.pythonhosted.org/packages/f8/4c/d101ace719ca6a4ec043eb516fcfcb1b396a9fccc4fcd9ef593df34ba0d5/lxml-5.4.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:b5aff6f3e818e6bdbbb38e5967520f174b18f539c2b9de867b1e7fde6f8d95a4", size = 8127392, upload-time = "2025-04-23T01:46:04.09Z" }, + { url = "https://files.pythonhosted.org/packages/11/84/beddae0cec4dd9ddf46abf156f0af451c13019a0fa25d7445b655ba5ccb7/lxml-5.4.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:942a5d73f739ad7c452bf739a62a0f83e2578afd6b8e5406308731f4ce78b16d", size = 4415103, upload-time = "2025-04-23T01:46:07.227Z" }, + { url = "https://files.pythonhosted.org/packages/d0/25/d0d93a4e763f0462cccd2b8a665bf1e4343dd788c76dcfefa289d46a38a9/lxml-5.4.0-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:460508a4b07364d6abf53acaa0a90b6d370fafde5693ef37602566613a9b0779", size = 5024224, upload-time = "2025-04-23T01:46:10.237Z" }, + { url = "https://files.pythonhosted.org/packages/31/ce/1df18fb8f7946e7f3388af378b1f34fcf253b94b9feedb2cec5969da8012/lxml-5.4.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:529024ab3a505fed78fe3cc5ddc079464e709f6c892733e3f5842007cec8ac6e", size = 4769913, upload-time = "2025-04-23T01:46:12.757Z" }, + { url = "https://files.pythonhosted.org/packages/4e/62/f4a6c60ae7c40d43657f552f3045df05118636be1165b906d3423790447f/lxml-5.4.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7ca56ebc2c474e8f3d5761debfd9283b8b18c76c4fc0967b74aeafba1f5647f9", size = 5290441, upload-time = "2025-04-23T01:46:16.037Z" }, + { url = "https://files.pythonhosted.org/packages/9e/aa/04f00009e1e3a77838c7fc948f161b5d2d5de1136b2b81c712a263829ea4/lxml-5.4.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a81e1196f0a5b4167a8dafe3a66aa67c4addac1b22dc47947abd5d5c7a3f24b5", size = 4820165, upload-time = "2025-04-23T01:46:19.137Z" }, + { url = "https://files.pythonhosted.org/packages/c9/1f/e0b2f61fa2404bf0f1fdf1898377e5bd1b74cc9b2cf2c6ba8509b8f27990/lxml-5.4.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:00b8686694423ddae324cf614e1b9659c2edb754de617703c3d29ff568448df5", size = 4932580, upload-time = "2025-04-23T01:46:21.963Z" }, + { url = "https://files.pythonhosted.org/packages/24/a2/8263f351b4ffe0ed3e32ea7b7830f845c795349034f912f490180d88a877/lxml-5.4.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:c5681160758d3f6ac5b4fea370495c48aac0989d6a0f01bb9a72ad8ef5ab75c4", size = 4759493, upload-time = "2025-04-23T01:46:24.316Z" }, + { url = "https://files.pythonhosted.org/packages/05/00/41db052f279995c0e35c79d0f0fc9f8122d5b5e9630139c592a0b58c71b4/lxml-5.4.0-cp312-cp312-manylinux_2_28_ppc64le.whl", hash = "sha256:2dc191e60425ad70e75a68c9fd90ab284df64d9cd410ba8d2b641c0c45bc006e", size = 5324679, upload-time = "2025-04-23T01:46:27.097Z" }, + { url = "https://files.pythonhosted.org/packages/1d/be/ee99e6314cdef4587617d3b3b745f9356d9b7dd12a9663c5f3b5734b64ba/lxml-5.4.0-cp312-cp312-manylinux_2_28_s390x.whl", hash = "sha256:67f779374c6b9753ae0a0195a892a1c234ce8416e4448fe1e9f34746482070a7", size = 4890691, upload-time = "2025-04-23T01:46:30.009Z" }, + { url = "https://files.pythonhosted.org/packages/ad/36/239820114bf1d71f38f12208b9c58dec033cbcf80101cde006b9bde5cffd/lxml-5.4.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:79d5bfa9c1b455336f52343130b2067164040604e41f6dc4d8313867ed540079", size = 4955075, upload-time = "2025-04-23T01:46:32.33Z" }, + { url = "https://files.pythonhosted.org/packages/d4/e1/1b795cc0b174efc9e13dbd078a9ff79a58728a033142bc6d70a1ee8fc34d/lxml-5.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3d3c30ba1c9b48c68489dc1829a6eede9873f52edca1dda900066542528d6b20", size = 4838680, upload-time = "2025-04-23T01:46:34.852Z" }, + { url = "https://files.pythonhosted.org/packages/72/48/3c198455ca108cec5ae3662ae8acd7fd99476812fd712bb17f1b39a0b589/lxml-5.4.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:1af80c6316ae68aded77e91cd9d80648f7dd40406cef73df841aa3c36f6907c8", size = 5391253, upload-time = "2025-04-23T01:46:37.608Z" }, + { url = "https://files.pythonhosted.org/packages/d6/10/5bf51858971c51ec96cfc13e800a9951f3fd501686f4c18d7d84fe2d6352/lxml-5.4.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:4d885698f5019abe0de3d352caf9466d5de2baded00a06ef3f1216c1a58ae78f", size = 5261651, upload-time = "2025-04-23T01:46:40.183Z" }, + { url = "https://files.pythonhosted.org/packages/2b/11/06710dd809205377da380546f91d2ac94bad9ff735a72b64ec029f706c85/lxml-5.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:aea53d51859b6c64e7c51d522c03cc2c48b9b5d6172126854cc7f01aa11f52bc", size = 5024315, upload-time = "2025-04-23T01:46:43.333Z" }, + { url = "https://files.pythonhosted.org/packages/f5/b0/15b6217834b5e3a59ebf7f53125e08e318030e8cc0d7310355e6edac98ef/lxml-5.4.0-cp312-cp312-win32.whl", hash = "sha256:d90b729fd2732df28130c064aac9bb8aff14ba20baa4aee7bd0795ff1187545f", size = 3486149, upload-time = "2025-04-23T01:46:45.684Z" }, + { url = "https://files.pythonhosted.org/packages/91/1e/05ddcb57ad2f3069101611bd5f5084157d90861a2ef460bf42f45cced944/lxml-5.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:1dc4ca99e89c335a7ed47d38964abcb36c5910790f9bd106f2a8fa2ee0b909d2", size = 3817095, upload-time = "2025-04-23T01:46:48.521Z" }, ] [[package]] name = "lxml-stubs" version = "0.5.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/99/da/1a3a3e5d159b249fc2970d73437496b908de8e4716a089c69591b4ffa6fd/lxml-stubs-0.5.1.tar.gz", hash = "sha256:e0ec2aa1ce92d91278b719091ce4515c12adc1d564359dfaf81efa7d4feab79d", size = 14778 } +sdist = { url = "https://files.pythonhosted.org/packages/99/da/1a3a3e5d159b249fc2970d73437496b908de8e4716a089c69591b4ffa6fd/lxml-stubs-0.5.1.tar.gz", hash = "sha256:e0ec2aa1ce92d91278b719091ce4515c12adc1d564359dfaf81efa7d4feab79d", size = 14778, upload-time = "2024-01-10T09:37:46.521Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/1f/c9/e0f8e4e6e8a69e5959b06499582dca6349db6769cc7fdfb8a02a7c75a9ae/lxml_stubs-0.5.1-py3-none-any.whl", hash = "sha256:1f689e5dbc4b9247cb09ae820c7d34daeb1fdbd1db06123814b856dae7787272", size = 13584 }, + { url = "https://files.pythonhosted.org/packages/1f/c9/e0f8e4e6e8a69e5959b06499582dca6349db6769cc7fdfb8a02a7c75a9ae/lxml_stubs-0.5.1-py3-none-any.whl", hash = "sha256:1f689e5dbc4b9247cb09ae820c7d34daeb1fdbd1db06123814b856dae7787272", size = 13584, upload-time = "2024-01-10T09:37:44.931Z" }, ] [[package]] name = "lz4" version = "4.4.4" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/c6/5a/945f5086326d569f14c84ac6f7fcc3229f0b9b1e8cc536b951fd53dfb9e1/lz4-4.4.4.tar.gz", hash = "sha256:070fd0627ec4393011251a094e08ed9fdcc78cb4e7ab28f507638eee4e39abda", size = 171884 } +sdist = { url = "https://files.pythonhosted.org/packages/c6/5a/945f5086326d569f14c84ac6f7fcc3229f0b9b1e8cc536b951fd53dfb9e1/lz4-4.4.4.tar.gz", hash = "sha256:070fd0627ec4393011251a094e08ed9fdcc78cb4e7ab28f507638eee4e39abda", size = 171884, upload-time = "2025-04-01T22:55:58.62Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/28/e8/63843dc5ecb1529eb38e1761ceed04a0ad52a9ad8929ab8b7930ea2e4976/lz4-4.4.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ddfc7194cd206496c445e9e5b0c47f970ce982c725c87bd22de028884125b68f", size = 220898 }, - { url = "https://files.pythonhosted.org/packages/e4/94/c53de5f07c7dc11cf459aab2a1d754f5df5f693bfacbbe1e4914bfd02f1e/lz4-4.4.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:714f9298c86f8e7278f1c6af23e509044782fa8220eb0260f8f8f1632f820550", size = 189685 }, - { url = "https://files.pythonhosted.org/packages/fe/59/c22d516dd0352f2a3415d1f665ccef2f3e74ecec3ca6a8f061a38f97d50d/lz4-4.4.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a8474c91de47733856c6686df3c4aca33753741da7e757979369c2c0d32918ba", size = 1239225 }, - { url = "https://files.pythonhosted.org/packages/81/af/665685072e71f3f0e626221b7922867ec249cd8376aca761078c8f11f5da/lz4-4.4.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80dd27d7d680ea02c261c226acf1d41de2fd77af4fb2da62b278a9376e380de0", size = 1265881 }, - { url = "https://files.pythonhosted.org/packages/90/04/b4557ae381d3aa451388a29755cc410066f5e2f78c847f66f154f4520a68/lz4-4.4.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9b7d6dddfd01b49aedb940fdcaf32f41dc58c926ba35f4e31866aeec2f32f4f4", size = 1185593 }, - { url = "https://files.pythonhosted.org/packages/7b/e4/03636979f4e8bf92c557f998ca98ee4e6ef92e92eaf0ed6d3c7f2524e790/lz4-4.4.4-cp311-cp311-win32.whl", hash = "sha256:4134b9fd70ac41954c080b772816bb1afe0c8354ee993015a83430031d686a4c", size = 88259 }, - { url = "https://files.pythonhosted.org/packages/07/f0/9efe53b4945441a5d2790d455134843ad86739855b7e6199977bf6dc8898/lz4-4.4.4-cp311-cp311-win_amd64.whl", hash = "sha256:f5024d3ca2383470f7c4ef4d0ed8eabad0b22b23eeefde1c192cf1a38d5e9f78", size = 99916 }, - { url = "https://files.pythonhosted.org/packages/87/c8/1675527549ee174b9e1db089f7ddfbb962a97314657269b1e0344a5eaf56/lz4-4.4.4-cp311-cp311-win_arm64.whl", hash = "sha256:6ea715bb3357ea1665f77874cf8f55385ff112553db06f3742d3cdcec08633f7", size = 89741 }, - { url = "https://files.pythonhosted.org/packages/f7/2d/5523b4fabe11cd98f040f715728d1932eb7e696bfe94391872a823332b94/lz4-4.4.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:23ae267494fdd80f0d2a131beff890cf857f1b812ee72dbb96c3204aab725553", size = 220669 }, - { url = "https://files.pythonhosted.org/packages/91/06/1a5bbcacbfb48d8ee5b6eb3fca6aa84143a81d92946bdb5cd6b005f1863e/lz4-4.4.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fff9f3a1ed63d45cb6514bfb8293005dc4141341ce3500abdfeb76124c0b9b2e", size = 189661 }, - { url = "https://files.pythonhosted.org/packages/fa/08/39eb7ac907f73e11a69a11576a75a9e36406b3241c0ba41453a7eb842abb/lz4-4.4.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1ea7f07329f85a8eda4d8cf937b87f27f0ac392c6400f18bea2c667c8b7f8ecc", size = 1238775 }, - { url = "https://files.pythonhosted.org/packages/e9/26/05840fbd4233e8d23e88411a066ab19f1e9de332edddb8df2b6a95c7fddc/lz4-4.4.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8ccab8f7f7b82f9fa9fc3b0ba584d353bd5aa818d5821d77d5b9447faad2aaad", size = 1265143 }, - { url = "https://files.pythonhosted.org/packages/b7/5d/5f2db18c298a419932f3ab2023deb689863cf8fd7ed875b1c43492479af2/lz4-4.4.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e43e9d48b2daf80e486213128b0763deed35bbb7a59b66d1681e205e1702d735", size = 1185032 }, - { url = "https://files.pythonhosted.org/packages/c4/e6/736ab5f128694b0f6aac58343bcf37163437ac95997276cd0be3ea4c3342/lz4-4.4.4-cp312-cp312-win32.whl", hash = "sha256:33e01e18e4561b0381b2c33d58e77ceee850a5067f0ece945064cbaac2176962", size = 88284 }, - { url = "https://files.pythonhosted.org/packages/40/b8/243430cb62319175070e06e3a94c4c7bd186a812e474e22148ae1290d47d/lz4-4.4.4-cp312-cp312-win_amd64.whl", hash = "sha256:d21d1a2892a2dcc193163dd13eaadabb2c1b803807a5117d8f8588b22eaf9f12", size = 99918 }, - { url = "https://files.pythonhosted.org/packages/6c/e1/0686c91738f3e6c2e1a243e0fdd4371667c4d2e5009b0a3605806c2aa020/lz4-4.4.4-cp312-cp312-win_arm64.whl", hash = "sha256:2f4f2965c98ab254feddf6b5072854a6935adab7bc81412ec4fe238f07b85f62", size = 89736 }, + { url = "https://files.pythonhosted.org/packages/28/e8/63843dc5ecb1529eb38e1761ceed04a0ad52a9ad8929ab8b7930ea2e4976/lz4-4.4.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ddfc7194cd206496c445e9e5b0c47f970ce982c725c87bd22de028884125b68f", size = 220898, upload-time = "2025-04-01T22:55:23.085Z" }, + { url = "https://files.pythonhosted.org/packages/e4/94/c53de5f07c7dc11cf459aab2a1d754f5df5f693bfacbbe1e4914bfd02f1e/lz4-4.4.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:714f9298c86f8e7278f1c6af23e509044782fa8220eb0260f8f8f1632f820550", size = 189685, upload-time = "2025-04-01T22:55:24.413Z" }, + { url = "https://files.pythonhosted.org/packages/fe/59/c22d516dd0352f2a3415d1f665ccef2f3e74ecec3ca6a8f061a38f97d50d/lz4-4.4.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a8474c91de47733856c6686df3c4aca33753741da7e757979369c2c0d32918ba", size = 1239225, upload-time = "2025-04-01T22:55:25.737Z" }, + { url = "https://files.pythonhosted.org/packages/81/af/665685072e71f3f0e626221b7922867ec249cd8376aca761078c8f11f5da/lz4-4.4.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80dd27d7d680ea02c261c226acf1d41de2fd77af4fb2da62b278a9376e380de0", size = 1265881, upload-time = "2025-04-01T22:55:26.817Z" }, + { url = "https://files.pythonhosted.org/packages/90/04/b4557ae381d3aa451388a29755cc410066f5e2f78c847f66f154f4520a68/lz4-4.4.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9b7d6dddfd01b49aedb940fdcaf32f41dc58c926ba35f4e31866aeec2f32f4f4", size = 1185593, upload-time = "2025-04-01T22:55:27.896Z" }, + { url = "https://files.pythonhosted.org/packages/7b/e4/03636979f4e8bf92c557f998ca98ee4e6ef92e92eaf0ed6d3c7f2524e790/lz4-4.4.4-cp311-cp311-win32.whl", hash = "sha256:4134b9fd70ac41954c080b772816bb1afe0c8354ee993015a83430031d686a4c", size = 88259, upload-time = "2025-04-01T22:55:29.03Z" }, + { url = "https://files.pythonhosted.org/packages/07/f0/9efe53b4945441a5d2790d455134843ad86739855b7e6199977bf6dc8898/lz4-4.4.4-cp311-cp311-win_amd64.whl", hash = "sha256:f5024d3ca2383470f7c4ef4d0ed8eabad0b22b23eeefde1c192cf1a38d5e9f78", size = 99916, upload-time = "2025-04-01T22:55:29.933Z" }, + { url = "https://files.pythonhosted.org/packages/87/c8/1675527549ee174b9e1db089f7ddfbb962a97314657269b1e0344a5eaf56/lz4-4.4.4-cp311-cp311-win_arm64.whl", hash = "sha256:6ea715bb3357ea1665f77874cf8f55385ff112553db06f3742d3cdcec08633f7", size = 89741, upload-time = "2025-04-01T22:55:31.184Z" }, + { url = "https://files.pythonhosted.org/packages/f7/2d/5523b4fabe11cd98f040f715728d1932eb7e696bfe94391872a823332b94/lz4-4.4.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:23ae267494fdd80f0d2a131beff890cf857f1b812ee72dbb96c3204aab725553", size = 220669, upload-time = "2025-04-01T22:55:32.032Z" }, + { url = "https://files.pythonhosted.org/packages/91/06/1a5bbcacbfb48d8ee5b6eb3fca6aa84143a81d92946bdb5cd6b005f1863e/lz4-4.4.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fff9f3a1ed63d45cb6514bfb8293005dc4141341ce3500abdfeb76124c0b9b2e", size = 189661, upload-time = "2025-04-01T22:55:33.413Z" }, + { url = "https://files.pythonhosted.org/packages/fa/08/39eb7ac907f73e11a69a11576a75a9e36406b3241c0ba41453a7eb842abb/lz4-4.4.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1ea7f07329f85a8eda4d8cf937b87f27f0ac392c6400f18bea2c667c8b7f8ecc", size = 1238775, upload-time = "2025-04-01T22:55:34.835Z" }, + { url = "https://files.pythonhosted.org/packages/e9/26/05840fbd4233e8d23e88411a066ab19f1e9de332edddb8df2b6a95c7fddc/lz4-4.4.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8ccab8f7f7b82f9fa9fc3b0ba584d353bd5aa818d5821d77d5b9447faad2aaad", size = 1265143, upload-time = "2025-04-01T22:55:35.933Z" }, + { url = "https://files.pythonhosted.org/packages/b7/5d/5f2db18c298a419932f3ab2023deb689863cf8fd7ed875b1c43492479af2/lz4-4.4.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e43e9d48b2daf80e486213128b0763deed35bbb7a59b66d1681e205e1702d735", size = 1185032, upload-time = "2025-04-01T22:55:37.454Z" }, + { url = "https://files.pythonhosted.org/packages/c4/e6/736ab5f128694b0f6aac58343bcf37163437ac95997276cd0be3ea4c3342/lz4-4.4.4-cp312-cp312-win32.whl", hash = "sha256:33e01e18e4561b0381b2c33d58e77ceee850a5067f0ece945064cbaac2176962", size = 88284, upload-time = "2025-04-01T22:55:38.536Z" }, + { url = "https://files.pythonhosted.org/packages/40/b8/243430cb62319175070e06e3a94c4c7bd186a812e474e22148ae1290d47d/lz4-4.4.4-cp312-cp312-win_amd64.whl", hash = "sha256:d21d1a2892a2dcc193163dd13eaadabb2c1b803807a5117d8f8588b22eaf9f12", size = 99918, upload-time = "2025-04-01T22:55:39.628Z" }, + { url = "https://files.pythonhosted.org/packages/6c/e1/0686c91738f3e6c2e1a243e0fdd4371667c4d2e5009b0a3605806c2aa020/lz4-4.4.4-cp312-cp312-win_arm64.whl", hash = "sha256:2f4f2965c98ab254feddf6b5072854a6935adab7bc81412ec4fe238f07b85f62", size = 89736, upload-time = "2025-04-01T22:55:40.5Z" }, ] [[package]] @@ -2823,28 +2963,28 @@ dependencies = [ { name = "urllib3" }, ] wheels = [ - { url = "https://files.pythonhosted.org/packages/5f/bc/cb60d02c00996839bbd87444a97d0ba5ac271b1a324001562afb8f685251/mailchimp_transactional-1.0.56-py3-none-any.whl", hash = "sha256:a76ea88b90a2d47d8b5134586aabbd3a96c459f6066d8886748ab59e50de36eb", size = 31660 }, + { url = "https://files.pythonhosted.org/packages/5f/bc/cb60d02c00996839bbd87444a97d0ba5ac271b1a324001562afb8f685251/mailchimp_transactional-1.0.56-py3-none-any.whl", hash = "sha256:a76ea88b90a2d47d8b5134586aabbd3a96c459f6066d8886748ab59e50de36eb", size = 31660, upload-time = "2024-02-01T18:39:19.717Z" }, ] [[package]] name = "mako" -version = "1.3.9" +version = "1.3.10" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "markupsafe" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/62/4f/ddb1965901bc388958db9f0c991255b2c469349a741ae8c9cd8a562d70a6/mako-1.3.9.tar.gz", hash = "sha256:b5d65ff3462870feec922dbccf38f6efb44e5714d7b593a656be86663d8600ac", size = 392195 } +sdist = { url = "https://files.pythonhosted.org/packages/9e/38/bd5b78a920a64d708fe6bc8e0a2c075e1389d53bef8413725c63ba041535/mako-1.3.10.tar.gz", hash = "sha256:99579a6f39583fa7e5630a28c3c1f440e4e97a414b80372649c0ce338da2ea28", size = 392474, upload-time = "2025-04-10T12:44:31.16Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/cd/83/de0a49e7de540513f53ab5d2e105321dedeb08a8f5850f0208decf4390ec/Mako-1.3.9-py3-none-any.whl", hash = "sha256:95920acccb578427a9aa38e37a186b1e43156c87260d7ba18ca63aa4c7cbd3a1", size = 78456 }, + { url = "https://files.pythonhosted.org/packages/87/fb/99f81ac72ae23375f22b7afdb7642aba97c00a713c217124420147681a2f/mako-1.3.10-py3-none-any.whl", hash = "sha256:baef24a52fc4fc514a0887ac600f9f1cff3d82c61d4d700a1fa84d597b88db59", size = 78509, upload-time = "2025-04-10T12:50:53.297Z" }, ] [[package]] name = "markdown" version = "3.5.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/11/28/c5441a6642681d92de56063fa7984df56f783d3f1eba518dc3e7a253b606/Markdown-3.5.2.tar.gz", hash = "sha256:e1ac7b3dc550ee80e602e71c1d168002f062e49f1b11e26a36264dafd4df2ef8", size = 349398 } +sdist = { url = "https://files.pythonhosted.org/packages/11/28/c5441a6642681d92de56063fa7984df56f783d3f1eba518dc3e7a253b606/Markdown-3.5.2.tar.gz", hash = "sha256:e1ac7b3dc550ee80e602e71c1d168002f062e49f1b11e26a36264dafd4df2ef8", size = 349398, upload-time = "2024-01-10T15:19:38.261Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/42/f4/f0031854de10a0bc7821ef9fca0b92ca0d7aa6fbfbf504c5473ba825e49c/Markdown-3.5.2-py3-none-any.whl", hash = "sha256:d43323865d89fc0cb9b20c75fc8ad313af307cc087e84b657d9eec768eddeadd", size = 103870 }, + { url = "https://files.pythonhosted.org/packages/42/f4/f0031854de10a0bc7821ef9fca0b92ca0d7aa6fbfbf504c5473ba825e49c/Markdown-3.5.2-py3-none-any.whl", hash = "sha256:d43323865d89fc0cb9b20c75fc8ad313af307cc087e84b657d9eec768eddeadd", size = 103870, upload-time = "2024-01-10T15:19:36.071Z" }, ] [[package]] @@ -2854,37 +2994,37 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "mdurl" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/38/71/3b932df36c1a044d397a1f92d1cf91ee0a503d91e470cbd670aa66b07ed0/markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb", size = 74596 } +sdist = { url = "https://files.pythonhosted.org/packages/38/71/3b932df36c1a044d397a1f92d1cf91ee0a503d91e470cbd670aa66b07ed0/markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb", size = 74596, upload-time = "2023-06-03T06:41:14.443Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/42/d7/1ec15b46af6af88f19b8e5ffea08fa375d433c998b8a7639e76935c14f1f/markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1", size = 87528 }, + { url = "https://files.pythonhosted.org/packages/42/d7/1ec15b46af6af88f19b8e5ffea08fa375d433c998b8a7639e76935c14f1f/markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1", size = 87528, upload-time = "2023-06-03T06:41:11.019Z" }, ] [[package]] name = "markupsafe" version = "3.0.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b2/97/5d42485e71dfc078108a86d6de8fa46db44a1a9295e89c5d6d4a06e23a62/markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0", size = 20537 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/6b/28/bbf83e3f76936960b850435576dd5e67034e200469571be53f69174a2dfd/MarkupSafe-3.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9025b4018f3a1314059769c7bf15441064b2207cb3f065e6ea1e7359cb46db9d", size = 14353 }, - { url = "https://files.pythonhosted.org/packages/6c/30/316d194b093cde57d448a4c3209f22e3046c5bb2fb0820b118292b334be7/MarkupSafe-3.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:93335ca3812df2f366e80509ae119189886b0f3c2b81325d39efdb84a1e2ae93", size = 12392 }, - { url = "https://files.pythonhosted.org/packages/f2/96/9cdafba8445d3a53cae530aaf83c38ec64c4d5427d975c974084af5bc5d2/MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2cb8438c3cbb25e220c2ab33bb226559e7afb3baec11c4f218ffa7308603c832", size = 23984 }, - { url = "https://files.pythonhosted.org/packages/f1/a4/aefb044a2cd8d7334c8a47d3fb2c9f328ac48cb349468cc31c20b539305f/MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a123e330ef0853c6e822384873bef7507557d8e4a082961e1defa947aa59ba84", size = 23120 }, - { url = "https://files.pythonhosted.org/packages/8d/21/5e4851379f88f3fad1de30361db501300d4f07bcad047d3cb0449fc51f8c/MarkupSafe-3.0.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e084f686b92e5b83186b07e8a17fc09e38fff551f3602b249881fec658d3eca", size = 23032 }, - { url = "https://files.pythonhosted.org/packages/00/7b/e92c64e079b2d0d7ddf69899c98842f3f9a60a1ae72657c89ce2655c999d/MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d8213e09c917a951de9d09ecee036d5c7d36cb6cb7dbaece4c71a60d79fb9798", size = 24057 }, - { url = "https://files.pythonhosted.org/packages/f9/ac/46f960ca323037caa0a10662ef97d0a4728e890334fc156b9f9e52bcc4ca/MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5b02fb34468b6aaa40dfc198d813a641e3a63b98c2b05a16b9f80b7ec314185e", size = 23359 }, - { url = "https://files.pythonhosted.org/packages/69/84/83439e16197337b8b14b6a5b9c2105fff81d42c2a7c5b58ac7b62ee2c3b1/MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0bff5e0ae4ef2e1ae4fdf2dfd5b76c75e5c2fa4132d05fc1b0dabcd20c7e28c4", size = 23306 }, - { url = "https://files.pythonhosted.org/packages/9a/34/a15aa69f01e2181ed8d2b685c0d2f6655d5cca2c4db0ddea775e631918cd/MarkupSafe-3.0.2-cp311-cp311-win32.whl", hash = "sha256:6c89876f41da747c8d3677a2b540fb32ef5715f97b66eeb0c6b66f5e3ef6f59d", size = 15094 }, - { url = "https://files.pythonhosted.org/packages/da/b8/3a3bd761922d416f3dc5d00bfbed11f66b1ab89a0c2b6e887240a30b0f6b/MarkupSafe-3.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:70a87b411535ccad5ef2f1df5136506a10775d267e197e4cf531ced10537bd6b", size = 15521 }, - { url = "https://files.pythonhosted.org/packages/22/09/d1f21434c97fc42f09d290cbb6350d44eb12f09cc62c9476effdb33a18aa/MarkupSafe-3.0.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf", size = 14274 }, - { url = "https://files.pythonhosted.org/packages/6b/b0/18f76bba336fa5aecf79d45dcd6c806c280ec44538b3c13671d49099fdd0/MarkupSafe-3.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225", size = 12348 }, - { url = "https://files.pythonhosted.org/packages/e0/25/dd5c0f6ac1311e9b40f4af06c78efde0f3b5cbf02502f8ef9501294c425b/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028", size = 24149 }, - { url = "https://files.pythonhosted.org/packages/f3/f0/89e7aadfb3749d0f52234a0c8c7867877876e0a20b60e2188e9850794c17/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e17c96c14e19278594aa4841ec148115f9c7615a47382ecb6b82bd8fea3ab0c8", size = 23118 }, - { url = "https://files.pythonhosted.org/packages/d5/da/f2eeb64c723f5e3777bc081da884b414671982008c47dcc1873d81f625b6/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88416bd1e65dcea10bc7569faacb2c20ce071dd1f87539ca2ab364bf6231393c", size = 22993 }, - { url = "https://files.pythonhosted.org/packages/da/0e/1f32af846df486dce7c227fe0f2398dc7e2e51d4a370508281f3c1c5cddc/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2181e67807fc2fa785d0592dc2d6206c019b9502410671cc905d132a92866557", size = 24178 }, - { url = "https://files.pythonhosted.org/packages/c4/f6/bb3ca0532de8086cbff5f06d137064c8410d10779c4c127e0e47d17c0b71/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:52305740fe773d09cffb16f8ed0427942901f00adedac82ec8b67752f58a1b22", size = 23319 }, - { url = "https://files.pythonhosted.org/packages/a2/82/8be4c96ffee03c5b4a034e60a31294daf481e12c7c43ab8e34a1453ee48b/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ad10d3ded218f1039f11a75f8091880239651b52e9bb592ca27de44eed242a48", size = 23352 }, - { url = "https://files.pythonhosted.org/packages/51/ae/97827349d3fcffee7e184bdf7f41cd6b88d9919c80f0263ba7acd1bbcb18/MarkupSafe-3.0.2-cp312-cp312-win32.whl", hash = "sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30", size = 15097 }, - { url = "https://files.pythonhosted.org/packages/c1/80/a61f99dc3a936413c3ee4e1eecac96c0da5ed07ad56fd975f1a9da5bc630/MarkupSafe-3.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87", size = 15601 }, +sdist = { url = "https://files.pythonhosted.org/packages/b2/97/5d42485e71dfc078108a86d6de8fa46db44a1a9295e89c5d6d4a06e23a62/markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0", size = 20537, upload-time = "2024-10-18T15:21:54.129Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6b/28/bbf83e3f76936960b850435576dd5e67034e200469571be53f69174a2dfd/MarkupSafe-3.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9025b4018f3a1314059769c7bf15441064b2207cb3f065e6ea1e7359cb46db9d", size = 14353, upload-time = "2024-10-18T15:21:02.187Z" }, + { url = "https://files.pythonhosted.org/packages/6c/30/316d194b093cde57d448a4c3209f22e3046c5bb2fb0820b118292b334be7/MarkupSafe-3.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:93335ca3812df2f366e80509ae119189886b0f3c2b81325d39efdb84a1e2ae93", size = 12392, upload-time = "2024-10-18T15:21:02.941Z" }, + { url = "https://files.pythonhosted.org/packages/f2/96/9cdafba8445d3a53cae530aaf83c38ec64c4d5427d975c974084af5bc5d2/MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2cb8438c3cbb25e220c2ab33bb226559e7afb3baec11c4f218ffa7308603c832", size = 23984, upload-time = "2024-10-18T15:21:03.953Z" }, + { url = "https://files.pythonhosted.org/packages/f1/a4/aefb044a2cd8d7334c8a47d3fb2c9f328ac48cb349468cc31c20b539305f/MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a123e330ef0853c6e822384873bef7507557d8e4a082961e1defa947aa59ba84", size = 23120, upload-time = "2024-10-18T15:21:06.495Z" }, + { url = "https://files.pythonhosted.org/packages/8d/21/5e4851379f88f3fad1de30361db501300d4f07bcad047d3cb0449fc51f8c/MarkupSafe-3.0.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e084f686b92e5b83186b07e8a17fc09e38fff551f3602b249881fec658d3eca", size = 23032, upload-time = "2024-10-18T15:21:07.295Z" }, + { url = "https://files.pythonhosted.org/packages/00/7b/e92c64e079b2d0d7ddf69899c98842f3f9a60a1ae72657c89ce2655c999d/MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d8213e09c917a951de9d09ecee036d5c7d36cb6cb7dbaece4c71a60d79fb9798", size = 24057, upload-time = "2024-10-18T15:21:08.073Z" }, + { url = "https://files.pythonhosted.org/packages/f9/ac/46f960ca323037caa0a10662ef97d0a4728e890334fc156b9f9e52bcc4ca/MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5b02fb34468b6aaa40dfc198d813a641e3a63b98c2b05a16b9f80b7ec314185e", size = 23359, upload-time = "2024-10-18T15:21:09.318Z" }, + { url = "https://files.pythonhosted.org/packages/69/84/83439e16197337b8b14b6a5b9c2105fff81d42c2a7c5b58ac7b62ee2c3b1/MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0bff5e0ae4ef2e1ae4fdf2dfd5b76c75e5c2fa4132d05fc1b0dabcd20c7e28c4", size = 23306, upload-time = "2024-10-18T15:21:10.185Z" }, + { url = "https://files.pythonhosted.org/packages/9a/34/a15aa69f01e2181ed8d2b685c0d2f6655d5cca2c4db0ddea775e631918cd/MarkupSafe-3.0.2-cp311-cp311-win32.whl", hash = "sha256:6c89876f41da747c8d3677a2b540fb32ef5715f97b66eeb0c6b66f5e3ef6f59d", size = 15094, upload-time = "2024-10-18T15:21:11.005Z" }, + { url = "https://files.pythonhosted.org/packages/da/b8/3a3bd761922d416f3dc5d00bfbed11f66b1ab89a0c2b6e887240a30b0f6b/MarkupSafe-3.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:70a87b411535ccad5ef2f1df5136506a10775d267e197e4cf531ced10537bd6b", size = 15521, upload-time = "2024-10-18T15:21:12.911Z" }, + { url = "https://files.pythonhosted.org/packages/22/09/d1f21434c97fc42f09d290cbb6350d44eb12f09cc62c9476effdb33a18aa/MarkupSafe-3.0.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf", size = 14274, upload-time = "2024-10-18T15:21:13.777Z" }, + { url = "https://files.pythonhosted.org/packages/6b/b0/18f76bba336fa5aecf79d45dcd6c806c280ec44538b3c13671d49099fdd0/MarkupSafe-3.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225", size = 12348, upload-time = "2024-10-18T15:21:14.822Z" }, + { url = "https://files.pythonhosted.org/packages/e0/25/dd5c0f6ac1311e9b40f4af06c78efde0f3b5cbf02502f8ef9501294c425b/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028", size = 24149, upload-time = "2024-10-18T15:21:15.642Z" }, + { url = "https://files.pythonhosted.org/packages/f3/f0/89e7aadfb3749d0f52234a0c8c7867877876e0a20b60e2188e9850794c17/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e17c96c14e19278594aa4841ec148115f9c7615a47382ecb6b82bd8fea3ab0c8", size = 23118, upload-time = "2024-10-18T15:21:17.133Z" }, + { url = "https://files.pythonhosted.org/packages/d5/da/f2eeb64c723f5e3777bc081da884b414671982008c47dcc1873d81f625b6/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88416bd1e65dcea10bc7569faacb2c20ce071dd1f87539ca2ab364bf6231393c", size = 22993, upload-time = "2024-10-18T15:21:18.064Z" }, + { url = "https://files.pythonhosted.org/packages/da/0e/1f32af846df486dce7c227fe0f2398dc7e2e51d4a370508281f3c1c5cddc/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2181e67807fc2fa785d0592dc2d6206c019b9502410671cc905d132a92866557", size = 24178, upload-time = "2024-10-18T15:21:18.859Z" }, + { url = "https://files.pythonhosted.org/packages/c4/f6/bb3ca0532de8086cbff5f06d137064c8410d10779c4c127e0e47d17c0b71/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:52305740fe773d09cffb16f8ed0427942901f00adedac82ec8b67752f58a1b22", size = 23319, upload-time = "2024-10-18T15:21:19.671Z" }, + { url = "https://files.pythonhosted.org/packages/a2/82/8be4c96ffee03c5b4a034e60a31294daf481e12c7c43ab8e34a1453ee48b/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ad10d3ded218f1039f11a75f8091880239651b52e9bb592ca27de44eed242a48", size = 23352, upload-time = "2024-10-18T15:21:20.971Z" }, + { url = "https://files.pythonhosted.org/packages/51/ae/97827349d3fcffee7e184bdf7f41cd6b88d9919c80f0263ba7acd1bbcb18/MarkupSafe-3.0.2-cp312-cp312-win32.whl", hash = "sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30", size = 15097, upload-time = "2024-10-18T15:21:22.646Z" }, + { url = "https://files.pythonhosted.org/packages/c1/80/a61f99dc3a936413c3ee4e1eecac96c0da5ed07ad56fd975f1a9da5bc630/MarkupSafe-3.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87", size = 15601, upload-time = "2024-10-18T15:21:23.499Z" }, ] [[package]] @@ -2894,18 +3034,18 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "packaging" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ab/5e/5e53d26b42ab75491cda89b871dab9e97c840bf12c63ec58a1919710cd06/marshmallow-3.26.1.tar.gz", hash = "sha256:e6d8affb6cb61d39d26402096dc0aee12d5a26d490a121f118d2e81dc0719dc6", size = 221825 } +sdist = { url = "https://files.pythonhosted.org/packages/ab/5e/5e53d26b42ab75491cda89b871dab9e97c840bf12c63ec58a1919710cd06/marshmallow-3.26.1.tar.gz", hash = "sha256:e6d8affb6cb61d39d26402096dc0aee12d5a26d490a121f118d2e81dc0719dc6", size = 221825, upload-time = "2025-02-03T15:32:25.093Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/34/75/51952c7b2d3873b44a0028b1bd26a25078c18f92f256608e8d1dc61b39fd/marshmallow-3.26.1-py3-none-any.whl", hash = "sha256:3350409f20a70a7e4e11a27661187b77cdcaeb20abca41c1454fe33636bea09c", size = 50878 }, + { url = "https://files.pythonhosted.org/packages/34/75/51952c7b2d3873b44a0028b1bd26a25078c18f92f256608e8d1dc61b39fd/marshmallow-3.26.1-py3-none-any.whl", hash = "sha256:3350409f20a70a7e4e11a27661187b77cdcaeb20abca41c1454fe33636bea09c", size = 50878, upload-time = "2025-02-03T15:32:22.295Z" }, ] [[package]] name = "mdurl" version = "0.1.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729 } +sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729, upload-time = "2022-08-14T12:40:10.846Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979 }, + { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979, upload-time = "2022-08-14T12:40:09.779Z" }, ] [[package]] @@ -2916,82 +3056,73 @@ dependencies = [ { name = "tqdm" }, ] wheels = [ - { url = "https://files.pythonhosted.org/packages/64/3a/110e46db650ced604f97307e48e353726cfa6d26b1bf72acb81bbf07ecbd/milvus_lite-2.4.12-py3-none-macosx_10_9_x86_64.whl", hash = "sha256:e8d4f7cdd5f731efd6faeee3715d280fd91a5f9b4d89312664d56401f65b1473", size = 19843871 }, - { url = "https://files.pythonhosted.org/packages/a5/a7/11c21f2d6f3299ad07af8142b007e4297ff12d4bdc53e1e1ba48f661954b/milvus_lite-2.4.12-py3-none-macosx_11_0_arm64.whl", hash = "sha256:20087663e7b4385050b7ad08f1f03404426d4c87b1ff91d5a8723eee7fd49e88", size = 17411635 }, - { url = "https://files.pythonhosted.org/packages/a8/cc/b6f465e984439adf24da0a8ff3035d5c9ece30b6ff19f9a53f73f9ef901a/milvus_lite-2.4.12-py3-none-manylinux2014_aarch64.whl", hash = "sha256:a0f3a5ddbfd19f4a6b842b2fd3445693c796cde272b701a1646a94c1ac45d3d7", size = 35693118 }, - { url = "https://files.pythonhosted.org/packages/44/43/b3f6e9defd1f3927b972beac7abe3d5b4a3bdb287e3bad69618e2e76cf0a/milvus_lite-2.4.12-py3-none-manylinux2014_x86_64.whl", hash = "sha256:334037ebbab60243b5d8b43d54ca2f835d81d48c3cda0c6a462605e588deb05d", size = 45182549 }, + { url = "https://files.pythonhosted.org/packages/64/3a/110e46db650ced604f97307e48e353726cfa6d26b1bf72acb81bbf07ecbd/milvus_lite-2.4.12-py3-none-macosx_10_9_x86_64.whl", hash = "sha256:e8d4f7cdd5f731efd6faeee3715d280fd91a5f9b4d89312664d56401f65b1473", size = 19843871, upload-time = "2025-03-21T06:20:26.141Z" }, + { url = "https://files.pythonhosted.org/packages/a5/a7/11c21f2d6f3299ad07af8142b007e4297ff12d4bdc53e1e1ba48f661954b/milvus_lite-2.4.12-py3-none-macosx_11_0_arm64.whl", hash = "sha256:20087663e7b4385050b7ad08f1f03404426d4c87b1ff91d5a8723eee7fd49e88", size = 17411635, upload-time = "2025-03-21T06:20:43.548Z" }, + { url = "https://files.pythonhosted.org/packages/a8/cc/b6f465e984439adf24da0a8ff3035d5c9ece30b6ff19f9a53f73f9ef901a/milvus_lite-2.4.12-py3-none-manylinux2014_aarch64.whl", hash = "sha256:a0f3a5ddbfd19f4a6b842b2fd3445693c796cde272b701a1646a94c1ac45d3d7", size = 35693118, upload-time = "2025-03-21T06:21:14.921Z" }, + { url = "https://files.pythonhosted.org/packages/44/43/b3f6e9defd1f3927b972beac7abe3d5b4a3bdb287e3bad69618e2e76cf0a/milvus_lite-2.4.12-py3-none-manylinux2014_x86_64.whl", hash = "sha256:334037ebbab60243b5d8b43d54ca2f835d81d48c3cda0c6a462605e588deb05d", size = 45182549, upload-time = "2025-03-21T06:21:45.425Z" }, ] [[package]] name = "mmh3" version = "5.1.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/47/1b/1fc6888c74cbd8abad1292dde2ddfcf8fc059e114c97dd6bf16d12f36293/mmh3-5.1.0.tar.gz", hash = "sha256:136e1e670500f177f49ec106a4ebf0adf20d18d96990cc36ea492c651d2b406c", size = 33728 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/56/09/fda7af7fe65928262098382e3bf55950cfbf67d30bf9e47731bf862161e9/mmh3-5.1.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0b529dcda3f951ff363a51d5866bc6d63cf57f1e73e8961f864ae5010647079d", size = 56098 }, - { url = "https://files.pythonhosted.org/packages/0c/ab/84c7bc3f366d6f3bd8b5d9325a10c367685bc17c26dac4c068e2001a4671/mmh3-5.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4db1079b3ace965e562cdfc95847312f9273eb2ad3ebea983435c8423e06acd7", size = 40513 }, - { url = "https://files.pythonhosted.org/packages/4f/21/25ea58ca4a652bdc83d1528bec31745cce35802381fb4fe3c097905462d2/mmh3-5.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:22d31e3a0ff89b8eb3b826d6fc8e19532998b2aa6b9143698043a1268da413e1", size = 40112 }, - { url = "https://files.pythonhosted.org/packages/bd/78/4f12f16ae074ddda6f06745254fdb50f8cf3c85b0bbf7eaca58bed84bf58/mmh3-5.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2139bfbd354cd6cb0afed51c4b504f29bcd687a3b1460b7e89498329cc28a894", size = 102632 }, - { url = "https://files.pythonhosted.org/packages/48/11/8f09dc999cf2a09b6138d8d7fc734efb7b7bfdd9adb9383380941caadff0/mmh3-5.1.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8c8105c6a435bc2cd6ea2ef59558ab1a2976fd4a4437026f562856d08996673a", size = 108884 }, - { url = "https://files.pythonhosted.org/packages/bd/91/e59a66538a3364176f6c3f7620eee0ab195bfe26f89a95cbcc7a1fb04b28/mmh3-5.1.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:57730067174a7f36fcd6ce012fe359bd5510fdaa5fe067bc94ed03e65dafb769", size = 106835 }, - { url = "https://files.pythonhosted.org/packages/25/14/b85836e21ab90e5cddb85fe79c494ebd8f81d96a87a664c488cc9277668b/mmh3-5.1.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bde80eb196d7fdc765a318604ded74a4378f02c5b46c17aa48a27d742edaded2", size = 93688 }, - { url = "https://files.pythonhosted.org/packages/ac/aa/8bc964067df9262740c95e4cde2d19f149f2224f426654e14199a9e47df6/mmh3-5.1.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e9c8eddcb441abddeb419c16c56fd74b3e2df9e57f7aa2903221996718435c7a", size = 101569 }, - { url = "https://files.pythonhosted.org/packages/70/b6/1fb163cbf919046a64717466c00edabebece3f95c013853fec76dbf2df92/mmh3-5.1.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:99e07e4acafbccc7a28c076a847fb060ffc1406036bc2005acb1b2af620e53c3", size = 98483 }, - { url = "https://files.pythonhosted.org/packages/70/49/ba64c050dd646060f835f1db6b2cd60a6485f3b0ea04976e7a29ace7312e/mmh3-5.1.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:9e25ba5b530e9a7d65f41a08d48f4b3fedc1e89c26486361166a5544aa4cad33", size = 96496 }, - { url = "https://files.pythonhosted.org/packages/9e/07/f2751d6a0b535bb865e1066e9c6b80852571ef8d61bce7eb44c18720fbfc/mmh3-5.1.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:bb9bf7475b4d99156ce2f0cf277c061a17560c8c10199c910a680869a278ddc7", size = 105109 }, - { url = "https://files.pythonhosted.org/packages/b7/02/30360a5a66f7abba44596d747cc1e6fb53136b168eaa335f63454ab7bb79/mmh3-5.1.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:2a1b0878dd281ea3003368ab53ff6f568e175f1b39f281df1da319e58a19c23a", size = 98231 }, - { url = "https://files.pythonhosted.org/packages/8c/60/8526b0c750ff4d7ae1266e68b795f14b97758a1d9fcc19f6ecabf9c55656/mmh3-5.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:25f565093ac8b8aefe0f61f8f95c9a9d11dd69e6a9e9832ff0d293511bc36258", size = 97548 }, - { url = "https://files.pythonhosted.org/packages/6d/4c/26e1222aca65769280d5427a1ce5875ef4213449718c8f03958d0bf91070/mmh3-5.1.0-cp311-cp311-win32.whl", hash = "sha256:1e3554d8792387eac73c99c6eaea0b3f884e7130eb67986e11c403e4f9b6d372", size = 40810 }, - { url = "https://files.pythonhosted.org/packages/98/d5/424ba95062d1212ea615dc8debc8d57983f2242d5e6b82e458b89a117a1e/mmh3-5.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:8ad777a48197882492af50bf3098085424993ce850bdda406a358b6ab74be759", size = 41476 }, - { url = "https://files.pythonhosted.org/packages/bd/08/0315ccaf087ba55bb19a6dd3b1e8acd491e74ce7f5f9c4aaa06a90d66441/mmh3-5.1.0-cp311-cp311-win_arm64.whl", hash = "sha256:f29dc4efd99bdd29fe85ed6c81915b17b2ef2cf853abf7213a48ac6fb3eaabe1", size = 38880 }, - { url = "https://files.pythonhosted.org/packages/f4/47/e5f452bdf16028bfd2edb4e2e35d0441e4a4740f30e68ccd4cfd2fb2c57e/mmh3-5.1.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:45712987367cb9235026e3cbf4334670522a97751abfd00b5bc8bfa022c3311d", size = 56152 }, - { url = "https://files.pythonhosted.org/packages/60/38/2132d537dc7a7fdd8d2e98df90186c7fcdbd3f14f95502a24ba443c92245/mmh3-5.1.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b1020735eb35086ab24affbea59bb9082f7f6a0ad517cb89f0fc14f16cea4dae", size = 40564 }, - { url = "https://files.pythonhosted.org/packages/c0/2a/c52cf000581bfb8d94794f58865658e7accf2fa2e90789269d4ae9560b16/mmh3-5.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:babf2a78ce5513d120c358722a2e3aa7762d6071cd10cede026f8b32452be322", size = 40104 }, - { url = "https://files.pythonhosted.org/packages/83/33/30d163ce538c54fc98258db5621447e3ab208d133cece5d2577cf913e708/mmh3-5.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d4f47f58cd5cbef968c84a7c1ddc192fef0a36b48b0b8a3cb67354531aa33b00", size = 102634 }, - { url = "https://files.pythonhosted.org/packages/94/5c/5a18acb6ecc6852be2d215c3d811aa61d7e425ab6596be940877355d7f3e/mmh3-5.1.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2044a601c113c981f2c1e14fa33adc9b826c9017034fe193e9eb49a6882dbb06", size = 108888 }, - { url = "https://files.pythonhosted.org/packages/1f/f6/11c556324c64a92aa12f28e221a727b6e082e426dc502e81f77056f6fc98/mmh3-5.1.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c94d999c9f2eb2da44d7c2826d3fbffdbbbbcde8488d353fee7c848ecc42b968", size = 106968 }, - { url = "https://files.pythonhosted.org/packages/5d/61/ca0c196a685aba7808a5c00246f17b988a9c4f55c594ee0a02c273e404f3/mmh3-5.1.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a015dcb24fa0c7a78f88e9419ac74f5001c1ed6a92e70fd1803f74afb26a4c83", size = 93771 }, - { url = "https://files.pythonhosted.org/packages/b4/55/0927c33528710085ee77b808d85bbbafdb91a1db7c8eaa89cac16d6c513e/mmh3-5.1.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:457da019c491a2d20e2022c7d4ce723675e4c081d9efc3b4d8b9f28a5ea789bd", size = 101726 }, - { url = "https://files.pythonhosted.org/packages/49/39/a92c60329fa470f41c18614a93c6cd88821412a12ee78c71c3f77e1cfc2d/mmh3-5.1.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:71408579a570193a4ac9c77344d68ddefa440b00468a0b566dcc2ba282a9c559", size = 98523 }, - { url = "https://files.pythonhosted.org/packages/81/90/26adb15345af8d9cf433ae1b6adcf12e0a4cad1e692de4fa9f8e8536c5ae/mmh3-5.1.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:8b3a04bc214a6e16c81f02f855e285c6df274a2084787eeafaa45f2fbdef1b63", size = 96628 }, - { url = "https://files.pythonhosted.org/packages/8a/4d/340d1e340df972a13fd4ec84c787367f425371720a1044220869c82364e9/mmh3-5.1.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:832dae26a35514f6d3c1e267fa48e8de3c7b978afdafa0529c808ad72e13ada3", size = 105190 }, - { url = "https://files.pythonhosted.org/packages/d3/7c/65047d1cccd3782d809936db446430fc7758bda9def5b0979887e08302a2/mmh3-5.1.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:bf658a61fc92ef8a48945ebb1076ef4ad74269e353fffcb642dfa0890b13673b", size = 98439 }, - { url = "https://files.pythonhosted.org/packages/72/d2/3c259d43097c30f062050f7e861075099404e8886b5d4dd3cebf180d6e02/mmh3-5.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3313577453582b03383731b66447cdcdd28a68f78df28f10d275d7d19010c1df", size = 97780 }, - { url = "https://files.pythonhosted.org/packages/29/29/831ea8d4abe96cdb3e28b79eab49cac7f04f9c6b6e36bfc686197ddba09d/mmh3-5.1.0-cp312-cp312-win32.whl", hash = "sha256:1d6508504c531ab86c4424b5a5ff07c1132d063863339cf92f6657ff7a580f76", size = 40835 }, - { url = "https://files.pythonhosted.org/packages/12/dd/7cbc30153b73f08eeac43804c1dbc770538a01979b4094edbe1a4b8eb551/mmh3-5.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:aa75981fcdf3f21759d94f2c81b6a6e04a49dfbcdad88b152ba49b8e20544776", size = 41509 }, - { url = "https://files.pythonhosted.org/packages/80/9d/627375bab4c90dd066093fc2c9a26b86f87e26d980dbf71667b44cbee3eb/mmh3-5.1.0-cp312-cp312-win_arm64.whl", hash = "sha256:a4c1a76808dfea47f7407a0b07aaff9087447ef6280716fd0783409b3088bb3c", size = 38888 }, -] - -[[package]] -name = "monotonic" -version = "1.6" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ea/ca/8e91948b782ddfbd194f323e7e7d9ba12e5877addf04fb2bf8fca38e86ac/monotonic-1.6.tar.gz", hash = "sha256:3a55207bcfed53ddd5c5bae174524062935efed17792e9de2ad0205ce9ad63f7", size = 7615 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/9a/67/7e8406a29b6c45be7af7740456f7f37025f0506ae2e05fb9009a53946860/monotonic-1.6-py2.py3-none-any.whl", hash = "sha256:68687e19a14f11f26d140dd5c86f3dba4bf5df58003000ed467e0e2a69bca96c", size = 8154 }, +sdist = { url = "https://files.pythonhosted.org/packages/47/1b/1fc6888c74cbd8abad1292dde2ddfcf8fc059e114c97dd6bf16d12f36293/mmh3-5.1.0.tar.gz", hash = "sha256:136e1e670500f177f49ec106a4ebf0adf20d18d96990cc36ea492c651d2b406c", size = 33728, upload-time = "2025-01-25T08:39:43.386Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/56/09/fda7af7fe65928262098382e3bf55950cfbf67d30bf9e47731bf862161e9/mmh3-5.1.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0b529dcda3f951ff363a51d5866bc6d63cf57f1e73e8961f864ae5010647079d", size = 56098, upload-time = "2025-01-25T08:38:22.917Z" }, + { url = "https://files.pythonhosted.org/packages/0c/ab/84c7bc3f366d6f3bd8b5d9325a10c367685bc17c26dac4c068e2001a4671/mmh3-5.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4db1079b3ace965e562cdfc95847312f9273eb2ad3ebea983435c8423e06acd7", size = 40513, upload-time = "2025-01-25T08:38:25.079Z" }, + { url = "https://files.pythonhosted.org/packages/4f/21/25ea58ca4a652bdc83d1528bec31745cce35802381fb4fe3c097905462d2/mmh3-5.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:22d31e3a0ff89b8eb3b826d6fc8e19532998b2aa6b9143698043a1268da413e1", size = 40112, upload-time = "2025-01-25T08:38:25.947Z" }, + { url = "https://files.pythonhosted.org/packages/bd/78/4f12f16ae074ddda6f06745254fdb50f8cf3c85b0bbf7eaca58bed84bf58/mmh3-5.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2139bfbd354cd6cb0afed51c4b504f29bcd687a3b1460b7e89498329cc28a894", size = 102632, upload-time = "2025-01-25T08:38:26.939Z" }, + { url = "https://files.pythonhosted.org/packages/48/11/8f09dc999cf2a09b6138d8d7fc734efb7b7bfdd9adb9383380941caadff0/mmh3-5.1.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8c8105c6a435bc2cd6ea2ef59558ab1a2976fd4a4437026f562856d08996673a", size = 108884, upload-time = "2025-01-25T08:38:29.159Z" }, + { url = "https://files.pythonhosted.org/packages/bd/91/e59a66538a3364176f6c3f7620eee0ab195bfe26f89a95cbcc7a1fb04b28/mmh3-5.1.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:57730067174a7f36fcd6ce012fe359bd5510fdaa5fe067bc94ed03e65dafb769", size = 106835, upload-time = "2025-01-25T08:38:33.04Z" }, + { url = "https://files.pythonhosted.org/packages/25/14/b85836e21ab90e5cddb85fe79c494ebd8f81d96a87a664c488cc9277668b/mmh3-5.1.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bde80eb196d7fdc765a318604ded74a4378f02c5b46c17aa48a27d742edaded2", size = 93688, upload-time = "2025-01-25T08:38:34.987Z" }, + { url = "https://files.pythonhosted.org/packages/ac/aa/8bc964067df9262740c95e4cde2d19f149f2224f426654e14199a9e47df6/mmh3-5.1.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e9c8eddcb441abddeb419c16c56fd74b3e2df9e57f7aa2903221996718435c7a", size = 101569, upload-time = "2025-01-25T08:38:35.983Z" }, + { url = "https://files.pythonhosted.org/packages/70/b6/1fb163cbf919046a64717466c00edabebece3f95c013853fec76dbf2df92/mmh3-5.1.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:99e07e4acafbccc7a28c076a847fb060ffc1406036bc2005acb1b2af620e53c3", size = 98483, upload-time = "2025-01-25T08:38:38.198Z" }, + { url = "https://files.pythonhosted.org/packages/70/49/ba64c050dd646060f835f1db6b2cd60a6485f3b0ea04976e7a29ace7312e/mmh3-5.1.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:9e25ba5b530e9a7d65f41a08d48f4b3fedc1e89c26486361166a5544aa4cad33", size = 96496, upload-time = "2025-01-25T08:38:39.257Z" }, + { url = "https://files.pythonhosted.org/packages/9e/07/f2751d6a0b535bb865e1066e9c6b80852571ef8d61bce7eb44c18720fbfc/mmh3-5.1.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:bb9bf7475b4d99156ce2f0cf277c061a17560c8c10199c910a680869a278ddc7", size = 105109, upload-time = "2025-01-25T08:38:40.395Z" }, + { url = "https://files.pythonhosted.org/packages/b7/02/30360a5a66f7abba44596d747cc1e6fb53136b168eaa335f63454ab7bb79/mmh3-5.1.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:2a1b0878dd281ea3003368ab53ff6f568e175f1b39f281df1da319e58a19c23a", size = 98231, upload-time = "2025-01-25T08:38:42.141Z" }, + { url = "https://files.pythonhosted.org/packages/8c/60/8526b0c750ff4d7ae1266e68b795f14b97758a1d9fcc19f6ecabf9c55656/mmh3-5.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:25f565093ac8b8aefe0f61f8f95c9a9d11dd69e6a9e9832ff0d293511bc36258", size = 97548, upload-time = "2025-01-25T08:38:43.402Z" }, + { url = "https://files.pythonhosted.org/packages/6d/4c/26e1222aca65769280d5427a1ce5875ef4213449718c8f03958d0bf91070/mmh3-5.1.0-cp311-cp311-win32.whl", hash = "sha256:1e3554d8792387eac73c99c6eaea0b3f884e7130eb67986e11c403e4f9b6d372", size = 40810, upload-time = "2025-01-25T08:38:45.143Z" }, + { url = "https://files.pythonhosted.org/packages/98/d5/424ba95062d1212ea615dc8debc8d57983f2242d5e6b82e458b89a117a1e/mmh3-5.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:8ad777a48197882492af50bf3098085424993ce850bdda406a358b6ab74be759", size = 41476, upload-time = "2025-01-25T08:38:46.029Z" }, + { url = "https://files.pythonhosted.org/packages/bd/08/0315ccaf087ba55bb19a6dd3b1e8acd491e74ce7f5f9c4aaa06a90d66441/mmh3-5.1.0-cp311-cp311-win_arm64.whl", hash = "sha256:f29dc4efd99bdd29fe85ed6c81915b17b2ef2cf853abf7213a48ac6fb3eaabe1", size = 38880, upload-time = "2025-01-25T08:38:47.035Z" }, + { url = "https://files.pythonhosted.org/packages/f4/47/e5f452bdf16028bfd2edb4e2e35d0441e4a4740f30e68ccd4cfd2fb2c57e/mmh3-5.1.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:45712987367cb9235026e3cbf4334670522a97751abfd00b5bc8bfa022c3311d", size = 56152, upload-time = "2025-01-25T08:38:47.902Z" }, + { url = "https://files.pythonhosted.org/packages/60/38/2132d537dc7a7fdd8d2e98df90186c7fcdbd3f14f95502a24ba443c92245/mmh3-5.1.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b1020735eb35086ab24affbea59bb9082f7f6a0ad517cb89f0fc14f16cea4dae", size = 40564, upload-time = "2025-01-25T08:38:48.839Z" }, + { url = "https://files.pythonhosted.org/packages/c0/2a/c52cf000581bfb8d94794f58865658e7accf2fa2e90789269d4ae9560b16/mmh3-5.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:babf2a78ce5513d120c358722a2e3aa7762d6071cd10cede026f8b32452be322", size = 40104, upload-time = "2025-01-25T08:38:49.773Z" }, + { url = "https://files.pythonhosted.org/packages/83/33/30d163ce538c54fc98258db5621447e3ab208d133cece5d2577cf913e708/mmh3-5.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d4f47f58cd5cbef968c84a7c1ddc192fef0a36b48b0b8a3cb67354531aa33b00", size = 102634, upload-time = "2025-01-25T08:38:51.5Z" }, + { url = "https://files.pythonhosted.org/packages/94/5c/5a18acb6ecc6852be2d215c3d811aa61d7e425ab6596be940877355d7f3e/mmh3-5.1.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2044a601c113c981f2c1e14fa33adc9b826c9017034fe193e9eb49a6882dbb06", size = 108888, upload-time = "2025-01-25T08:38:52.542Z" }, + { url = "https://files.pythonhosted.org/packages/1f/f6/11c556324c64a92aa12f28e221a727b6e082e426dc502e81f77056f6fc98/mmh3-5.1.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c94d999c9f2eb2da44d7c2826d3fbffdbbbbcde8488d353fee7c848ecc42b968", size = 106968, upload-time = "2025-01-25T08:38:54.286Z" }, + { url = "https://files.pythonhosted.org/packages/5d/61/ca0c196a685aba7808a5c00246f17b988a9c4f55c594ee0a02c273e404f3/mmh3-5.1.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a015dcb24fa0c7a78f88e9419ac74f5001c1ed6a92e70fd1803f74afb26a4c83", size = 93771, upload-time = "2025-01-25T08:38:55.576Z" }, + { url = "https://files.pythonhosted.org/packages/b4/55/0927c33528710085ee77b808d85bbbafdb91a1db7c8eaa89cac16d6c513e/mmh3-5.1.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:457da019c491a2d20e2022c7d4ce723675e4c081d9efc3b4d8b9f28a5ea789bd", size = 101726, upload-time = "2025-01-25T08:38:56.654Z" }, + { url = "https://files.pythonhosted.org/packages/49/39/a92c60329fa470f41c18614a93c6cd88821412a12ee78c71c3f77e1cfc2d/mmh3-5.1.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:71408579a570193a4ac9c77344d68ddefa440b00468a0b566dcc2ba282a9c559", size = 98523, upload-time = "2025-01-25T08:38:57.662Z" }, + { url = "https://files.pythonhosted.org/packages/81/90/26adb15345af8d9cf433ae1b6adcf12e0a4cad1e692de4fa9f8e8536c5ae/mmh3-5.1.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:8b3a04bc214a6e16c81f02f855e285c6df274a2084787eeafaa45f2fbdef1b63", size = 96628, upload-time = "2025-01-25T08:38:59.505Z" }, + { url = "https://files.pythonhosted.org/packages/8a/4d/340d1e340df972a13fd4ec84c787367f425371720a1044220869c82364e9/mmh3-5.1.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:832dae26a35514f6d3c1e267fa48e8de3c7b978afdafa0529c808ad72e13ada3", size = 105190, upload-time = "2025-01-25T08:39:00.483Z" }, + { url = "https://files.pythonhosted.org/packages/d3/7c/65047d1cccd3782d809936db446430fc7758bda9def5b0979887e08302a2/mmh3-5.1.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:bf658a61fc92ef8a48945ebb1076ef4ad74269e353fffcb642dfa0890b13673b", size = 98439, upload-time = "2025-01-25T08:39:01.484Z" }, + { url = "https://files.pythonhosted.org/packages/72/d2/3c259d43097c30f062050f7e861075099404e8886b5d4dd3cebf180d6e02/mmh3-5.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3313577453582b03383731b66447cdcdd28a68f78df28f10d275d7d19010c1df", size = 97780, upload-time = "2025-01-25T08:39:02.444Z" }, + { url = "https://files.pythonhosted.org/packages/29/29/831ea8d4abe96cdb3e28b79eab49cac7f04f9c6b6e36bfc686197ddba09d/mmh3-5.1.0-cp312-cp312-win32.whl", hash = "sha256:1d6508504c531ab86c4424b5a5ff07c1132d063863339cf92f6657ff7a580f76", size = 40835, upload-time = "2025-01-25T08:39:03.369Z" }, + { url = "https://files.pythonhosted.org/packages/12/dd/7cbc30153b73f08eeac43804c1dbc770538a01979b4094edbe1a4b8eb551/mmh3-5.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:aa75981fcdf3f21759d94f2c81b6a6e04a49dfbcdad88b152ba49b8e20544776", size = 41509, upload-time = "2025-01-25T08:39:04.284Z" }, + { url = "https://files.pythonhosted.org/packages/80/9d/627375bab4c90dd066093fc2c9a26b86f87e26d980dbf71667b44cbee3eb/mmh3-5.1.0-cp312-cp312-win_arm64.whl", hash = "sha256:a4c1a76808dfea47f7407a0b07aaff9087447ef6280716fd0783409b3088bb3c", size = 38888, upload-time = "2025-01-25T08:39:05.174Z" }, ] [[package]] name = "mpmath" version = "1.3.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e0/47/dd32fa426cc72114383ac549964eecb20ecfd886d1e5ccf5340b55b02f57/mpmath-1.3.0.tar.gz", hash = "sha256:7a28eb2a9774d00c7bc92411c19a89209d5da7c4c9a9e227be8330a23a25b91f", size = 508106 } +sdist = { url = "https://files.pythonhosted.org/packages/e0/47/dd32fa426cc72114383ac549964eecb20ecfd886d1e5ccf5340b55b02f57/mpmath-1.3.0.tar.gz", hash = "sha256:7a28eb2a9774d00c7bc92411c19a89209d5da7c4c9a9e227be8330a23a25b91f", size = 508106, upload-time = "2023-03-07T16:47:11.061Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/43/e3/7d92a15f894aa0c9c4b49b8ee9ac9850d6e63b03c9c32c0367a13ae62209/mpmath-1.3.0-py3-none-any.whl", hash = "sha256:a0b2b9fe80bbcd81a6647ff13108738cfb482d481d826cc0e02f5b35e5c88d2c", size = 536198 }, + { url = "https://files.pythonhosted.org/packages/43/e3/7d92a15f894aa0c9c4b49b8ee9ac9850d6e63b03c9c32c0367a13ae62209/mpmath-1.3.0-py3-none-any.whl", hash = "sha256:a0b2b9fe80bbcd81a6647ff13108738cfb482d481d826cc0e02f5b35e5c88d2c", size = 536198, upload-time = "2023-03-07T16:47:09.197Z" }, ] [[package]] name = "msal" -version = "1.32.0" +version = "1.32.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cryptography" }, { name = "pyjwt", extra = ["crypto"] }, { name = "requests" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/aa/5f/ef42ef25fba682e83a8ee326a1a788e60c25affb58d014495349e37bce50/msal-1.32.0.tar.gz", hash = "sha256:5445fe3af1da6be484991a7ab32eaa82461dc2347de105b76af92c610c3335c2", size = 149817 } +sdist = { url = "https://files.pythonhosted.org/packages/3f/90/81dcc50f0be11a8c4dcbae1a9f761a26e5f905231330a7cacc9f04ec4c61/msal-1.32.3.tar.gz", hash = "sha256:5eea038689c78a5a70ca8ecbe1245458b55a857bd096efb6989c69ba15985d35", size = 151449, upload-time = "2025-04-25T13:12:34.204Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/93/5a/2e663ef56a5d89eba962941b267ebe5be8c5ea340a9929d286e2f5fac505/msal-1.32.0-py3-none-any.whl", hash = "sha256:9dbac5384a10bbbf4dae5c7ea0d707d14e087b92c5aa4954b3feaa2d1aa0bcb7", size = 114655 }, + { url = "https://files.pythonhosted.org/packages/04/bf/81516b9aac7fd867709984d08eb4db1d2e3fe1df795c8e442cde9b568962/msal-1.32.3-py3-none-any.whl", hash = "sha256:b2798db57760b1961b142f027ffb7c8169536bf77316e99a0df5c4aaebb11569", size = 115358, upload-time = "2025-04-25T13:12:33.034Z" }, ] [[package]] @@ -3001,9 +3132,9 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "msal" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/01/99/5d239b6156eddf761a636bded1118414d161bd6b7b37a9335549ed159396/msal_extensions-1.3.1.tar.gz", hash = "sha256:c5b0fd10f65ef62b5f1d62f4251d51cbcaf003fcedae8c91b040a488614be1a4", size = 23315 } +sdist = { url = "https://files.pythonhosted.org/packages/01/99/5d239b6156eddf761a636bded1118414d161bd6b7b37a9335549ed159396/msal_extensions-1.3.1.tar.gz", hash = "sha256:c5b0fd10f65ef62b5f1d62f4251d51cbcaf003fcedae8c91b040a488614be1a4", size = 23315, upload-time = "2025-03-14T23:51:03.902Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/5e/75/bd9b7bb966668920f06b200e84454c8f3566b102183bc55c5473d96cb2b9/msal_extensions-1.3.1-py3-none-any.whl", hash = "sha256:96d3de4d034504e969ac5e85bae8106c8373b5c6568e4c8fa7af2eca9dbe6bca", size = 20583 }, + { url = "https://files.pythonhosted.org/packages/5e/75/bd9b7bb966668920f06b200e84454c8f3566b102183bc55c5473d96cb2b9/msal_extensions-1.3.1-py3-none-any.whl", hash = "sha256:96d3de4d034504e969ac5e85bae8106c8373b5c6568e4c8fa7af2eca9dbe6bca", size = 20583, upload-time = "2025-03-14T23:51:03.016Z" }, ] [[package]] @@ -3017,91 +3148,108 @@ dependencies = [ { name = "requests" }, { name = "requests-oauthlib" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/68/77/8397c8fb8fc257d8ea0fa66f8068e073278c65f05acb17dcb22a02bfdc42/msrest-0.7.1.zip", hash = "sha256:6e7661f46f3afd88b75667b7187a92829924446c7ea1d169be8c4bb7eeb788b9", size = 175332 } +sdist = { url = "https://files.pythonhosted.org/packages/68/77/8397c8fb8fc257d8ea0fa66f8068e073278c65f05acb17dcb22a02bfdc42/msrest-0.7.1.zip", hash = "sha256:6e7661f46f3afd88b75667b7187a92829924446c7ea1d169be8c4bb7eeb788b9", size = 175332, upload-time = "2022-06-13T22:41:25.111Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/15/cf/f2966a2638144491f8696c27320d5219f48a072715075d168b31d3237720/msrest-0.7.1-py3-none-any.whl", hash = "sha256:21120a810e1233e5e6cc7fe40b474eeb4ec6f757a15d7cf86702c369f9567c32", size = 85384 }, + { url = "https://files.pythonhosted.org/packages/15/cf/f2966a2638144491f8696c27320d5219f48a072715075d168b31d3237720/msrest-0.7.1-py3-none-any.whl", hash = "sha256:21120a810e1233e5e6cc7fe40b474eeb4ec6f757a15d7cf86702c369f9567c32", size = 85384, upload-time = "2022-06-13T22:41:22.42Z" }, ] [[package]] name = "multidict" -version = "6.2.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/82/4a/7874ca44a1c9b23796c767dd94159f6c17e31c0e7d090552a1c623247d82/multidict-6.2.0.tar.gz", hash = "sha256:0085b0afb2446e57050140240a8595846ed64d1cbd26cef936bfab3192c673b8", size = 71066 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/97/aa/879cf5581bd56c19f1bd2682ee4ecfd4085a404668d4ee5138b0a08eaf2a/multidict-6.2.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:84e87a7d75fa36839a3a432286d719975362d230c70ebfa0948549cc38bd5b46", size = 49125 }, - { url = "https://files.pythonhosted.org/packages/9e/d8/e6d47c166c13c48be8efb9720afe0f5cdc4da4687547192cbc3c03903041/multidict-6.2.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:8de4d42dffd5ced9117af2ce66ba8722402541a3aa98ffdf78dde92badb68932", size = 29689 }, - { url = "https://files.pythonhosted.org/packages/a4/20/f3f0a2ca142c81100b6d4cbf79505961b54181d66157615bba3955304442/multidict-6.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e7d91a230c7f8af86c904a5a992b8c064b66330544693fd6759c3d6162382ecf", size = 29975 }, - { url = "https://files.pythonhosted.org/packages/ab/2d/1724972c7aeb7aa1916a3276cb32f9c39e186456ee7ed621504e7a758322/multidict-6.2.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9f6cad071960ba1914fa231677d21b1b4a3acdcce463cee41ea30bc82e6040cf", size = 135688 }, - { url = "https://files.pythonhosted.org/packages/1a/08/ea54e7e245aaf0bb1c758578e5afba394ffccb8bd80d229a499b9b83f2b1/multidict-6.2.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0f74f2fc51555f4b037ef278efc29a870d327053aba5cb7d86ae572426c7cccc", size = 142703 }, - { url = "https://files.pythonhosted.org/packages/97/76/960dee0424f38c71eda54101ee1ca7bb47c5250ed02f7b3e8e50b1ce0603/multidict-6.2.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:14ed9ed1bfedd72a877807c71113deac292bf485159a29025dfdc524c326f3e1", size = 138559 }, - { url = "https://files.pythonhosted.org/packages/d0/35/969fd792e2e72801d80307f0a14f5b19c066d4a51d34dded22c71401527d/multidict-6.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4ac3fcf9a2d369bd075b2c2965544036a27ccd277fc3c04f708338cc57533081", size = 133312 }, - { url = "https://files.pythonhosted.org/packages/a4/b8/f96657a2f744d577cfda5a7edf9da04a731b80d3239eafbfe7ca4d944695/multidict-6.2.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2fc6af8e39f7496047c7876314f4317736eac82bf85b54c7c76cf1a6f8e35d98", size = 125652 }, - { url = "https://files.pythonhosted.org/packages/35/9d/97696d052297d8e2e08195a25c7aae873a6186c147b7635f979edbe3acde/multidict-6.2.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:5f8cb1329f42fadfb40d6211e5ff568d71ab49be36e759345f91c69d1033d633", size = 139015 }, - { url = "https://files.pythonhosted.org/packages/31/a0/5c106e28d42f20288c10049bc6647364287ba049dc00d6ae4f1584eb1bd1/multidict-6.2.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5389445f0173c197f4a3613713b5fb3f3879df1ded2a1a2e4bc4b5b9c5441b7e", size = 132437 }, - { url = "https://files.pythonhosted.org/packages/55/57/d5c60c075fef73422ae3b8f914221485b9ff15000b2db657c03bd190aee0/multidict-6.2.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:94a7bb972178a8bfc4055db80c51efd24baefaced5e51c59b0d598a004e8305d", size = 144037 }, - { url = "https://files.pythonhosted.org/packages/eb/56/a23f599c697a455bf65ecb0f69a5b052d6442c567d380ed423f816246824/multidict-6.2.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:da51d8928ad8b4244926fe862ba1795f0b6e68ed8c42cd2f822d435db9c2a8f4", size = 138535 }, - { url = "https://files.pythonhosted.org/packages/34/3a/a06ff9b5899090f4bbdbf09e237964c76cecfe75d2aa921e801356314017/multidict-6.2.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:063be88bd684782a0715641de853e1e58a2f25b76388538bd62d974777ce9bc2", size = 136885 }, - { url = "https://files.pythonhosted.org/packages/d6/28/489c0eca1df3800cb5d0a66278d5dd2a4deae747a41d1cf553e6a4c0a984/multidict-6.2.0-cp311-cp311-win32.whl", hash = "sha256:52b05e21ff05729fbea9bc20b3a791c3c11da61649ff64cce8257c82a020466d", size = 27044 }, - { url = "https://files.pythonhosted.org/packages/d0/b5/c7cd5ba9581add40bc743980f82426b90d9f42db0b56502011f1b3c929df/multidict-6.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:1e2a2193d3aa5cbf5758f6d5680a52aa848e0cf611da324f71e5e48a9695cc86", size = 29145 }, - { url = "https://files.pythonhosted.org/packages/a4/e2/0153a8db878aef9b2397be81e62cbc3b32ca9b94e0f700b103027db9d506/multidict-6.2.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:437c33561edb6eb504b5a30203daf81d4a9b727e167e78b0854d9a4e18e8950b", size = 49204 }, - { url = "https://files.pythonhosted.org/packages/bb/9d/5ccb3224a976d1286f360bb4e89e67b7cdfb87336257fc99be3c17f565d7/multidict-6.2.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:9f49585f4abadd2283034fc605961f40c638635bc60f5162276fec075f2e37a4", size = 29807 }, - { url = "https://files.pythonhosted.org/packages/62/32/ef20037f51b84b074a89bab5af46d4565381c3f825fc7cbfc19c1ee156be/multidict-6.2.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5dd7106d064d05896ce28c97da3f46caa442fe5a43bc26dfb258e90853b39b44", size = 30000 }, - { url = "https://files.pythonhosted.org/packages/97/81/b0a7560bfc3ec72606232cd7e60159e09b9cf29e66014d770c1315868fa2/multidict-6.2.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e25b11a0417475f093d0f0809a149aff3943c2c56da50fdf2c3c88d57fe3dfbd", size = 131820 }, - { url = "https://files.pythonhosted.org/packages/49/3b/768bfc0e41179fbccd3a22925329a11755b7fdd53bec66dbf6b8772f0bce/multidict-6.2.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ac380cacdd3b183338ba63a144a34e9044520a6fb30c58aa14077157a033c13e", size = 136272 }, - { url = "https://files.pythonhosted.org/packages/71/ac/fd2be3fe98ff54e7739448f771ba730d42036de0870737db9ae34bb8efe9/multidict-6.2.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:61d5541f27533f803a941d3a3f8a3d10ed48c12cf918f557efcbf3cd04ef265c", size = 135233 }, - { url = "https://files.pythonhosted.org/packages/93/76/1657047da771315911a927b364a32dafce4135b79b64208ce4ac69525c56/multidict-6.2.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:facaf11f21f3a4c51b62931feb13310e6fe3475f85e20d9c9fdce0d2ea561b87", size = 132861 }, - { url = "https://files.pythonhosted.org/packages/19/a5/9f07ffb9bf68b8aaa406c2abee27ad87e8b62a60551587b8e59ee91aea84/multidict-6.2.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:095a2eabe8c43041d3e6c2cb8287a257b5f1801c2d6ebd1dd877424f1e89cf29", size = 122166 }, - { url = "https://files.pythonhosted.org/packages/95/23/b5ce3318d9d6c8f105c3679510f9d7202980545aad8eb4426313bd8da3ee/multidict-6.2.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a0cc398350ef31167e03f3ca7c19313d4e40a662adcb98a88755e4e861170bdd", size = 136052 }, - { url = "https://files.pythonhosted.org/packages/ce/5c/02cffec58ffe120873dce520af593415b91cc324be0345f534ad3637da4e/multidict-6.2.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:7c611345bbe7cb44aabb877cb94b63e86f2d0db03e382667dbd037866d44b4f8", size = 130094 }, - { url = "https://files.pythonhosted.org/packages/49/f3/3b19a83f4ebf53a3a2a0435f3e447aa227b242ba3fd96a92404b31fb3543/multidict-6.2.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:8cd1a0644ccaf27e9d2f6d9c9474faabee21f0578fe85225cc5af9a61e1653df", size = 140962 }, - { url = "https://files.pythonhosted.org/packages/cc/1a/c916b54fb53168c24cb6a3a0795fd99d0a59a0ea93fa9f6edeff5565cb20/multidict-6.2.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:89b3857652183b8206a891168af47bac10b970d275bba1f6ee46565a758c078d", size = 138082 }, - { url = "https://files.pythonhosted.org/packages/ef/1a/dcb7fb18f64b3727c61f432c1e1a0d52b3924016124e4bbc8a7d2e4fa57b/multidict-6.2.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:125dd82b40f8c06d08d87b3510beaccb88afac94e9ed4a6f6c71362dc7dbb04b", size = 136019 }, - { url = "https://files.pythonhosted.org/packages/fb/02/7695485375106f5c542574f70e1968c391f86fa3efc9f1fd76aac0af7237/multidict-6.2.0-cp312-cp312-win32.whl", hash = "sha256:76b34c12b013d813e6cb325e6bd4f9c984db27758b16085926bbe7ceeaace626", size = 26676 }, - { url = "https://files.pythonhosted.org/packages/3c/f5/f147000fe1f4078160157b15b0790fff0513646b0f9b7404bf34007a9b44/multidict-6.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:0b183a959fb88ad1be201de2c4bdf52fa8e46e6c185d76201286a97b6f5ee65c", size = 28899 }, - { url = "https://files.pythonhosted.org/packages/9c/fd/b247aec6add5601956d440488b7f23151d8343747e82c038af37b28d6098/multidict-6.2.0-py3-none-any.whl", hash = "sha256:5d26547423e5e71dcc562c4acdc134b900640a39abd9066d7326a7cc2324c530", size = 10266 }, +version = "6.4.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/91/2f/a3470242707058fe856fe59241eee5635d79087100b7042a867368863a27/multidict-6.4.4.tar.gz", hash = "sha256:69ee9e6ba214b5245031b76233dd95408a0fd57fdb019ddcc1ead4790932a8e8", size = 90183, upload-time = "2025-05-19T14:16:37.381Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/19/1b/4c6e638195851524a63972c5773c7737bea7e47b1ba402186a37773acee2/multidict-6.4.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:4f5f29794ac0e73d2a06ac03fd18870adc0135a9d384f4a306a951188ed02f95", size = 65515, upload-time = "2025-05-19T14:14:19.767Z" }, + { url = "https://files.pythonhosted.org/packages/25/d5/10e6bca9a44b8af3c7f920743e5fc0c2bcf8c11bf7a295d4cfe00b08fb46/multidict-6.4.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c04157266344158ebd57b7120d9b0b35812285d26d0e78193e17ef57bfe2979a", size = 38609, upload-time = "2025-05-19T14:14:21.538Z" }, + { url = "https://files.pythonhosted.org/packages/26/b4/91fead447ccff56247edc7f0535fbf140733ae25187a33621771ee598a18/multidict-6.4.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:bb61ffd3ab8310d93427e460f565322c44ef12769f51f77277b4abad7b6f7223", size = 37871, upload-time = "2025-05-19T14:14:22.666Z" }, + { url = "https://files.pythonhosted.org/packages/3b/37/cbc977cae59277e99d15bbda84cc53b5e0c4929ffd91d958347200a42ad0/multidict-6.4.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5e0ba18a9afd495f17c351d08ebbc4284e9c9f7971d715f196b79636a4d0de44", size = 226661, upload-time = "2025-05-19T14:14:24.124Z" }, + { url = "https://files.pythonhosted.org/packages/15/cd/7e0b57fbd4dc2fc105169c4ecce5be1a63970f23bb4ec8c721b67e11953d/multidict-6.4.4-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:9faf1b1dcaadf9f900d23a0e6d6c8eadd6a95795a0e57fcca73acce0eb912065", size = 223422, upload-time = "2025-05-19T14:14:25.437Z" }, + { url = "https://files.pythonhosted.org/packages/f1/01/1de268da121bac9f93242e30cd3286f6a819e5f0b8896511162d6ed4bf8d/multidict-6.4.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a4d1cb1327c6082c4fce4e2a438483390964c02213bc6b8d782cf782c9b1471f", size = 235447, upload-time = "2025-05-19T14:14:26.793Z" }, + { url = "https://files.pythonhosted.org/packages/d2/8c/8b9a5e4aaaf4f2de14e86181a3a3d7b105077f668b6a06f043ec794f684c/multidict-6.4.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:941f1bec2f5dbd51feeb40aea654c2747f811ab01bdd3422a48a4e4576b7d76a", size = 231455, upload-time = "2025-05-19T14:14:28.149Z" }, + { url = "https://files.pythonhosted.org/packages/35/db/e1817dcbaa10b319c412769cf999b1016890849245d38905b73e9c286862/multidict-6.4.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e5f8a146184da7ea12910a4cec51ef85e44f6268467fb489c3caf0cd512f29c2", size = 223666, upload-time = "2025-05-19T14:14:29.584Z" }, + { url = "https://files.pythonhosted.org/packages/4a/e1/66e8579290ade8a00e0126b3d9a93029033ffd84f0e697d457ed1814d0fc/multidict-6.4.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:232b7237e57ec3c09be97206bfb83a0aa1c5d7d377faa019c68a210fa35831f1", size = 217392, upload-time = "2025-05-19T14:14:30.961Z" }, + { url = "https://files.pythonhosted.org/packages/7b/6f/f8639326069c24a48c7747c2a5485d37847e142a3f741ff3340c88060a9a/multidict-6.4.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:55ae0721c1513e5e3210bca4fc98456b980b0c2c016679d3d723119b6b202c42", size = 228969, upload-time = "2025-05-19T14:14:32.672Z" }, + { url = "https://files.pythonhosted.org/packages/d2/c3/3d58182f76b960eeade51c89fcdce450f93379340457a328e132e2f8f9ed/multidict-6.4.4-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:51d662c072579f63137919d7bb8fc250655ce79f00c82ecf11cab678f335062e", size = 217433, upload-time = "2025-05-19T14:14:34.016Z" }, + { url = "https://files.pythonhosted.org/packages/e1/4b/f31a562906f3bd375f3d0e83ce314e4a660c01b16c2923e8229b53fba5d7/multidict-6.4.4-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:0e05c39962baa0bb19a6b210e9b1422c35c093b651d64246b6c2e1a7e242d9fd", size = 225418, upload-time = "2025-05-19T14:14:35.376Z" }, + { url = "https://files.pythonhosted.org/packages/99/89/78bb95c89c496d64b5798434a3deee21996114d4d2c28dd65850bf3a691e/multidict-6.4.4-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:d5b1cc3ab8c31d9ebf0faa6e3540fb91257590da330ffe6d2393d4208e638925", size = 235042, upload-time = "2025-05-19T14:14:36.723Z" }, + { url = "https://files.pythonhosted.org/packages/74/91/8780a6e5885a8770442a8f80db86a0887c4becca0e5a2282ba2cae702bc4/multidict-6.4.4-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:93ec84488a384cd7b8a29c2c7f467137d8a73f6fe38bb810ecf29d1ade011a7c", size = 230280, upload-time = "2025-05-19T14:14:38.194Z" }, + { url = "https://files.pythonhosted.org/packages/68/c1/fcf69cabd542eb6f4b892469e033567ee6991d361d77abdc55e3a0f48349/multidict-6.4.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b308402608493638763abc95f9dc0030bbd6ac6aff784512e8ac3da73a88af08", size = 223322, upload-time = "2025-05-19T14:14:40.015Z" }, + { url = "https://files.pythonhosted.org/packages/b8/85/5b80bf4b83d8141bd763e1d99142a9cdfd0db83f0739b4797172a4508014/multidict-6.4.4-cp311-cp311-win32.whl", hash = "sha256:343892a27d1a04d6ae455ecece12904d242d299ada01633d94c4f431d68a8c49", size = 35070, upload-time = "2025-05-19T14:14:41.904Z" }, + { url = "https://files.pythonhosted.org/packages/09/66/0bed198ffd590ab86e001f7fa46b740d58cf8ff98c2f254e4a36bf8861ad/multidict-6.4.4-cp311-cp311-win_amd64.whl", hash = "sha256:73484a94f55359780c0f458bbd3c39cb9cf9c182552177d2136e828269dee529", size = 38667, upload-time = "2025-05-19T14:14:43.534Z" }, + { url = "https://files.pythonhosted.org/packages/d2/b5/5675377da23d60875fe7dae6be841787755878e315e2f517235f22f59e18/multidict-6.4.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:dc388f75a1c00000824bf28b7633e40854f4127ede80512b44c3cfeeea1839a2", size = 64293, upload-time = "2025-05-19T14:14:44.724Z" }, + { url = "https://files.pythonhosted.org/packages/34/a7/be384a482754bb8c95d2bbe91717bf7ccce6dc38c18569997a11f95aa554/multidict-6.4.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:98af87593a666f739d9dba5d0ae86e01b0e1a9cfcd2e30d2d361fbbbd1a9162d", size = 38096, upload-time = "2025-05-19T14:14:45.95Z" }, + { url = "https://files.pythonhosted.org/packages/66/6d/d59854bb4352306145bdfd1704d210731c1bb2c890bfee31fb7bbc1c4c7f/multidict-6.4.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:aff4cafea2d120327d55eadd6b7f1136a8e5a0ecf6fb3b6863e8aca32cd8e50a", size = 37214, upload-time = "2025-05-19T14:14:47.158Z" }, + { url = "https://files.pythonhosted.org/packages/99/e0/c29d9d462d7cfc5fc8f9bf24f9c6843b40e953c0b55e04eba2ad2cf54fba/multidict-6.4.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:169c4ba7858176b797fe551d6e99040c531c775d2d57b31bcf4de6d7a669847f", size = 224686, upload-time = "2025-05-19T14:14:48.366Z" }, + { url = "https://files.pythonhosted.org/packages/dc/4a/da99398d7fd8210d9de068f9a1b5f96dfaf67d51e3f2521f17cba4ee1012/multidict-6.4.4-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:b9eb4c59c54421a32b3273d4239865cb14ead53a606db066d7130ac80cc8ec93", size = 231061, upload-time = "2025-05-19T14:14:49.952Z" }, + { url = "https://files.pythonhosted.org/packages/21/f5/ac11add39a0f447ac89353e6ca46666847051103649831c08a2800a14455/multidict-6.4.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7cf3bd54c56aa16fdb40028d545eaa8d051402b61533c21e84046e05513d5780", size = 232412, upload-time = "2025-05-19T14:14:51.812Z" }, + { url = "https://files.pythonhosted.org/packages/d9/11/4b551e2110cded705a3c13a1d4b6a11f73891eb5a1c449f1b2b6259e58a6/multidict-6.4.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f682c42003c7264134bfe886376299db4cc0c6cd06a3295b41b347044bcb5482", size = 231563, upload-time = "2025-05-19T14:14:53.262Z" }, + { url = "https://files.pythonhosted.org/packages/4c/02/751530c19e78fe73b24c3da66618eda0aa0d7f6e7aa512e46483de6be210/multidict-6.4.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a920f9cf2abdf6e493c519492d892c362007f113c94da4c239ae88429835bad1", size = 223811, upload-time = "2025-05-19T14:14:55.232Z" }, + { url = "https://files.pythonhosted.org/packages/c7/cb/2be8a214643056289e51ca356026c7b2ce7225373e7a1f8c8715efee8988/multidict-6.4.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:530d86827a2df6504526106b4c104ba19044594f8722d3e87714e847c74a0275", size = 216524, upload-time = "2025-05-19T14:14:57.226Z" }, + { url = "https://files.pythonhosted.org/packages/19/f3/6d5011ec375c09081f5250af58de85f172bfcaafebff286d8089243c4bd4/multidict-6.4.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ecde56ea2439b96ed8a8d826b50c57364612ddac0438c39e473fafad7ae1c23b", size = 229012, upload-time = "2025-05-19T14:14:58.597Z" }, + { url = "https://files.pythonhosted.org/packages/67/9c/ca510785df5cf0eaf5b2a8132d7d04c1ce058dcf2c16233e596ce37a7f8e/multidict-6.4.4-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:dc8c9736d8574b560634775ac0def6bdc1661fc63fa27ffdfc7264c565bcb4f2", size = 226765, upload-time = "2025-05-19T14:15:00.048Z" }, + { url = "https://files.pythonhosted.org/packages/36/c8/ca86019994e92a0f11e642bda31265854e6ea7b235642f0477e8c2e25c1f/multidict-6.4.4-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:7f3d3b3c34867579ea47cbd6c1f2ce23fbfd20a273b6f9e3177e256584f1eacc", size = 222888, upload-time = "2025-05-19T14:15:01.568Z" }, + { url = "https://files.pythonhosted.org/packages/c6/67/bc25a8e8bd522935379066950ec4e2277f9b236162a73548a2576d4b9587/multidict-6.4.4-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:87a728af265e08f96b6318ebe3c0f68b9335131f461efab2fc64cc84a44aa6ed", size = 234041, upload-time = "2025-05-19T14:15:03.759Z" }, + { url = "https://files.pythonhosted.org/packages/f1/a0/70c4c2d12857fccbe607b334b7ee28b6b5326c322ca8f73ee54e70d76484/multidict-6.4.4-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:9f193eeda1857f8e8d3079a4abd258f42ef4a4bc87388452ed1e1c4d2b0c8740", size = 231046, upload-time = "2025-05-19T14:15:05.698Z" }, + { url = "https://files.pythonhosted.org/packages/c1/0f/52954601d02d39742aab01d6b92f53c1dd38b2392248154c50797b4df7f1/multidict-6.4.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:be06e73c06415199200e9a2324a11252a3d62030319919cde5e6950ffeccf72e", size = 227106, upload-time = "2025-05-19T14:15:07.124Z" }, + { url = "https://files.pythonhosted.org/packages/af/24/679d83ec4379402d28721790dce818e5d6b9f94ce1323a556fb17fa9996c/multidict-6.4.4-cp312-cp312-win32.whl", hash = "sha256:622f26ea6a7e19b7c48dd9228071f571b2fbbd57a8cd71c061e848f281550e6b", size = 35351, upload-time = "2025-05-19T14:15:08.556Z" }, + { url = "https://files.pythonhosted.org/packages/52/ef/40d98bc5f986f61565f9b345f102409534e29da86a6454eb6b7c00225a13/multidict-6.4.4-cp312-cp312-win_amd64.whl", hash = "sha256:5e2bcda30d5009996ff439e02a9f2b5c3d64a20151d34898c000a6281faa3781", size = 38791, upload-time = "2025-05-19T14:15:09.825Z" }, + { url = "https://files.pythonhosted.org/packages/84/5d/e17845bb0fa76334477d5de38654d27946d5b5d3695443987a094a71b440/multidict-6.4.4-py3-none-any.whl", hash = "sha256:bd4557071b561a8b3b6075c3ce93cf9bfb6182cb241805c3d66ced3b75eff4ac", size = 10481, upload-time = "2025-05-19T14:16:36.024Z" }, ] [[package]] name = "mypy" -version = "1.15.0" +version = "1.16.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "mypy-extensions" }, + { name = "pathspec" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ce/43/d5e49a86afa64bd3839ea0d5b9c7103487007d728e1293f52525d6d5486a/mypy-1.15.0.tar.gz", hash = "sha256:404534629d51d3efea5c800ee7c42b72a6554d6c400e6a79eafe15d11341fd43", size = 3239717 } +sdist = { url = "https://files.pythonhosted.org/packages/d4/38/13c2f1abae94d5ea0354e146b95a1be9b2137a0d506728e0da037c4276f6/mypy-1.16.0.tar.gz", hash = "sha256:84b94283f817e2aa6350a14b4a8fb2a35a53c286f97c9d30f53b63620e7af8ab", size = 3323139, upload-time = "2025-05-29T13:46:12.532Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/24/c4/ff2f79db7075c274fe85b5fff8797d29c6b61b8854c39e3b7feb556aa377/mypy-1.16.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9f826aaa7ff8443bac6a494cf743f591488ea940dd360e7dd330e30dd772a5ab", size = 10884498, upload-time = "2025-05-29T13:18:54.066Z" }, + { url = "https://files.pythonhosted.org/packages/02/07/12198e83006235f10f6a7808917376b5d6240a2fd5dce740fe5d2ebf3247/mypy-1.16.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:82d056e6faa508501af333a6af192c700b33e15865bda49611e3d7d8358ebea2", size = 10011755, upload-time = "2025-05-29T13:34:00.851Z" }, + { url = "https://files.pythonhosted.org/packages/f1/9b/5fd5801a72b5d6fb6ec0105ea1d0e01ab2d4971893076e558d4b6d6b5f80/mypy-1.16.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:089bedc02307c2548eb51f426e085546db1fa7dd87fbb7c9fa561575cf6eb1ff", size = 11800138, upload-time = "2025-05-29T13:32:55.082Z" }, + { url = "https://files.pythonhosted.org/packages/2e/81/a117441ea5dfc3746431e51d78a4aca569c677aa225bca2cc05a7c239b61/mypy-1.16.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6a2322896003ba66bbd1318c10d3afdfe24e78ef12ea10e2acd985e9d684a666", size = 12533156, upload-time = "2025-05-29T13:19:12.963Z" }, + { url = "https://files.pythonhosted.org/packages/3f/38/88ec57c6c86014d3f06251e00f397b5a7daa6888884d0abf187e4f5f587f/mypy-1.16.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:021a68568082c5b36e977d54e8f1de978baf401a33884ffcea09bd8e88a98f4c", size = 12742426, upload-time = "2025-05-29T13:20:22.72Z" }, + { url = "https://files.pythonhosted.org/packages/bd/53/7e9d528433d56e6f6f77ccf24af6ce570986c2d98a5839e4c2009ef47283/mypy-1.16.0-cp311-cp311-win_amd64.whl", hash = "sha256:54066fed302d83bf5128632d05b4ec68412e1f03ef2c300434057d66866cea4b", size = 9478319, upload-time = "2025-05-29T13:21:17.582Z" }, + { url = "https://files.pythonhosted.org/packages/70/cf/158e5055e60ca2be23aec54a3010f89dcffd788732634b344fc9cb1e85a0/mypy-1.16.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c5436d11e89a3ad16ce8afe752f0f373ae9620841c50883dc96f8b8805620b13", size = 11062927, upload-time = "2025-05-29T13:35:52.328Z" }, + { url = "https://files.pythonhosted.org/packages/94/34/cfff7a56be1609f5d10ef386342ce3494158e4d506516890142007e6472c/mypy-1.16.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f2622af30bf01d8fc36466231bdd203d120d7a599a6d88fb22bdcb9dbff84090", size = 10083082, upload-time = "2025-05-29T13:35:33.378Z" }, + { url = "https://files.pythonhosted.org/packages/b3/7f/7242062ec6288c33d8ad89574df87c3903d394870e5e6ba1699317a65075/mypy-1.16.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d045d33c284e10a038f5e29faca055b90eee87da3fc63b8889085744ebabb5a1", size = 11828306, upload-time = "2025-05-29T13:21:02.164Z" }, + { url = "https://files.pythonhosted.org/packages/6f/5f/b392f7b4f659f5b619ce5994c5c43caab3d80df2296ae54fa888b3d17f5a/mypy-1.16.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b4968f14f44c62e2ec4a038c8797a87315be8df7740dc3ee8d3bfe1c6bf5dba8", size = 12702764, upload-time = "2025-05-29T13:20:42.826Z" }, + { url = "https://files.pythonhosted.org/packages/9b/c0/7646ef3a00fa39ac9bc0938626d9ff29d19d733011be929cfea59d82d136/mypy-1.16.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:eb14a4a871bb8efb1e4a50360d4e3c8d6c601e7a31028a2c79f9bb659b63d730", size = 12896233, upload-time = "2025-05-29T13:18:37.446Z" }, + { url = "https://files.pythonhosted.org/packages/6d/38/52f4b808b3fef7f0ef840ee8ff6ce5b5d77381e65425758d515cdd4f5bb5/mypy-1.16.0-cp312-cp312-win_amd64.whl", hash = "sha256:bd4e1ebe126152a7bbaa4daedd781c90c8f9643c79b9748caa270ad542f12bec", size = 9565547, upload-time = "2025-05-29T13:20:02.836Z" }, + { url = "https://files.pythonhosted.org/packages/99/a3/6ed10530dec8e0fdc890d81361260c9ef1f5e5c217ad8c9b21ecb2b8366b/mypy-1.16.0-py3-none-any.whl", hash = "sha256:29e1499864a3888bca5c1542f2d7232c6e586295183320caa95758fc84034031", size = 2265773, upload-time = "2025-05-29T13:35:18.762Z" }, +] + +[[package]] +name = "mypy-boto3-bedrock-runtime" +version = "1.38.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions", marker = "python_full_version < '3.12'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7d/55/56ce6f23d7fb98ce5b8a4261f089890bc94250666ea7089539dab55f6c25/mypy_boto3_bedrock_runtime-1.38.4.tar.gz", hash = "sha256:315a5f84c014c54e5406fdb80b030aba5cc79eb27982aff3d09ef331fb2cdd4d", size = 26169, upload-time = "2025-04-28T19:26:13.437Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/03/bc/f6339726c627bd7ca1ce0fa56c9ae2d0144604a319e0e339bdadafbbb599/mypy-1.15.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2922d42e16d6de288022e5ca321cd0618b238cfc5570e0263e5ba0a77dbef56f", size = 10662338 }, - { url = "https://files.pythonhosted.org/packages/e2/90/8dcf506ca1a09b0d17555cc00cd69aee402c203911410136cd716559efe7/mypy-1.15.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2ee2d57e01a7c35de00f4634ba1bbf015185b219e4dc5909e281016df43f5ee5", size = 9787540 }, - { url = "https://files.pythonhosted.org/packages/05/05/a10f9479681e5da09ef2f9426f650d7b550d4bafbef683b69aad1ba87457/mypy-1.15.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:973500e0774b85d9689715feeffcc980193086551110fd678ebe1f4342fb7c5e", size = 11538051 }, - { url = "https://files.pythonhosted.org/packages/e9/9a/1f7d18b30edd57441a6411fcbc0c6869448d1a4bacbaee60656ac0fc29c8/mypy-1.15.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5a95fb17c13e29d2d5195869262f8125dfdb5c134dc8d9a9d0aecf7525b10c2c", size = 12286751 }, - { url = "https://files.pythonhosted.org/packages/72/af/19ff499b6f1dafcaf56f9881f7a965ac2f474f69f6f618b5175b044299f5/mypy-1.15.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1905f494bfd7d85a23a88c5d97840888a7bd516545fc5aaedff0267e0bb54e2f", size = 12421783 }, - { url = "https://files.pythonhosted.org/packages/96/39/11b57431a1f686c1aed54bf794870efe0f6aeca11aca281a0bd87a5ad42c/mypy-1.15.0-cp311-cp311-win_amd64.whl", hash = "sha256:c9817fa23833ff189db061e6d2eff49b2f3b6ed9856b4a0a73046e41932d744f", size = 9265618 }, - { url = "https://files.pythonhosted.org/packages/98/3a/03c74331c5eb8bd025734e04c9840532226775c47a2c39b56a0c8d4f128d/mypy-1.15.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:aea39e0583d05124836ea645f412e88a5c7d0fd77a6d694b60d9b6b2d9f184fd", size = 10793981 }, - { url = "https://files.pythonhosted.org/packages/f0/1a/41759b18f2cfd568848a37c89030aeb03534411eef981df621d8fad08a1d/mypy-1.15.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2f2147ab812b75e5b5499b01ade1f4a81489a147c01585cda36019102538615f", size = 9749175 }, - { url = "https://files.pythonhosted.org/packages/12/7e/873481abf1ef112c582db832740f4c11b2bfa510e829d6da29b0ab8c3f9c/mypy-1.15.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ce436f4c6d218a070048ed6a44c0bbb10cd2cc5e272b29e7845f6a2f57ee4464", size = 11455675 }, - { url = "https://files.pythonhosted.org/packages/b3/d0/92ae4cde706923a2d3f2d6c39629134063ff64b9dedca9c1388363da072d/mypy-1.15.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8023ff13985661b50a5928fc7a5ca15f3d1affb41e5f0a9952cb68ef090b31ee", size = 12410020 }, - { url = "https://files.pythonhosted.org/packages/46/8b/df49974b337cce35f828ba6fda228152d6db45fed4c86ba56ffe442434fd/mypy-1.15.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1124a18bc11a6a62887e3e137f37f53fbae476dc36c185d549d4f837a2a6a14e", size = 12498582 }, - { url = "https://files.pythonhosted.org/packages/13/50/da5203fcf6c53044a0b699939f31075c45ae8a4cadf538a9069b165c1050/mypy-1.15.0-cp312-cp312-win_amd64.whl", hash = "sha256:171a9ca9a40cd1843abeca0e405bc1940cd9b305eaeea2dda769ba096932bb22", size = 9366614 }, - { url = "https://files.pythonhosted.org/packages/09/4e/a7d65c7322c510de2c409ff3828b03354a7c43f5a8ed458a7a131b41c7b9/mypy-1.15.0-py3-none-any.whl", hash = "sha256:5469affef548bd1895d86d3bf10ce2b44e33d86923c29e4d675b3e323437ea3e", size = 2221777 }, + { url = "https://files.pythonhosted.org/packages/43/eb/3015c6504540ca4888789ee14f47590c0340b748a33b059eeb6a48b406bb/mypy_boto3_bedrock_runtime-1.38.4-py3-none-any.whl", hash = "sha256:af14320532e9b798095129a3307f4b186ba80258917bb31410cda7f423592d72", size = 31858, upload-time = "2025-04-28T19:26:09.667Z" }, ] [[package]] name = "mypy-extensions" -version = "1.0.0" +version = "1.1.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/98/a4/1ab47638b92648243faf97a5aeb6ea83059cc3624972ab6b8d2316078d3f/mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782", size = 4433 } +sdist = { url = "https://files.pythonhosted.org/packages/a2/6e/371856a3fb9d31ca8dac321cda606860fa4548858c0cc45d9d1d4ca2628b/mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558", size = 6343, upload-time = "2025-04-22T14:54:24.164Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/2a/e2/5d3f6ada4297caebe1a2add3b126fe800c96f56dbe5d1988a2cbe0b267aa/mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d", size = 4695 }, + { url = "https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", size = 4963, upload-time = "2025-04-22T14:54:22.983Z" }, ] [[package]] name = "nest-asyncio" version = "1.6.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/83/f8/51569ac65d696c8ecbee95938f89d4abf00f47d58d48f6fbabfe8f0baefe/nest_asyncio-1.6.0.tar.gz", hash = "sha256:6f172d5449aca15afd6c646851f4e31e02c598d553a667e38cafa997cfec55fe", size = 7418 } +sdist = { url = "https://files.pythonhosted.org/packages/83/f8/51569ac65d696c8ecbee95938f89d4abf00f47d58d48f6fbabfe8f0baefe/nest_asyncio-1.6.0.tar.gz", hash = "sha256:6f172d5449aca15afd6c646851f4e31e02c598d553a667e38cafa997cfec55fe", size = 7418, upload-time = "2024-01-21T14:25:19.227Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a0/c4/c2971a3ba4c6103a3d10c4b0f24f461ddc027f0f09763220cf35ca1401b3/nest_asyncio-1.6.0-py3-none-any.whl", hash = "sha256:87af6efd6b5e897c81050477ef65c62e2b2f35d51703cae01aff2905b1852e1c", size = 5195 }, + { url = "https://files.pythonhosted.org/packages/a0/c4/c2971a3ba4c6103a3d10c4b0f24f461ddc027f0f09763220cf35ca1401b3/nest_asyncio-1.6.0-py3-none-any.whl", hash = "sha256:87af6efd6b5e897c81050477ef65c62e2b2f35d51703cae01aff2905b1852e1c", size = 5195, upload-time = "2024-01-21T14:25:17.223Z" }, ] [[package]] @@ -3114,9 +3262,9 @@ dependencies = [ { name = "regex" }, { name = "tqdm" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/3c/87/db8be88ad32c2d042420b6fd9ffd4a149f9a0d7f0e86b3f543be2eeeedd2/nltk-3.9.1.tar.gz", hash = "sha256:87d127bd3de4bd89a4f81265e5fa59cb1b199b27440175370f7417d2bc7ae868", size = 2904691 } +sdist = { url = "https://files.pythonhosted.org/packages/3c/87/db8be88ad32c2d042420b6fd9ffd4a149f9a0d7f0e86b3f543be2eeeedd2/nltk-3.9.1.tar.gz", hash = "sha256:87d127bd3de4bd89a4f81265e5fa59cb1b199b27440175370f7417d2bc7ae868", size = 2904691, upload-time = "2024-08-18T19:48:37.769Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/4d/66/7d9e26593edda06e8cb531874633f7c2372279c3b0f46235539fe546df8b/nltk-3.9.1-py3-none-any.whl", hash = "sha256:4fa26829c5b00715afe3061398a8989dc643b92ce7dd93fb4585a70930d168a1", size = 1505442 }, + { url = "https://files.pythonhosted.org/packages/4d/66/7d9e26593edda06e8cb531874633f7c2372279c3b0f46235539fe546df8b/nltk-3.9.1-py3-none-any.whl", hash = "sha256:4fa26829c5b00715afe3061398a8989dc643b92ce7dd93fb4585a70930d168a1", size = 1505442, upload-time = "2024-08-18T19:48:21.909Z" }, ] [[package]] @@ -3127,18 +3275,18 @@ dependencies = [ { name = "llvmlite" }, { name = "numpy" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/1c/a0/e21f57604304aa03ebb8e098429222722ad99176a4f979d34af1d1ee80da/numba-0.61.2.tar.gz", hash = "sha256:8750ee147940a6637b80ecf7f95062185ad8726c8c28a2295b8ec1160a196f7d", size = 2820615 } +sdist = { url = "https://files.pythonhosted.org/packages/1c/a0/e21f57604304aa03ebb8e098429222722ad99176a4f979d34af1d1ee80da/numba-0.61.2.tar.gz", hash = "sha256:8750ee147940a6637b80ecf7f95062185ad8726c8c28a2295b8ec1160a196f7d", size = 2820615, upload-time = "2025-04-09T02:58:07.659Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/3f/97/c99d1056aed767503c228f7099dc11c402906b42a4757fec2819329abb98/numba-0.61.2-cp311-cp311-macosx_10_14_x86_64.whl", hash = "sha256:efd3db391df53aaa5cfbee189b6c910a5b471488749fd6606c3f33fc984c2ae2", size = 2775825 }, - { url = "https://files.pythonhosted.org/packages/95/9e/63c549f37136e892f006260c3e2613d09d5120672378191f2dc387ba65a2/numba-0.61.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:49c980e4171948ffebf6b9a2520ea81feed113c1f4890747ba7f59e74be84b1b", size = 2778695 }, - { url = "https://files.pythonhosted.org/packages/97/c8/8740616c8436c86c1b9a62e72cb891177d2c34c2d24ddcde4c390371bf4c/numba-0.61.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3945615cd73c2c7eba2a85ccc9c1730c21cd3958bfcf5a44302abae0fb07bb60", size = 3829227 }, - { url = "https://files.pythonhosted.org/packages/fc/06/66e99ae06507c31d15ff3ecd1f108f2f59e18b6e08662cd5f8a5853fbd18/numba-0.61.2-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:bbfdf4eca202cebade0b7d43896978e146f39398909a42941c9303f82f403a18", size = 3523422 }, - { url = "https://files.pythonhosted.org/packages/0f/a4/2b309a6a9f6d4d8cfba583401c7c2f9ff887adb5d54d8e2e130274c0973f/numba-0.61.2-cp311-cp311-win_amd64.whl", hash = "sha256:76bcec9f46259cedf888041b9886e257ae101c6268261b19fda8cfbc52bec9d1", size = 2831505 }, - { url = "https://files.pythonhosted.org/packages/b4/a0/c6b7b9c615cfa3b98c4c63f4316e3f6b3bbe2387740277006551784218cd/numba-0.61.2-cp312-cp312-macosx_10_14_x86_64.whl", hash = "sha256:34fba9406078bac7ab052efbf0d13939426c753ad72946baaa5bf9ae0ebb8dd2", size = 2776626 }, - { url = "https://files.pythonhosted.org/packages/92/4a/fe4e3c2ecad72d88f5f8cd04e7f7cff49e718398a2fac02d2947480a00ca/numba-0.61.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4ddce10009bc097b080fc96876d14c051cc0c7679e99de3e0af59014dab7dfe8", size = 2779287 }, - { url = "https://files.pythonhosted.org/packages/9a/2d/e518df036feab381c23a624dac47f8445ac55686ec7f11083655eb707da3/numba-0.61.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5b1bb509d01f23d70325d3a5a0e237cbc9544dd50e50588bc581ba860c213546", size = 3885928 }, - { url = "https://files.pythonhosted.org/packages/10/0f/23cced68ead67b75d77cfcca3df4991d1855c897ee0ff3fe25a56ed82108/numba-0.61.2-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:48a53a3de8f8793526cbe330f2a39fe9a6638efcbf11bd63f3d2f9757ae345cd", size = 3577115 }, - { url = "https://files.pythonhosted.org/packages/68/1d/ddb3e704c5a8fb90142bf9dc195c27db02a08a99f037395503bfbc1d14b3/numba-0.61.2-cp312-cp312-win_amd64.whl", hash = "sha256:97cf4f12c728cf77c9c1d7c23707e4d8fb4632b46275f8f3397de33e5877af18", size = 2831929 }, + { url = "https://files.pythonhosted.org/packages/3f/97/c99d1056aed767503c228f7099dc11c402906b42a4757fec2819329abb98/numba-0.61.2-cp311-cp311-macosx_10_14_x86_64.whl", hash = "sha256:efd3db391df53aaa5cfbee189b6c910a5b471488749fd6606c3f33fc984c2ae2", size = 2775825, upload-time = "2025-04-09T02:57:43.442Z" }, + { url = "https://files.pythonhosted.org/packages/95/9e/63c549f37136e892f006260c3e2613d09d5120672378191f2dc387ba65a2/numba-0.61.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:49c980e4171948ffebf6b9a2520ea81feed113c1f4890747ba7f59e74be84b1b", size = 2778695, upload-time = "2025-04-09T02:57:44.968Z" }, + { url = "https://files.pythonhosted.org/packages/97/c8/8740616c8436c86c1b9a62e72cb891177d2c34c2d24ddcde4c390371bf4c/numba-0.61.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3945615cd73c2c7eba2a85ccc9c1730c21cd3958bfcf5a44302abae0fb07bb60", size = 3829227, upload-time = "2025-04-09T02:57:46.63Z" }, + { url = "https://files.pythonhosted.org/packages/fc/06/66e99ae06507c31d15ff3ecd1f108f2f59e18b6e08662cd5f8a5853fbd18/numba-0.61.2-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:bbfdf4eca202cebade0b7d43896978e146f39398909a42941c9303f82f403a18", size = 3523422, upload-time = "2025-04-09T02:57:48.222Z" }, + { url = "https://files.pythonhosted.org/packages/0f/a4/2b309a6a9f6d4d8cfba583401c7c2f9ff887adb5d54d8e2e130274c0973f/numba-0.61.2-cp311-cp311-win_amd64.whl", hash = "sha256:76bcec9f46259cedf888041b9886e257ae101c6268261b19fda8cfbc52bec9d1", size = 2831505, upload-time = "2025-04-09T02:57:50.108Z" }, + { url = "https://files.pythonhosted.org/packages/b4/a0/c6b7b9c615cfa3b98c4c63f4316e3f6b3bbe2387740277006551784218cd/numba-0.61.2-cp312-cp312-macosx_10_14_x86_64.whl", hash = "sha256:34fba9406078bac7ab052efbf0d13939426c753ad72946baaa5bf9ae0ebb8dd2", size = 2776626, upload-time = "2025-04-09T02:57:51.857Z" }, + { url = "https://files.pythonhosted.org/packages/92/4a/fe4e3c2ecad72d88f5f8cd04e7f7cff49e718398a2fac02d2947480a00ca/numba-0.61.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4ddce10009bc097b080fc96876d14c051cc0c7679e99de3e0af59014dab7dfe8", size = 2779287, upload-time = "2025-04-09T02:57:53.658Z" }, + { url = "https://files.pythonhosted.org/packages/9a/2d/e518df036feab381c23a624dac47f8445ac55686ec7f11083655eb707da3/numba-0.61.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5b1bb509d01f23d70325d3a5a0e237cbc9544dd50e50588bc581ba860c213546", size = 3885928, upload-time = "2025-04-09T02:57:55.206Z" }, + { url = "https://files.pythonhosted.org/packages/10/0f/23cced68ead67b75d77cfcca3df4991d1855c897ee0ff3fe25a56ed82108/numba-0.61.2-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:48a53a3de8f8793526cbe330f2a39fe9a6638efcbf11bd63f3d2f9757ae345cd", size = 3577115, upload-time = "2025-04-09T02:57:56.818Z" }, + { url = "https://files.pythonhosted.org/packages/68/1d/ddb3e704c5a8fb90142bf9dc195c27db02a08a99f037395503bfbc1d14b3/numba-0.61.2-cp312-cp312-win_amd64.whl", hash = "sha256:97cf4f12c728cf77c9c1d7c23707e4d8fb4632b46275f8f3397de33e5877af18", size = 2831929, upload-time = "2025-04-09T02:57:58.45Z" }, ] [[package]] @@ -3148,72 +3296,55 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "numpy" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/21/67/c7415cf04ebe418193cfd6595ae03e3a64d76dac7b9c010098b39cc7992e/numexpr-2.10.2.tar.gz", hash = "sha256:b0aff6b48ebc99d2f54f27b5f73a58cb92fde650aeff1b397c71c8788b4fff1a", size = 106787 } +sdist = { url = "https://files.pythonhosted.org/packages/21/67/c7415cf04ebe418193cfd6595ae03e3a64d76dac7b9c010098b39cc7992e/numexpr-2.10.2.tar.gz", hash = "sha256:b0aff6b48ebc99d2f54f27b5f73a58cb92fde650aeff1b397c71c8788b4fff1a", size = 106787, upload-time = "2024-11-23T13:34:23.127Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/de/b7/f25d6166f92ef23737c1c90416144492a664f0a56510d90f7c6577c2cd14/numexpr-2.10.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6b360eb8d392483410fe6a3d5a7144afa298c9a0aa3e9fe193e89590b47dd477", size = 145055 }, - { url = "https://files.pythonhosted.org/packages/66/64/428361ea6415826332f38ef2dd5c3abf4e7e601f033bfc9be68b680cb765/numexpr-2.10.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d9a42f5c24880350d88933c4efee91b857c378aaea7e8b86221fff569069841e", size = 134743 }, - { url = "https://files.pythonhosted.org/packages/3f/fb/639ec91d2ea7b4a5d66e26e8ef8e06b020c8e9b9ebaf3bab7b0a9bee472e/numexpr-2.10.2-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:83fcb11988b57cc25b028a36d285287d706d1f536ebf2662ea30bd990e0de8b9", size = 410397 }, - { url = "https://files.pythonhosted.org/packages/89/5a/0f5c5b8a3a6d34eeecb30d0e2f722d50b9b38c0e175937e7c6268ffab997/numexpr-2.10.2-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4213a92efa9770bc28e3792134e27c7e5c7e97068bdfb8ba395baebbd12f991b", size = 398902 }, - { url = "https://files.pythonhosted.org/packages/a2/d5/ec734e735eba5a753efed5be3707ee7447ebd371772f8081b65a4153fb97/numexpr-2.10.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ebdbef5763ca057eea0c2b5698e4439d084a0505d9d6e94f4804f26e8890c45e", size = 1380354 }, - { url = "https://files.pythonhosted.org/packages/30/51/406e572531d817480bd612ee08239a36ee82865fea02fce569f15631f4ee/numexpr-2.10.2-cp311-cp311-win32.whl", hash = "sha256:3bf01ec502d89944e49e9c1b5cc7c7085be8ca2eb9dd46a0eafd218afbdbd5f5", size = 151938 }, - { url = "https://files.pythonhosted.org/packages/04/32/5882ed1dbd96234f327a73316a481add151ff827cfaf2ea24fb4d5ad04db/numexpr-2.10.2-cp311-cp311-win_amd64.whl", hash = "sha256:e2d0ae24b0728e4bc3f1d3f33310340d67321d36d6043f7ce26897f4f1042db0", size = 144961 }, - { url = "https://files.pythonhosted.org/packages/2b/96/d5053dea06d8298ae8052b4b049cbf8ef74998e28d57166cc27b8ae909e2/numexpr-2.10.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b5323a46e75832334f1af86da1ef6ff0add00fbacdd266250be872b438bdf2be", size = 145029 }, - { url = "https://files.pythonhosted.org/packages/3e/3c/fcd5a812ed5dda757b2d9ef2764a3e1cca6f6d1f02dbf113dc23a2c7702a/numexpr-2.10.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a42963bd4c62d8afa4f51e7974debfa39a048383f653544ab54f50a2f7ec6c42", size = 134851 }, - { url = "https://files.pythonhosted.org/packages/0a/52/0ed3b306d8c9944129bce97fec73a2caff13adbd7e1df148d546d7eb2d4d/numexpr-2.10.2-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5191ba8f2975cb9703afc04ae845a929e193498c0e8bcd408ecb147b35978470", size = 411837 }, - { url = "https://files.pythonhosted.org/packages/7d/9c/6b671dd3fb67d7e7da93cb76b7c5277743f310a216b7856bb18776bb3371/numexpr-2.10.2-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:97298b14f0105a794bea06fd9fbc5c423bd3ff4d88cbc618860b83eb7a436ad6", size = 400577 }, - { url = "https://files.pythonhosted.org/packages/ea/4d/a167d1a215fe10ce58c45109f2869fd13aa0eef66f7e8c69af68be45d436/numexpr-2.10.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f9d7805ccb6be2d3b0f7f6fad3707a09ac537811e8e9964f4074d28cb35543db", size = 1381735 }, - { url = "https://files.pythonhosted.org/packages/c1/d4/17e4434f989e4917d31cbd88a043e1c9c16958149cf43fa622987111392b/numexpr-2.10.2-cp312-cp312-win32.whl", hash = "sha256:cb845b2d4f9f8ef0eb1c9884f2b64780a85d3b5ae4eeb26ae2b0019f489cd35e", size = 152102 }, - { url = "https://files.pythonhosted.org/packages/b8/25/9ae599994076ef2a42d35ff6b0430da002647f212567851336a6c7b132d6/numexpr-2.10.2-cp312-cp312-win_amd64.whl", hash = "sha256:57b59cbb5dcce4edf09cd6ce0b57ff60312479930099ca8d944c2fac896a1ead", size = 145061 }, + { url = "https://files.pythonhosted.org/packages/de/b7/f25d6166f92ef23737c1c90416144492a664f0a56510d90f7c6577c2cd14/numexpr-2.10.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6b360eb8d392483410fe6a3d5a7144afa298c9a0aa3e9fe193e89590b47dd477", size = 145055, upload-time = "2024-11-23T13:33:36.154Z" }, + { url = "https://files.pythonhosted.org/packages/66/64/428361ea6415826332f38ef2dd5c3abf4e7e601f033bfc9be68b680cb765/numexpr-2.10.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d9a42f5c24880350d88933c4efee91b857c378aaea7e8b86221fff569069841e", size = 134743, upload-time = "2024-11-23T13:33:37.273Z" }, + { url = "https://files.pythonhosted.org/packages/3f/fb/639ec91d2ea7b4a5d66e26e8ef8e06b020c8e9b9ebaf3bab7b0a9bee472e/numexpr-2.10.2-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:83fcb11988b57cc25b028a36d285287d706d1f536ebf2662ea30bd990e0de8b9", size = 410397, upload-time = "2024-11-23T13:33:38.474Z" }, + { url = "https://files.pythonhosted.org/packages/89/5a/0f5c5b8a3a6d34eeecb30d0e2f722d50b9b38c0e175937e7c6268ffab997/numexpr-2.10.2-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4213a92efa9770bc28e3792134e27c7e5c7e97068bdfb8ba395baebbd12f991b", size = 398902, upload-time = "2024-11-23T13:33:39.802Z" }, + { url = "https://files.pythonhosted.org/packages/a2/d5/ec734e735eba5a753efed5be3707ee7447ebd371772f8081b65a4153fb97/numexpr-2.10.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ebdbef5763ca057eea0c2b5698e4439d084a0505d9d6e94f4804f26e8890c45e", size = 1380354, upload-time = "2024-11-23T13:33:41.68Z" }, + { url = "https://files.pythonhosted.org/packages/30/51/406e572531d817480bd612ee08239a36ee82865fea02fce569f15631f4ee/numexpr-2.10.2-cp311-cp311-win32.whl", hash = "sha256:3bf01ec502d89944e49e9c1b5cc7c7085be8ca2eb9dd46a0eafd218afbdbd5f5", size = 151938, upload-time = "2024-11-23T13:33:43.998Z" }, + { url = "https://files.pythonhosted.org/packages/04/32/5882ed1dbd96234f327a73316a481add151ff827cfaf2ea24fb4d5ad04db/numexpr-2.10.2-cp311-cp311-win_amd64.whl", hash = "sha256:e2d0ae24b0728e4bc3f1d3f33310340d67321d36d6043f7ce26897f4f1042db0", size = 144961, upload-time = "2024-11-23T13:33:45.329Z" }, + { url = "https://files.pythonhosted.org/packages/2b/96/d5053dea06d8298ae8052b4b049cbf8ef74998e28d57166cc27b8ae909e2/numexpr-2.10.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b5323a46e75832334f1af86da1ef6ff0add00fbacdd266250be872b438bdf2be", size = 145029, upload-time = "2024-11-23T13:33:46.892Z" }, + { url = "https://files.pythonhosted.org/packages/3e/3c/fcd5a812ed5dda757b2d9ef2764a3e1cca6f6d1f02dbf113dc23a2c7702a/numexpr-2.10.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a42963bd4c62d8afa4f51e7974debfa39a048383f653544ab54f50a2f7ec6c42", size = 134851, upload-time = "2024-11-23T13:33:47.986Z" }, + { url = "https://files.pythonhosted.org/packages/0a/52/0ed3b306d8c9944129bce97fec73a2caff13adbd7e1df148d546d7eb2d4d/numexpr-2.10.2-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5191ba8f2975cb9703afc04ae845a929e193498c0e8bcd408ecb147b35978470", size = 411837, upload-time = "2024-11-23T13:33:49.223Z" }, + { url = "https://files.pythonhosted.org/packages/7d/9c/6b671dd3fb67d7e7da93cb76b7c5277743f310a216b7856bb18776bb3371/numexpr-2.10.2-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:97298b14f0105a794bea06fd9fbc5c423bd3ff4d88cbc618860b83eb7a436ad6", size = 400577, upload-time = "2024-11-23T13:33:50.559Z" }, + { url = "https://files.pythonhosted.org/packages/ea/4d/a167d1a215fe10ce58c45109f2869fd13aa0eef66f7e8c69af68be45d436/numexpr-2.10.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f9d7805ccb6be2d3b0f7f6fad3707a09ac537811e8e9964f4074d28cb35543db", size = 1381735, upload-time = "2024-11-23T13:33:51.918Z" }, + { url = "https://files.pythonhosted.org/packages/c1/d4/17e4434f989e4917d31cbd88a043e1c9c16958149cf43fa622987111392b/numexpr-2.10.2-cp312-cp312-win32.whl", hash = "sha256:cb845b2d4f9f8ef0eb1c9884f2b64780a85d3b5ae4eeb26ae2b0019f489cd35e", size = 152102, upload-time = "2024-11-23T13:33:53.93Z" }, + { url = "https://files.pythonhosted.org/packages/b8/25/9ae599994076ef2a42d35ff6b0430da002647f212567851336a6c7b132d6/numexpr-2.10.2-cp312-cp312-win_amd64.whl", hash = "sha256:57b59cbb5dcce4edf09cd6ce0b57ff60312479930099ca8d944c2fac896a1ead", size = 145061, upload-time = "2024-11-23T13:33:55.161Z" }, ] [[package]] name = "numpy" version = "1.26.4" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/65/6e/09db70a523a96d25e115e71cc56a6f9031e7b8cd166c1ac8438307c14058/numpy-1.26.4.tar.gz", hash = "sha256:2a02aba9ed12e4ac4eb3ea9421c420301a0c6460d9830d74a9df87efa4912010", size = 15786129 } +sdist = { url = "https://files.pythonhosted.org/packages/65/6e/09db70a523a96d25e115e71cc56a6f9031e7b8cd166c1ac8438307c14058/numpy-1.26.4.tar.gz", hash = "sha256:2a02aba9ed12e4ac4eb3ea9421c420301a0c6460d9830d74a9df87efa4912010", size = 15786129, upload-time = "2024-02-06T00:26:44.495Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/11/57/baae43d14fe163fa0e4c47f307b6b2511ab8d7d30177c491960504252053/numpy-1.26.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4c66707fabe114439db9068ee468c26bbdf909cac0fb58686a42a24de1760c71", size = 20630554 }, - { url = "https://files.pythonhosted.org/packages/1a/2e/151484f49fd03944c4a3ad9c418ed193cfd02724e138ac8a9505d056c582/numpy-1.26.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:edd8b5fe47dab091176d21bb6de568acdd906d1887a4584a15a9a96a1dca06ef", size = 13997127 }, - { url = "https://files.pythonhosted.org/packages/79/ae/7e5b85136806f9dadf4878bf73cf223fe5c2636818ba3ab1c585d0403164/numpy-1.26.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7ab55401287bfec946ced39700c053796e7cc0e3acbef09993a9ad2adba6ca6e", size = 14222994 }, - { url = "https://files.pythonhosted.org/packages/3a/d0/edc009c27b406c4f9cbc79274d6e46d634d139075492ad055e3d68445925/numpy-1.26.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:666dbfb6ec68962c033a450943ded891bed2d54e6755e35e5835d63f4f6931d5", size = 18252005 }, - { url = "https://files.pythonhosted.org/packages/09/bf/2b1aaf8f525f2923ff6cfcf134ae5e750e279ac65ebf386c75a0cf6da06a/numpy-1.26.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:96ff0b2ad353d8f990b63294c8986f1ec3cb19d749234014f4e7eb0112ceba5a", size = 13885297 }, - { url = "https://files.pythonhosted.org/packages/df/a0/4e0f14d847cfc2a633a1c8621d00724f3206cfeddeb66d35698c4e2cf3d2/numpy-1.26.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:60dedbb91afcbfdc9bc0b1f3f402804070deed7392c23eb7a7f07fa857868e8a", size = 18093567 }, - { url = "https://files.pythonhosted.org/packages/d2/b7/a734c733286e10a7f1a8ad1ae8c90f2d33bf604a96548e0a4a3a6739b468/numpy-1.26.4-cp311-cp311-win32.whl", hash = "sha256:1af303d6b2210eb850fcf03064d364652b7120803a0b872f5211f5234b399f20", size = 5968812 }, - { url = "https://files.pythonhosted.org/packages/3f/6b/5610004206cf7f8e7ad91c5a85a8c71b2f2f8051a0c0c4d5916b76d6cbb2/numpy-1.26.4-cp311-cp311-win_amd64.whl", hash = "sha256:cd25bcecc4974d09257ffcd1f098ee778f7834c3ad767fe5db785be9a4aa9cb2", size = 15811913 }, - { url = "https://files.pythonhosted.org/packages/95/12/8f2020a8e8b8383ac0177dc9570aad031a3beb12e38847f7129bacd96228/numpy-1.26.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b3ce300f3644fb06443ee2222c2201dd3a89ea6040541412b8fa189341847218", size = 20335901 }, - { url = "https://files.pythonhosted.org/packages/75/5b/ca6c8bd14007e5ca171c7c03102d17b4f4e0ceb53957e8c44343a9546dcc/numpy-1.26.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:03a8c78d01d9781b28a6989f6fa1bb2c4f2d51201cf99d3dd875df6fbd96b23b", size = 13685868 }, - { url = "https://files.pythonhosted.org/packages/79/f8/97f10e6755e2a7d027ca783f63044d5b1bc1ae7acb12afe6a9b4286eac17/numpy-1.26.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9fad7dcb1aac3c7f0584a5a8133e3a43eeb2fe127f47e3632d43d677c66c102b", size = 13925109 }, - { url = "https://files.pythonhosted.org/packages/0f/50/de23fde84e45f5c4fda2488c759b69990fd4512387a8632860f3ac9cd225/numpy-1.26.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:675d61ffbfa78604709862923189bad94014bef562cc35cf61d3a07bba02a7ed", size = 17950613 }, - { url = "https://files.pythonhosted.org/packages/4c/0c/9c603826b6465e82591e05ca230dfc13376da512b25ccd0894709b054ed0/numpy-1.26.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ab47dbe5cc8210f55aa58e4805fe224dac469cde56b9f731a4c098b91917159a", size = 13572172 }, - { url = "https://files.pythonhosted.org/packages/76/8c/2ba3902e1a0fc1c74962ea9bb33a534bb05984ad7ff9515bf8d07527cadd/numpy-1.26.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:1dda2e7b4ec9dd512f84935c5f126c8bd8b9f2fc001e9f54af255e8c5f16b0e0", size = 17786643 }, - { url = "https://files.pythonhosted.org/packages/28/4a/46d9e65106879492374999e76eb85f87b15328e06bd1550668f79f7b18c6/numpy-1.26.4-cp312-cp312-win32.whl", hash = "sha256:50193e430acfc1346175fcbdaa28ffec49947a06918b7b92130744e81e640110", size = 5677803 }, - { url = "https://files.pythonhosted.org/packages/16/2e/86f24451c2d530c88daf997cb8d6ac622c1d40d19f5a031ed68a4b73a374/numpy-1.26.4-cp312-cp312-win_amd64.whl", hash = "sha256:08beddf13648eb95f8d867350f6a018a4be2e5ad54c8d8caed89ebca558b2818", size = 15517754 }, + { url = "https://files.pythonhosted.org/packages/11/57/baae43d14fe163fa0e4c47f307b6b2511ab8d7d30177c491960504252053/numpy-1.26.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4c66707fabe114439db9068ee468c26bbdf909cac0fb58686a42a24de1760c71", size = 20630554, upload-time = "2024-02-05T23:51:50.149Z" }, + { url = "https://files.pythonhosted.org/packages/1a/2e/151484f49fd03944c4a3ad9c418ed193cfd02724e138ac8a9505d056c582/numpy-1.26.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:edd8b5fe47dab091176d21bb6de568acdd906d1887a4584a15a9a96a1dca06ef", size = 13997127, upload-time = "2024-02-05T23:52:15.314Z" }, + { url = "https://files.pythonhosted.org/packages/79/ae/7e5b85136806f9dadf4878bf73cf223fe5c2636818ba3ab1c585d0403164/numpy-1.26.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7ab55401287bfec946ced39700c053796e7cc0e3acbef09993a9ad2adba6ca6e", size = 14222994, upload-time = "2024-02-05T23:52:47.569Z" }, + { url = "https://files.pythonhosted.org/packages/3a/d0/edc009c27b406c4f9cbc79274d6e46d634d139075492ad055e3d68445925/numpy-1.26.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:666dbfb6ec68962c033a450943ded891bed2d54e6755e35e5835d63f4f6931d5", size = 18252005, upload-time = "2024-02-05T23:53:15.637Z" }, + { url = "https://files.pythonhosted.org/packages/09/bf/2b1aaf8f525f2923ff6cfcf134ae5e750e279ac65ebf386c75a0cf6da06a/numpy-1.26.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:96ff0b2ad353d8f990b63294c8986f1ec3cb19d749234014f4e7eb0112ceba5a", size = 13885297, upload-time = "2024-02-05T23:53:42.16Z" }, + { url = "https://files.pythonhosted.org/packages/df/a0/4e0f14d847cfc2a633a1c8621d00724f3206cfeddeb66d35698c4e2cf3d2/numpy-1.26.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:60dedbb91afcbfdc9bc0b1f3f402804070deed7392c23eb7a7f07fa857868e8a", size = 18093567, upload-time = "2024-02-05T23:54:11.696Z" }, + { url = "https://files.pythonhosted.org/packages/d2/b7/a734c733286e10a7f1a8ad1ae8c90f2d33bf604a96548e0a4a3a6739b468/numpy-1.26.4-cp311-cp311-win32.whl", hash = "sha256:1af303d6b2210eb850fcf03064d364652b7120803a0b872f5211f5234b399f20", size = 5968812, upload-time = "2024-02-05T23:54:26.453Z" }, + { url = "https://files.pythonhosted.org/packages/3f/6b/5610004206cf7f8e7ad91c5a85a8c71b2f2f8051a0c0c4d5916b76d6cbb2/numpy-1.26.4-cp311-cp311-win_amd64.whl", hash = "sha256:cd25bcecc4974d09257ffcd1f098ee778f7834c3ad767fe5db785be9a4aa9cb2", size = 15811913, upload-time = "2024-02-05T23:54:53.933Z" }, + { url = "https://files.pythonhosted.org/packages/95/12/8f2020a8e8b8383ac0177dc9570aad031a3beb12e38847f7129bacd96228/numpy-1.26.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b3ce300f3644fb06443ee2222c2201dd3a89ea6040541412b8fa189341847218", size = 20335901, upload-time = "2024-02-05T23:55:32.801Z" }, + { url = "https://files.pythonhosted.org/packages/75/5b/ca6c8bd14007e5ca171c7c03102d17b4f4e0ceb53957e8c44343a9546dcc/numpy-1.26.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:03a8c78d01d9781b28a6989f6fa1bb2c4f2d51201cf99d3dd875df6fbd96b23b", size = 13685868, upload-time = "2024-02-05T23:55:56.28Z" }, + { url = "https://files.pythonhosted.org/packages/79/f8/97f10e6755e2a7d027ca783f63044d5b1bc1ae7acb12afe6a9b4286eac17/numpy-1.26.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9fad7dcb1aac3c7f0584a5a8133e3a43eeb2fe127f47e3632d43d677c66c102b", size = 13925109, upload-time = "2024-02-05T23:56:20.368Z" }, + { url = "https://files.pythonhosted.org/packages/0f/50/de23fde84e45f5c4fda2488c759b69990fd4512387a8632860f3ac9cd225/numpy-1.26.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:675d61ffbfa78604709862923189bad94014bef562cc35cf61d3a07bba02a7ed", size = 17950613, upload-time = "2024-02-05T23:56:56.054Z" }, + { url = "https://files.pythonhosted.org/packages/4c/0c/9c603826b6465e82591e05ca230dfc13376da512b25ccd0894709b054ed0/numpy-1.26.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ab47dbe5cc8210f55aa58e4805fe224dac469cde56b9f731a4c098b91917159a", size = 13572172, upload-time = "2024-02-05T23:57:21.56Z" }, + { url = "https://files.pythonhosted.org/packages/76/8c/2ba3902e1a0fc1c74962ea9bb33a534bb05984ad7ff9515bf8d07527cadd/numpy-1.26.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:1dda2e7b4ec9dd512f84935c5f126c8bd8b9f2fc001e9f54af255e8c5f16b0e0", size = 17786643, upload-time = "2024-02-05T23:57:56.585Z" }, + { url = "https://files.pythonhosted.org/packages/28/4a/46d9e65106879492374999e76eb85f87b15328e06bd1550668f79f7b18c6/numpy-1.26.4-cp312-cp312-win32.whl", hash = "sha256:50193e430acfc1346175fcbdaa28ffec49947a06918b7b92130744e81e640110", size = 5677803, upload-time = "2024-02-05T23:58:08.963Z" }, + { url = "https://files.pythonhosted.org/packages/16/2e/86f24451c2d530c88daf997cb8d6ac622c1d40d19f5a031ed68a4b73a374/numpy-1.26.4-cp312-cp312-win_amd64.whl", hash = "sha256:08beddf13648eb95f8d867350f6a018a4be2e5ad54c8d8caed89ebca558b2818", size = 15517754, upload-time = "2024-02-05T23:58:36.364Z" }, ] [[package]] name = "oauthlib" version = "3.2.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/6d/fa/fbf4001037904031639e6bfbfc02badfc7e12f137a8afa254df6c4c8a670/oauthlib-3.2.2.tar.gz", hash = "sha256:9859c40929662bec5d64f34d01c99e093149682a3f38915dc0655d5a633dd918", size = 177352 } +sdist = { url = "https://files.pythonhosted.org/packages/6d/fa/fbf4001037904031639e6bfbfc02badfc7e12f137a8afa254df6c4c8a670/oauthlib-3.2.2.tar.gz", hash = "sha256:9859c40929662bec5d64f34d01c99e093149682a3f38915dc0655d5a633dd918", size = 177352, upload-time = "2022-10-17T20:04:27.471Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/7e/80/cab10959dc1faead58dc8384a781dfbf93cb4d33d50988f7a69f1b7c9bbe/oauthlib-3.2.2-py3-none-any.whl", hash = "sha256:8139f29aac13e25d502680e9e19963e83f16838d48a0d71c287fe40e7067fbca", size = 151688 }, -] - -[[package]] -name = "oci" -version = "2.135.2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "certifi" }, - { name = "circuitbreaker" }, - { name = "cryptography" }, - { name = "pyopenssl" }, - { name = "python-dateutil" }, - { name = "pytz" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/68/56/b828096e323c140edce4656b2ad073d5b662c9602c89658d4a33a9573d09/oci-2.135.2.tar.gz", hash = "sha256:520f78983c5246eae80dd5ecfd05e3a565c8b98d02ef0c1b11ba1f61bcccb61d", size = 13813532 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e7/fe/7a106d278f3998ea2aca65d8772736396467efd4922c56c283604dbeec5d/oci-2.135.2-py3-none-any.whl", hash = "sha256:5213319244e1c7f108bcb417322f33f01f043fd9636d4063574039f5fdf4e4f7", size = 28290849 }, + { url = "https://files.pythonhosted.org/packages/7e/80/cab10959dc1faead58dc8384a781dfbf93cb4d33d50988f7a69f1b7c9bbe/oauthlib-3.2.2-py3-none-any.whl", hash = "sha256:8139f29aac13e25d502680e9e19963e83f16838d48a0d71c287fe40e7067fbca", size = 151688, upload-time = "2022-10-17T20:04:24.037Z" }, ] [[package]] @@ -3223,20 +3354,20 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "defusedxml" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/97/73/8ade73f6749177003f7ce3304f524774adda96e6aaab30ea79fd8fda7934/odfpy-1.4.1.tar.gz", hash = "sha256:db766a6e59c5103212f3cc92ec8dd50a0f3a02790233ed0b52148b70d3c438ec", size = 717045 } +sdist = { url = "https://files.pythonhosted.org/packages/97/73/8ade73f6749177003f7ce3304f524774adda96e6aaab30ea79fd8fda7934/odfpy-1.4.1.tar.gz", hash = "sha256:db766a6e59c5103212f3cc92ec8dd50a0f3a02790233ed0b52148b70d3c438ec", size = 717045, upload-time = "2020-01-18T16:55:48.852Z" } [[package]] name = "olefile" version = "0.47" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/69/1b/077b508e3e500e1629d366249c3ccb32f95e50258b231705c09e3c7a4366/olefile-0.47.zip", hash = "sha256:599383381a0bf3dfbd932ca0ca6515acd174ed48870cbf7fee123d698c192c1c", size = 112240 } +sdist = { url = "https://files.pythonhosted.org/packages/69/1b/077b508e3e500e1629d366249c3ccb32f95e50258b231705c09e3c7a4366/olefile-0.47.zip", hash = "sha256:599383381a0bf3dfbd932ca0ca6515acd174ed48870cbf7fee123d698c192c1c", size = 112240, upload-time = "2023-12-01T16:22:53.025Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/17/d3/b64c356a907242d719fc668b71befd73324e47ab46c8ebbbede252c154b2/olefile-0.47-py2.py3-none-any.whl", hash = "sha256:543c7da2a7adadf21214938bb79c83ea12b473a4b6ee4ad4bf854e7715e13d1f", size = 114565 }, + { url = "https://files.pythonhosted.org/packages/17/d3/b64c356a907242d719fc668b71befd73324e47ab46c8ebbbede252c154b2/olefile-0.47-py2.py3-none-any.whl", hash = "sha256:543c7da2a7adadf21214938bb79c83ea12b473a4b6ee4ad4bf854e7715e13d1f", size = 114565, upload-time = "2023-12-01T16:22:51.518Z" }, ] [[package]] name = "onnxruntime" -version = "1.21.0" +version = "1.22.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "coloredlogs" }, @@ -3247,14 +3378,14 @@ dependencies = [ { name = "sympy" }, ] wheels = [ - { url = "https://files.pythonhosted.org/packages/df/34/fd780c62b3ec9268224ada4205a5256618553b8cc26d7205d3cf8aafde47/onnxruntime-1.21.0-cp311-cp311-macosx_13_0_universal2.whl", hash = "sha256:8e16f8a79df03919810852fb46ffcc916dc87a9e9c6540a58f20c914c575678c", size = 33644022 }, - { url = "https://files.pythonhosted.org/packages/7b/df/622594b43d1a8644ac4d947f52e34a0e813b3d76a62af34667e343c34e98/onnxruntime-1.21.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7f9156cf6f8ee133d07a751e6518cf6f84ed37fbf8243156bd4a2c4ee6e073c8", size = 14159570 }, - { url = "https://files.pythonhosted.org/packages/f9/49/1e916e8d1d957a1432c1662ef2e94f3e4afab31f6f1888fb80d4da374a5d/onnxruntime-1.21.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8a5d09815a9e209fa0cb20c2985b34ab4daeba7aea94d0f96b8751eb10403201", size = 16001965 }, - { url = "https://files.pythonhosted.org/packages/09/05/15ec0933f8543f85743571da9b3bf4397f71792c9d375f01f61c6019f130/onnxruntime-1.21.0-cp311-cp311-win_amd64.whl", hash = "sha256:1d970dff1e2fa4d9c53f2787b3b7d0005596866e6a31997b41169017d1362dd0", size = 11759373 }, - { url = "https://files.pythonhosted.org/packages/ff/21/593c9bc56002a6d1ea7c2236f4a648e081ec37c8d51db2383a9e83a63325/onnxruntime-1.21.0-cp312-cp312-macosx_13_0_universal2.whl", hash = "sha256:893d67c68ca9e7a58202fa8d96061ed86a5815b0925b5a97aef27b8ba246a20b", size = 33658780 }, - { url = "https://files.pythonhosted.org/packages/4a/b4/33ec675a8ac150478091262824413e5d4acc359e029af87f9152e7c1c092/onnxruntime-1.21.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:37b7445c920a96271a8dfa16855e258dc5599235b41c7bbde0d262d55bcc105f", size = 14159975 }, - { url = "https://files.pythonhosted.org/packages/8b/08/eead6895ed83b56711ca6c0d31d82f109401b9937558b425509e497d6fb4/onnxruntime-1.21.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9a04aafb802c1e5573ba4552f8babcb5021b041eb4cfa802c9b7644ca3510eca", size = 16019285 }, - { url = "https://files.pythonhosted.org/packages/77/39/e83d56e3c215713b5263cb4d4f0c69e3964bba11634233d8ae04fc7e6bf3/onnxruntime-1.21.0-cp312-cp312-win_amd64.whl", hash = "sha256:7f801318476cd7003d636a5b392f7a37c08b6c8d2f829773f3c3887029e03f32", size = 11760975 }, + { url = "https://files.pythonhosted.org/packages/7a/08/c008711d1b92ff1272f4fea0fbee57723171f161d42e5c680625535280af/onnxruntime-1.22.0-cp311-cp311-macosx_13_0_universal2.whl", hash = "sha256:8d6725c5b9a681d8fe72f2960c191a96c256367887d076b08466f52b4e0991df", size = 34282151, upload-time = "2025-05-09T20:25:59.246Z" }, + { url = "https://files.pythonhosted.org/packages/3e/8b/22989f6b59bc4ad1324f07a945c80b9ab825f0a581ad7a6064b93716d9b7/onnxruntime-1.22.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fef17d665a917866d1f68f09edc98223b9a27e6cb167dec69da4c66484ad12fd", size = 14446302, upload-time = "2025-05-09T20:25:44.299Z" }, + { url = "https://files.pythonhosted.org/packages/7a/d5/aa83d084d05bc8f6cf8b74b499c77431ffd6b7075c761ec48ec0c161a47f/onnxruntime-1.22.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b978aa63a9a22095479c38371a9b359d4c15173cbb164eaad5f2cd27d666aa65", size = 16393496, upload-time = "2025-05-09T20:26:11.588Z" }, + { url = "https://files.pythonhosted.org/packages/89/a5/1c6c10322201566015183b52ef011dfa932f5dd1b278de8d75c3b948411d/onnxruntime-1.22.0-cp311-cp311-win_amd64.whl", hash = "sha256:03d3ef7fb11adf154149d6e767e21057e0e577b947dd3f66190b212528e1db31", size = 12691517, upload-time = "2025-05-12T21:26:13.354Z" }, + { url = "https://files.pythonhosted.org/packages/4d/de/9162872c6e502e9ac8c99a98a8738b2fab408123d11de55022ac4f92562a/onnxruntime-1.22.0-cp312-cp312-macosx_13_0_universal2.whl", hash = "sha256:f3c0380f53c1e72a41b3f4d6af2ccc01df2c17844072233442c3a7e74851ab97", size = 34298046, upload-time = "2025-05-09T20:26:02.399Z" }, + { url = "https://files.pythonhosted.org/packages/03/79/36f910cd9fc96b444b0e728bba14607016079786adf032dae61f7c63b4aa/onnxruntime-1.22.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c8601128eaef79b636152aea76ae6981b7c9fc81a618f584c15d78d42b310f1c", size = 14443220, upload-time = "2025-05-09T20:25:47.078Z" }, + { url = "https://files.pythonhosted.org/packages/8c/60/16d219b8868cc8e8e51a68519873bdb9f5f24af080b62e917a13fff9989b/onnxruntime-1.22.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6964a975731afc19dc3418fad8d4e08c48920144ff590149429a5ebe0d15fb3c", size = 16406377, upload-time = "2025-05-09T20:26:14.478Z" }, + { url = "https://files.pythonhosted.org/packages/36/b4/3f1c71ce1d3d21078a6a74c5483bfa2b07e41a8d2b8fb1e9993e6a26d8d3/onnxruntime-1.22.0-cp312-cp312-win_amd64.whl", hash = "sha256:c0d534a43d1264d1273c2d4f00a5a588fa98d21117a3345b7104fa0bbcaadb9a", size = 12692233, upload-time = "2025-05-12T21:26:16.963Z" }, ] [[package]] @@ -3271,22 +3402,25 @@ dependencies = [ { name = "tqdm" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d9/cf/61e71ce64cf0a38f029da0f9a5f10c9fa0e69a7a977b537126dac50adfea/openai-1.61.1.tar.gz", hash = "sha256:ce1851507218209961f89f3520e06726c0aa7d0512386f0f977e3ac3e4f2472e", size = 350784 } +sdist = { url = "https://files.pythonhosted.org/packages/d9/cf/61e71ce64cf0a38f029da0f9a5f10c9fa0e69a7a977b537126dac50adfea/openai-1.61.1.tar.gz", hash = "sha256:ce1851507218209961f89f3520e06726c0aa7d0512386f0f977e3ac3e4f2472e", size = 350784, upload-time = "2025-02-05T14:34:15.873Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/9a/b6/2e2a011b2dc27a6711376808b4cd8c922c476ea0f1420b39892117fa8563/openai-1.61.1-py3-none-any.whl", hash = "sha256:72b0826240ce26026ac2cd17951691f046e5be82ad122d20a8e1b30ca18bd11e", size = 463126 }, + { url = "https://files.pythonhosted.org/packages/9a/b6/2e2a011b2dc27a6711376808b4cd8c922c476ea0f1420b39892117fa8563/openai-1.61.1-py3-none-any.whl", hash = "sha256:72b0826240ce26026ac2cd17951691f046e5be82ad122d20a8e1b30ca18bd11e", size = 463126, upload-time = "2025-02-05T14:34:13.643Z" }, ] [[package]] name = "opendal" -version = "0.45.17" +version = "0.45.20" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a3/4d/4d1d72f1cef4085e38c32200ce7bf2461a36722a394b6b590e8ce39ae1f2/opendal-0.45.17.tar.gz", hash = "sha256:9985840087a097ead8ba5a1146ecfc1b337f62baa606ffe715b74ce523d192ee", size = 920824 } +sdist = { url = "https://files.pythonhosted.org/packages/2f/3f/927dfe1349ae58b9238b8eafba747af648d660a9425f486dda01a10f0b78/opendal-0.45.20.tar.gz", hash = "sha256:9f6f90d9e9f9d6e9e5a34aa7729169ef34d2f1869ad1e01ddc39b1c0ce0c9405", size = 990267, upload-time = "2025-05-26T07:02:11.819Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/11/ff/e983119c720c516e2ec836e140d773be975cf0c5ad5ed716bdcdb7926592/opendal-0.45.17-cp311-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:6f7905b8506103ea4ffca7ffa3c4f9d0154981727660fe8891385c236d7ebff2", size = 26892411 }, - { url = "https://files.pythonhosted.org/packages/0d/62/90dcc7b93471814dbf649d3a835f1d728578482cb3f6e4222d668e73830a/opendal-0.45.17-cp311-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4ad896559bb40482b6e17b10b5f61771734c86ea8d42733bc0d815122fdf30d1", size = 12902718 }, - { url = "https://files.pythonhosted.org/packages/63/3e/f8fdcded658d35abba8453150e620ee3cc5ba38369b4dc7bdac4dcb81d53/opendal-0.45.17-cp311-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4e78c8bc29c373b943cfc18f0ac2aba5b5f7252491237598e93b2babb4d30a13", size = 14472878 }, - { url = "https://files.pythonhosted.org/packages/0d/d9/74e33620e94aa3d0217e5f56552f1ff1b78662561a6aff47e109bb07308d/opendal-0.45.17-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:cf35aa4c133e3ad0f35e2583faa40c6e567ecb66f6ae99949ef613c11e5ce55e", size = 13454577 }, - { url = "https://files.pythonhosted.org/packages/fa/06/7876d8b8a9c67eefc64e984099d7dcc524c171c9925fb816b9acc061d4b2/opendal-0.45.17-cp311-abi3-win_amd64.whl", hash = "sha256:f48415f883b7469dc273c4580bd5adfd0a93d8cec9750596142a690ef31bb93e", size = 15125153 }, + { url = "https://files.pythonhosted.org/packages/84/77/6427e16b8630f0cc71f4a1b01648ed3264f1e04f1f6d9b5d09e5c6a4dd2f/opendal-0.45.20-cp311-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:35acdd8001e4a741532834fdbff3020ffb10b40028bb49fbe93c4f8197d66d8c", size = 26910966, upload-time = "2025-05-26T07:01:24.987Z" }, + { url = "https://files.pythonhosted.org/packages/12/1f/83e415334739f1ab4dba55cdd349abf0b66612249055afb422a354b96ac8/opendal-0.45.20-cp311-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:629bfe8d384364bced6cbeb01f49b99779fa5151c68048a1869ff645ddcfcb25", size = 13002770, upload-time = "2025-05-26T07:01:30.385Z" }, + { url = "https://files.pythonhosted.org/packages/49/94/c5de6ed54a02d7413636c2ccefa71d8dd09c2ada1cd6ecab202feb1fdeda/opendal-0.45.20-cp311-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d12cc5ac7e441fb93d86d1673112d9fb08580fc3226f864434f4a56a72efec53", size = 14387218, upload-time = "2025-05-26T07:01:33.017Z" }, + { url = "https://files.pythonhosted.org/packages/c6/83/713a1e1de8cbbd69af50e26644bbdeef3c1068b89f442417376fa3c0f591/opendal-0.45.20-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:45a3adae1f473052234fc4054a6f210df3ded9aff10db8d545d0a37eff3b13cc", size = 13424302, upload-time = "2025-05-26T07:01:36.417Z" }, + { url = "https://files.pythonhosted.org/packages/c7/78/c9651e753aaf6eb61887ca372a3f9c2ae57dae03c3159d24deaf018c26dc/opendal-0.45.20-cp311-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:d8947857052c85a4b0e251d50e23f5f68f0cdd9e509e32e614a5e4b2fc7424c4", size = 13622483, upload-time = "2025-05-26T07:01:38.886Z" }, + { url = "https://files.pythonhosted.org/packages/3c/9d/5d8c20c0fc93df5e349e5694167de30afdc54c5755704cc64764a6cbb309/opendal-0.45.20-cp311-abi3-musllinux_1_1_armv7l.whl", hash = "sha256:891d2f9114efeef648973049ed15e56477e8feb9e48b540bd8d6105ea22a253c", size = 13320229, upload-time = "2025-05-26T07:01:41.965Z" }, + { url = "https://files.pythonhosted.org/packages/21/39/05262f748a2085522e0c85f03eab945589313dc9caedc002872c39162776/opendal-0.45.20-cp311-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:539de9b825f6783d6289d88c0c9ac5415daa4d892d761e3540c565bda51e8997", size = 14574280, upload-time = "2025-05-26T07:01:44.413Z" }, + { url = "https://files.pythonhosted.org/packages/74/83/cc7c6de29b0a7585cd445258d174ca204d37729c3874ad08e515b0bf331c/opendal-0.45.20-cp311-abi3-win_amd64.whl", hash = "sha256:145efd56aa33b493d5b652c3e4f5ae5097ab69d38c132d80f108e9f5c1e4d863", size = 14929888, upload-time = "2025-05-26T07:01:46.929Z" }, ] [[package]] @@ -3296,9 +3430,9 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "et-xmlfile" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/3d/f9/88d94a75de065ea32619465d2f77b29a0469500e99012523b91cc4141cd1/openpyxl-3.1.5.tar.gz", hash = "sha256:cf0e3cf56142039133628b5acffe8ef0c12bc902d2aadd3e0fe5878dc08d1050", size = 186464 } +sdist = { url = "https://files.pythonhosted.org/packages/3d/f9/88d94a75de065ea32619465d2f77b29a0469500e99012523b91cc4141cd1/openpyxl-3.1.5.tar.gz", hash = "sha256:cf0e3cf56142039133628b5acffe8ef0c12bc902d2aadd3e0fe5878dc08d1050", size = 186464, upload-time = "2024-06-28T14:03:44.161Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c0/da/977ded879c29cbd04de313843e76868e6e13408a94ed6b987245dc7c8506/openpyxl-3.1.5-py2.py3-none-any.whl", hash = "sha256:5282c12b107bffeef825f4617dc029afaf41d0ea60823bbb665ef3079dc79de2", size = 250910 }, + { url = "https://files.pythonhosted.org/packages/c0/da/977ded879c29cbd04de313843e76868e6e13408a94ed6b987245dc7c8506/openpyxl-3.1.5-py2.py3-none-any.whl", hash = "sha256:5282c12b107bffeef825f4617dc029afaf41d0ea60823bbb665ef3079dc79de2", size = 250910, upload-time = "2024-06-28T14:03:41.161Z" }, ] [[package]] @@ -3312,9 +3446,9 @@ dependencies = [ { name = "six" }, { name = "urllib3" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/e4/dc/acb182db6bb0c71f1e6e41c49260e01d68e52a03efb64e44aed3cc7f483f/opensearch-py-2.4.0.tar.gz", hash = "sha256:7eba2b6ed2ddcf33225bfebfba2aee026877838cc39f760ec80f27827308cc4b", size = 182924 } +sdist = { url = "https://files.pythonhosted.org/packages/e4/dc/acb182db6bb0c71f1e6e41c49260e01d68e52a03efb64e44aed3cc7f483f/opensearch-py-2.4.0.tar.gz", hash = "sha256:7eba2b6ed2ddcf33225bfebfba2aee026877838cc39f760ec80f27827308cc4b", size = 182924, upload-time = "2023-11-15T21:41:37.329Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c1/98/178aacf07ece7f95d1948352778702898d57c286053813deb20ebb409923/opensearch_py-2.4.0-py2.py3-none-any.whl", hash = "sha256:316077235437c8ceac970232261f3393c65fb92a80f33c5b106f50f1dab24fd9", size = 258405 }, + { url = "https://files.pythonhosted.org/packages/c1/98/178aacf07ece7f95d1948352778702898d57c286053813deb20ebb409923/opensearch_py-2.4.0-py2.py3-none-any.whl", hash = "sha256:316077235437c8ceac970232261f3393c65fb92a80f33c5b106f50f1dab24fd9", size = 258405, upload-time = "2023-11-15T21:41:35.59Z" }, ] [[package]] @@ -3325,9 +3459,9 @@ dependencies = [ { name = "deprecated" }, { name = "importlib-metadata" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c9/83/93114b6de85a98963aec218a51509a52ed3f8de918fe91eb0f7299805c3f/opentelemetry_api-1.27.0.tar.gz", hash = "sha256:ed673583eaa5f81b5ce5e86ef7cdaf622f88ef65f0b9aab40b843dcae5bef342", size = 62693 } +sdist = { url = "https://files.pythonhosted.org/packages/c9/83/93114b6de85a98963aec218a51509a52ed3f8de918fe91eb0f7299805c3f/opentelemetry_api-1.27.0.tar.gz", hash = "sha256:ed673583eaa5f81b5ce5e86ef7cdaf622f88ef65f0b9aab40b843dcae5bef342", size = 62693, upload-time = "2024-08-28T21:35:31.445Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/fb/1f/737dcdbc9fea2fa96c1b392ae47275165a7c641663fbb08a8d252968eed2/opentelemetry_api-1.27.0-py3-none-any.whl", hash = "sha256:953d5871815e7c30c81b56d910c707588000fff7a3ca1c73e6531911d53065e7", size = 63970 }, + { url = "https://files.pythonhosted.org/packages/fb/1f/737dcdbc9fea2fa96c1b392ae47275165a7c641663fbb08a8d252968eed2/opentelemetry_api-1.27.0-py3-none-any.whl", hash = "sha256:953d5871815e7c30c81b56d910c707588000fff7a3ca1c73e6531911d53065e7", size = 63970, upload-time = "2024-08-28T21:35:00.598Z" }, ] [[package]] @@ -3339,9 +3473,9 @@ dependencies = [ { name = "opentelemetry-instrumentation" }, { name = "opentelemetry-sdk" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/f4/09/423e17c439ed24c45110affe84aad886a536b7871a42637d2ad14a179b47/opentelemetry_distro-0.48b0.tar.gz", hash = "sha256:5cb15915780ac4972583286a56683d43bd4ca95371d72f5f3f179c8b0b2ddc91", size = 2556 } +sdist = { url = "https://files.pythonhosted.org/packages/f4/09/423e17c439ed24c45110affe84aad886a536b7871a42637d2ad14a179b47/opentelemetry_distro-0.48b0.tar.gz", hash = "sha256:5cb15915780ac4972583286a56683d43bd4ca95371d72f5f3f179c8b0b2ddc91", size = 2556, upload-time = "2024-08-28T21:27:40.455Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/82/cf/fa9a5fe954f1942e03b319ae0e319ebc93d9f984b548bcd9b3f232a1434d/opentelemetry_distro-0.48b0-py3-none-any.whl", hash = "sha256:b2f8fce114325b020769af3b9bf503efb8af07efc190bd1b9deac7843171664a", size = 3321 }, + { url = "https://files.pythonhosted.org/packages/82/cf/fa9a5fe954f1942e03b319ae0e319ebc93d9f984b548bcd9b3f232a1434d/opentelemetry_distro-0.48b0-py3-none-any.whl", hash = "sha256:b2f8fce114325b020769af3b9bf503efb8af07efc190bd1b9deac7843171664a", size = 3321, upload-time = "2024-08-28T21:26:26.584Z" }, ] [[package]] @@ -3352,9 +3486,9 @@ dependencies = [ { name = "opentelemetry-exporter-otlp-proto-grpc" }, { name = "opentelemetry-exporter-otlp-proto-http" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/fc/d3/8156cc14e8f4573a3572ee7f30badc7aabd02961a09acc72ab5f2c789ef1/opentelemetry_exporter_otlp-1.27.0.tar.gz", hash = "sha256:4a599459e623868cc95d933c301199c2367e530f089750e115599fccd67cb2a1", size = 6166 } +sdist = { url = "https://files.pythonhosted.org/packages/fc/d3/8156cc14e8f4573a3572ee7f30badc7aabd02961a09acc72ab5f2c789ef1/opentelemetry_exporter_otlp-1.27.0.tar.gz", hash = "sha256:4a599459e623868cc95d933c301199c2367e530f089750e115599fccd67cb2a1", size = 6166, upload-time = "2024-08-28T21:35:33.746Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/59/6d/95e1fc2c8d945a734db32e87a5aa7a804f847c1657a21351df9338bd1c9c/opentelemetry_exporter_otlp-1.27.0-py3-none-any.whl", hash = "sha256:7688791cbdd951d71eb6445951d1cfbb7b6b2d7ee5948fac805d404802931145", size = 7001 }, + { url = "https://files.pythonhosted.org/packages/59/6d/95e1fc2c8d945a734db32e87a5aa7a804f847c1657a21351df9338bd1c9c/opentelemetry_exporter_otlp-1.27.0-py3-none-any.whl", hash = "sha256:7688791cbdd951d71eb6445951d1cfbb7b6b2d7ee5948fac805d404802931145", size = 7001, upload-time = "2024-08-28T21:35:04.02Z" }, ] [[package]] @@ -3364,9 +3498,9 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "opentelemetry-proto" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/cd/2e/7eaf4ba595fb5213cf639c9158dfb64aacb2e4c7d74bfa664af89fa111f4/opentelemetry_exporter_otlp_proto_common-1.27.0.tar.gz", hash = "sha256:159d27cf49f359e3798c4c3eb8da6ef4020e292571bd8c5604a2a573231dd5c8", size = 17860 } +sdist = { url = "https://files.pythonhosted.org/packages/cd/2e/7eaf4ba595fb5213cf639c9158dfb64aacb2e4c7d74bfa664af89fa111f4/opentelemetry_exporter_otlp_proto_common-1.27.0.tar.gz", hash = "sha256:159d27cf49f359e3798c4c3eb8da6ef4020e292571bd8c5604a2a573231dd5c8", size = 17860, upload-time = "2024-08-28T21:35:34.896Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/41/27/4610ab3d9bb3cde4309b6505f98b3aabca04a26aa480aa18cede23149837/opentelemetry_exporter_otlp_proto_common-1.27.0-py3-none-any.whl", hash = "sha256:675db7fffcb60946f3a5c43e17d1168a3307a94a930ecf8d2ea1f286f3d4f79a", size = 17848 }, + { url = "https://files.pythonhosted.org/packages/41/27/4610ab3d9bb3cde4309b6505f98b3aabca04a26aa480aa18cede23149837/opentelemetry_exporter_otlp_proto_common-1.27.0-py3-none-any.whl", hash = "sha256:675db7fffcb60946f3a5c43e17d1168a3307a94a930ecf8d2ea1f286f3d4f79a", size = 17848, upload-time = "2024-08-28T21:35:05.412Z" }, ] [[package]] @@ -3382,9 +3516,9 @@ dependencies = [ { name = "opentelemetry-proto" }, { name = "opentelemetry-sdk" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a1/d0/c1e375b292df26e0ffebf194e82cd197e4c26cc298582bda626ce3ce74c5/opentelemetry_exporter_otlp_proto_grpc-1.27.0.tar.gz", hash = "sha256:af6f72f76bcf425dfb5ad11c1a6d6eca2863b91e63575f89bb7b4b55099d968f", size = 26244 } +sdist = { url = "https://files.pythonhosted.org/packages/a1/d0/c1e375b292df26e0ffebf194e82cd197e4c26cc298582bda626ce3ce74c5/opentelemetry_exporter_otlp_proto_grpc-1.27.0.tar.gz", hash = "sha256:af6f72f76bcf425dfb5ad11c1a6d6eca2863b91e63575f89bb7b4b55099d968f", size = 26244, upload-time = "2024-08-28T21:35:36.314Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/8d/80/32217460c2c64c0568cea38410124ff680a9b65f6732867bbf857c4d8626/opentelemetry_exporter_otlp_proto_grpc-1.27.0-py3-none-any.whl", hash = "sha256:56b5bbd5d61aab05e300d9d62a6b3c134827bbd28d0b12f2649c2da368006c9e", size = 18541 }, + { url = "https://files.pythonhosted.org/packages/8d/80/32217460c2c64c0568cea38410124ff680a9b65f6732867bbf857c4d8626/opentelemetry_exporter_otlp_proto_grpc-1.27.0-py3-none-any.whl", hash = "sha256:56b5bbd5d61aab05e300d9d62a6b3c134827bbd28d0b12f2649c2da368006c9e", size = 18541, upload-time = "2024-08-28T21:35:06.493Z" }, ] [[package]] @@ -3400,9 +3534,9 @@ dependencies = [ { name = "opentelemetry-sdk" }, { name = "requests" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/31/0a/f05c55e8913bf58a033583f2580a0ec31a5f4cf2beacc9e286dcb74d6979/opentelemetry_exporter_otlp_proto_http-1.27.0.tar.gz", hash = "sha256:2103479092d8eb18f61f3fbff084f67cc7f2d4a7d37e75304b8b56c1d09ebef5", size = 15059 } +sdist = { url = "https://files.pythonhosted.org/packages/31/0a/f05c55e8913bf58a033583f2580a0ec31a5f4cf2beacc9e286dcb74d6979/opentelemetry_exporter_otlp_proto_http-1.27.0.tar.gz", hash = "sha256:2103479092d8eb18f61f3fbff084f67cc7f2d4a7d37e75304b8b56c1d09ebef5", size = 15059, upload-time = "2024-08-28T21:35:37.079Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/2d/8d/4755884afc0b1db6000527cac0ca17273063b6142c773ce4ecd307a82e72/opentelemetry_exporter_otlp_proto_http-1.27.0-py3-none-any.whl", hash = "sha256:688027575c9da42e179a69fe17e2d1eba9b14d81de8d13553a21d3114f3b4d75", size = 17203 }, + { url = "https://files.pythonhosted.org/packages/2d/8d/4755884afc0b1db6000527cac0ca17273063b6142c773ce4ecd307a82e72/opentelemetry_exporter_otlp_proto_http-1.27.0-py3-none-any.whl", hash = "sha256:688027575c9da42e179a69fe17e2d1eba9b14d81de8d13553a21d3114f3b4d75", size = 17203, upload-time = "2024-08-28T21:35:08.141Z" }, ] [[package]] @@ -3414,9 +3548,9 @@ dependencies = [ { name = "setuptools" }, { name = "wrapt" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/04/0e/d9394839af5d55c8feb3b22cd11138b953b49739b20678ca96289e30f904/opentelemetry_instrumentation-0.48b0.tar.gz", hash = "sha256:94929685d906380743a71c3970f76b5f07476eea1834abd5dd9d17abfe23cc35", size = 24724 } +sdist = { url = "https://files.pythonhosted.org/packages/04/0e/d9394839af5d55c8feb3b22cd11138b953b49739b20678ca96289e30f904/opentelemetry_instrumentation-0.48b0.tar.gz", hash = "sha256:94929685d906380743a71c3970f76b5f07476eea1834abd5dd9d17abfe23cc35", size = 24724, upload-time = "2024-08-28T21:27:42.82Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/0a/7f/405c41d4f359121376c9d5117dcf68149b8122d3f6c718996d037bd4d800/opentelemetry_instrumentation-0.48b0-py3-none-any.whl", hash = "sha256:a69750dc4ba6a5c3eb67986a337185a25b739966d80479befe37b546fc870b44", size = 29449 }, + { url = "https://files.pythonhosted.org/packages/0a/7f/405c41d4f359121376c9d5117dcf68149b8122d3f6c718996d037bd4d800/opentelemetry_instrumentation-0.48b0-py3-none-any.whl", hash = "sha256:a69750dc4ba6a5c3eb67986a337185a25b739966d80479befe37b546fc870b44", size = 29449, upload-time = "2024-08-28T21:26:31.288Z" }, ] [[package]] @@ -3430,9 +3564,9 @@ dependencies = [ { name = "opentelemetry-semantic-conventions" }, { name = "opentelemetry-util-http" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/44/ac/fd3d40bab3234ec3f5c052a815100676baaae1832fa1067935f11e5c59c6/opentelemetry_instrumentation_asgi-0.48b0.tar.gz", hash = "sha256:04c32174b23c7fa72ddfe192dad874954968a6a924608079af9952964ecdf785", size = 23435 } +sdist = { url = "https://files.pythonhosted.org/packages/44/ac/fd3d40bab3234ec3f5c052a815100676baaae1832fa1067935f11e5c59c6/opentelemetry_instrumentation_asgi-0.48b0.tar.gz", hash = "sha256:04c32174b23c7fa72ddfe192dad874954968a6a924608079af9952964ecdf785", size = 23435, upload-time = "2024-08-28T21:27:47.276Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/db/74/a0e0d38622856597dd8e630f2bd793760485eb165708e11b8be1696bbb5a/opentelemetry_instrumentation_asgi-0.48b0-py3-none-any.whl", hash = "sha256:ddb1b5fc800ae66e85a4e2eca4d9ecd66367a8c7b556169d9e7b57e10676e44d", size = 15958 }, + { url = "https://files.pythonhosted.org/packages/db/74/a0e0d38622856597dd8e630f2bd793760485eb165708e11b8be1696bbb5a/opentelemetry_instrumentation_asgi-0.48b0-py3-none-any.whl", hash = "sha256:ddb1b5fc800ae66e85a4e2eca4d9ecd66367a8c7b556169d9e7b57e10676e44d", size = 15958, upload-time = "2024-08-28T21:26:38.139Z" }, ] [[package]] @@ -3444,9 +3578,9 @@ dependencies = [ { name = "opentelemetry-instrumentation" }, { name = "opentelemetry-semantic-conventions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/42/68/72975eff50cc22d8f65f96c425a2e8844f91488e78ffcfb603ac7cee0e5a/opentelemetry_instrumentation_celery-0.48b0.tar.gz", hash = "sha256:1d33aa6c4a1e6c5d17a64215245208a96e56c9d07611685dbae09a557704af26", size = 14445 } +sdist = { url = "https://files.pythonhosted.org/packages/42/68/72975eff50cc22d8f65f96c425a2e8844f91488e78ffcfb603ac7cee0e5a/opentelemetry_instrumentation_celery-0.48b0.tar.gz", hash = "sha256:1d33aa6c4a1e6c5d17a64215245208a96e56c9d07611685dbae09a557704af26", size = 14445, upload-time = "2024-08-28T21:27:56.392Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/28/59/f09e8f9f596d375fd86b7677751525bbc485c8cc8c5388e39786a3d3b968/opentelemetry_instrumentation_celery-0.48b0-py3-none-any.whl", hash = "sha256:c1904e38cc58fb2a33cd657d6e296285c5ffb0dca3f164762f94b905e5abc88e", size = 13697 }, + { url = "https://files.pythonhosted.org/packages/28/59/f09e8f9f596d375fd86b7677751525bbc485c8cc8c5388e39786a3d3b968/opentelemetry_instrumentation_celery-0.48b0-py3-none-any.whl", hash = "sha256:c1904e38cc58fb2a33cd657d6e296285c5ffb0dca3f164762f94b905e5abc88e", size = 13697, upload-time = "2024-08-28T21:26:50.01Z" }, ] [[package]] @@ -3460,9 +3594,9 @@ dependencies = [ { name = "opentelemetry-semantic-conventions" }, { name = "opentelemetry-util-http" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/58/20/43477da5850ef2cd3792715d442aecd051e885e0603b6ee5783b2104ba8f/opentelemetry_instrumentation_fastapi-0.48b0.tar.gz", hash = "sha256:21a72563ea412c0b535815aeed75fc580240f1f02ebc72381cfab672648637a2", size = 18497 } +sdist = { url = "https://files.pythonhosted.org/packages/58/20/43477da5850ef2cd3792715d442aecd051e885e0603b6ee5783b2104ba8f/opentelemetry_instrumentation_fastapi-0.48b0.tar.gz", hash = "sha256:21a72563ea412c0b535815aeed75fc580240f1f02ebc72381cfab672648637a2", size = 18497, upload-time = "2024-08-28T21:28:01.14Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ee/50/745ab075a3041b7a5f29a579d2c28eaad54f64b4589d8f9fd364c62cf0f3/opentelemetry_instrumentation_fastapi-0.48b0-py3-none-any.whl", hash = "sha256:afeb820a59e139d3e5d96619600f11ce0187658b8ae9e3480857dd790bc024f2", size = 11777 }, + { url = "https://files.pythonhosted.org/packages/ee/50/745ab075a3041b7a5f29a579d2c28eaad54f64b4589d8f9fd364c62cf0f3/opentelemetry_instrumentation_fastapi-0.48b0-py3-none-any.whl", hash = "sha256:afeb820a59e139d3e5d96619600f11ce0187658b8ae9e3480857dd790bc024f2", size = 11777, upload-time = "2024-08-28T21:26:57.457Z" }, ] [[package]] @@ -3478,9 +3612,9 @@ dependencies = [ { name = "opentelemetry-util-http" }, { name = "packaging" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ed/2f/5c3af780a69f9ba78445fe0e5035c41f67281a31b08f3c3e7ec460bda726/opentelemetry_instrumentation_flask-0.48b0.tar.gz", hash = "sha256:e03a34428071aebf4864ea6c6a564acef64f88c13eb3818e64ea90da61266c3d", size = 19196 } +sdist = { url = "https://files.pythonhosted.org/packages/ed/2f/5c3af780a69f9ba78445fe0e5035c41f67281a31b08f3c3e7ec460bda726/opentelemetry_instrumentation_flask-0.48b0.tar.gz", hash = "sha256:e03a34428071aebf4864ea6c6a564acef64f88c13eb3818e64ea90da61266c3d", size = 19196, upload-time = "2024-08-28T21:28:01.986Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/78/3d/fcde4f8f0bf9fa1ee73a12304fa538076fb83fe0a2ae966ab0f0b7da5109/opentelemetry_instrumentation_flask-0.48b0-py3-none-any.whl", hash = "sha256:26b045420b9d76e85493b1c23fcf27517972423480dc6cf78fd6924248ba5808", size = 14588 }, + { url = "https://files.pythonhosted.org/packages/78/3d/fcde4f8f0bf9fa1ee73a12304fa538076fb83fe0a2ae966ab0f0b7da5109/opentelemetry_instrumentation_flask-0.48b0-py3-none-any.whl", hash = "sha256:26b045420b9d76e85493b1c23fcf27517972423480dc6cf78fd6924248ba5808", size = 14588, upload-time = "2024-08-28T21:26:58.504Z" }, ] [[package]] @@ -3494,9 +3628,9 @@ dependencies = [ { name = "packaging" }, { name = "wrapt" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/4c/77/3fcebbca8bd729da50dc2130d8ca869a235aa5483a85ef06c5dc8643476b/opentelemetry_instrumentation_sqlalchemy-0.48b0.tar.gz", hash = "sha256:dbf2d5a755b470e64e5e2762b56f8d56313787e4c7d71a87fe25c33f48eb3493", size = 13194 } +sdist = { url = "https://files.pythonhosted.org/packages/4c/77/3fcebbca8bd729da50dc2130d8ca869a235aa5483a85ef06c5dc8643476b/opentelemetry_instrumentation_sqlalchemy-0.48b0.tar.gz", hash = "sha256:dbf2d5a755b470e64e5e2762b56f8d56313787e4c7d71a87fe25c33f48eb3493", size = 13194, upload-time = "2024-08-28T21:28:18.122Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e1/84/4b6f1e9e9f83a52d966e91963f5a8424edc4a3d5ea32854c96c2d1618284/opentelemetry_instrumentation_sqlalchemy-0.48b0-py3-none-any.whl", hash = "sha256:625848a34aa5770cb4b1dcdbd95afce4307a0230338711101325261d739f391f", size = 13360 }, + { url = "https://files.pythonhosted.org/packages/e1/84/4b6f1e9e9f83a52d966e91963f5a8424edc4a3d5ea32854c96c2d1618284/opentelemetry_instrumentation_sqlalchemy-0.48b0-py3-none-any.whl", hash = "sha256:625848a34aa5770cb4b1dcdbd95afce4307a0230338711101325261d739f391f", size = 13360, upload-time = "2024-08-28T21:27:22.102Z" }, ] [[package]] @@ -3509,9 +3643,9 @@ dependencies = [ { name = "opentelemetry-semantic-conventions" }, { name = "opentelemetry-util-http" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/de/a5/f45cdfba18f22aefd2378eac8c07c1f8c9656d6bf7ce315ced48c67f3437/opentelemetry_instrumentation_wsgi-0.48b0.tar.gz", hash = "sha256:1a1e752367b0df4397e0b835839225ef5c2c3c053743a261551af13434fc4d51", size = 17974 } +sdist = { url = "https://files.pythonhosted.org/packages/de/a5/f45cdfba18f22aefd2378eac8c07c1f8c9656d6bf7ce315ced48c67f3437/opentelemetry_instrumentation_wsgi-0.48b0.tar.gz", hash = "sha256:1a1e752367b0df4397e0b835839225ef5c2c3c053743a261551af13434fc4d51", size = 17974, upload-time = "2024-08-28T21:28:24.902Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/fb/87/fa420007e0ba7e8cd43799ab204717ab515f000236fa2726a6be3299efdd/opentelemetry_instrumentation_wsgi-0.48b0-py3-none-any.whl", hash = "sha256:c6051124d741972090fe94b2fa302555e1e2a22e9cdda32dd39ed49a5b34e0c6", size = 13691 }, + { url = "https://files.pythonhosted.org/packages/fb/87/fa420007e0ba7e8cd43799ab204717ab515f000236fa2726a6be3299efdd/opentelemetry_instrumentation_wsgi-0.48b0-py3-none-any.whl", hash = "sha256:c6051124d741972090fe94b2fa302555e1e2a22e9cdda32dd39ed49a5b34e0c6", size = 13691, upload-time = "2024-08-28T21:27:33.257Z" }, ] [[package]] @@ -3522,9 +3656,9 @@ dependencies = [ { name = "deprecated" }, { name = "opentelemetry-api" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/53/a3/3ceeb5ff5a1906371834d5c594e24e5b84f35528d219054833deca4ac44c/opentelemetry_propagator_b3-1.27.0.tar.gz", hash = "sha256:39377b6aa619234e08fbc6db79bf880aff36d7e2761efa9afa28b78d5937308f", size = 9590 } +sdist = { url = "https://files.pythonhosted.org/packages/53/a3/3ceeb5ff5a1906371834d5c594e24e5b84f35528d219054833deca4ac44c/opentelemetry_propagator_b3-1.27.0.tar.gz", hash = "sha256:39377b6aa619234e08fbc6db79bf880aff36d7e2761efa9afa28b78d5937308f", size = 9590, upload-time = "2024-08-28T21:35:43.971Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/03/3f/75ba77b8d9938bae575bc457a5c56ca2246ff5367b54c7d4252a31d1c91f/opentelemetry_propagator_b3-1.27.0-py3-none-any.whl", hash = "sha256:1dd75e9801ba02e870df3830097d35771a64c123127c984d9b05c352a35aa9cc", size = 8899 }, + { url = "https://files.pythonhosted.org/packages/03/3f/75ba77b8d9938bae575bc457a5c56ca2246ff5367b54c7d4252a31d1c91f/opentelemetry_propagator_b3-1.27.0-py3-none-any.whl", hash = "sha256:1dd75e9801ba02e870df3830097d35771a64c123127c984d9b05c352a35aa9cc", size = 8899, upload-time = "2024-08-28T21:35:18.317Z" }, ] [[package]] @@ -3534,9 +3668,9 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "protobuf" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/9a/59/959f0beea798ae0ee9c979b90f220736fbec924eedbefc60ca581232e659/opentelemetry_proto-1.27.0.tar.gz", hash = "sha256:33c9345d91dafd8a74fc3d7576c5a38f18b7fdf8d02983ac67485386132aedd6", size = 34749 } +sdist = { url = "https://files.pythonhosted.org/packages/9a/59/959f0beea798ae0ee9c979b90f220736fbec924eedbefc60ca581232e659/opentelemetry_proto-1.27.0.tar.gz", hash = "sha256:33c9345d91dafd8a74fc3d7576c5a38f18b7fdf8d02983ac67485386132aedd6", size = 34749, upload-time = "2024-08-28T21:35:45.839Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/94/56/3d2d826834209b19a5141eed717f7922150224d1a982385d19a9444cbf8d/opentelemetry_proto-1.27.0-py3-none-any.whl", hash = "sha256:b133873de5581a50063e1e4b29cdcf0c5e253a8c2d8dc1229add20a4c3830ace", size = 52464 }, + { url = "https://files.pythonhosted.org/packages/94/56/3d2d826834209b19a5141eed717f7922150224d1a982385d19a9444cbf8d/opentelemetry_proto-1.27.0-py3-none-any.whl", hash = "sha256:b133873de5581a50063e1e4b29cdcf0c5e253a8c2d8dc1229add20a4c3830ace", size = 52464, upload-time = "2024-08-28T21:35:21.434Z" }, ] [[package]] @@ -3548,9 +3682,9 @@ dependencies = [ { name = "opentelemetry-semantic-conventions" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/0d/9a/82a6ac0f06590f3d72241a587cb8b0b751bd98728e896cc4cbd4847248e6/opentelemetry_sdk-1.27.0.tar.gz", hash = "sha256:d525017dea0ccce9ba4e0245100ec46ecdc043f2d7b8315d56b19aff0904fa6f", size = 145019 } +sdist = { url = "https://files.pythonhosted.org/packages/0d/9a/82a6ac0f06590f3d72241a587cb8b0b751bd98728e896cc4cbd4847248e6/opentelemetry_sdk-1.27.0.tar.gz", hash = "sha256:d525017dea0ccce9ba4e0245100ec46ecdc043f2d7b8315d56b19aff0904fa6f", size = 145019, upload-time = "2024-08-28T21:35:46.708Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c1/bd/a6602e71e315055d63b2ff07172bd2d012b4cba2d4e00735d74ba42fc4d6/opentelemetry_sdk-1.27.0-py3-none-any.whl", hash = "sha256:365f5e32f920faf0fd9e14fdfd92c086e317eaa5f860edba9cdc17a380d9197d", size = 110505 }, + { url = "https://files.pythonhosted.org/packages/c1/bd/a6602e71e315055d63b2ff07172bd2d012b4cba2d4e00735d74ba42fc4d6/opentelemetry_sdk-1.27.0-py3-none-any.whl", hash = "sha256:365f5e32f920faf0fd9e14fdfd92c086e317eaa5f860edba9cdc17a380d9197d", size = 110505, upload-time = "2024-08-28T21:35:24.769Z" }, ] [[package]] @@ -3561,27 +3695,29 @@ dependencies = [ { name = "deprecated" }, { name = "opentelemetry-api" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/0a/89/1724ad69f7411772446067cdfa73b598694c8c91f7f8c922e344d96d81f9/opentelemetry_semantic_conventions-0.48b0.tar.gz", hash = "sha256:12d74983783b6878162208be57c9effcb89dc88691c64992d70bb89dc00daa1a", size = 89445 } +sdist = { url = "https://files.pythonhosted.org/packages/0a/89/1724ad69f7411772446067cdfa73b598694c8c91f7f8c922e344d96d81f9/opentelemetry_semantic_conventions-0.48b0.tar.gz", hash = "sha256:12d74983783b6878162208be57c9effcb89dc88691c64992d70bb89dc00daa1a", size = 89445, upload-time = "2024-08-28T21:35:47.673Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b7/7a/4f0063dbb0b6c971568291a8bc19a4ca70d3c185db2d956230dd67429dfc/opentelemetry_semantic_conventions-0.48b0-py3-none-any.whl", hash = "sha256:a0de9f45c413a8669788a38569c7e0a11ce6ce97861a628cca785deecdc32a1f", size = 149685 }, + { url = "https://files.pythonhosted.org/packages/b7/7a/4f0063dbb0b6c971568291a8bc19a4ca70d3c185db2d956230dd67429dfc/opentelemetry_semantic_conventions-0.48b0-py3-none-any.whl", hash = "sha256:a0de9f45c413a8669788a38569c7e0a11ce6ce97861a628cca785deecdc32a1f", size = 149685, upload-time = "2024-08-28T21:35:25.983Z" }, ] [[package]] name = "opentelemetry-util-http" version = "0.48b0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d6/d7/185c494754340e0a3928fd39fde2616ee78f2c9d66253affaad62d5b7935/opentelemetry_util_http-0.48b0.tar.gz", hash = "sha256:60312015153580cc20f322e5cdc3d3ecad80a71743235bdb77716e742814623c", size = 7863 } +sdist = { url = "https://files.pythonhosted.org/packages/d6/d7/185c494754340e0a3928fd39fde2616ee78f2c9d66253affaad62d5b7935/opentelemetry_util_http-0.48b0.tar.gz", hash = "sha256:60312015153580cc20f322e5cdc3d3ecad80a71743235bdb77716e742814623c", size = 7863, upload-time = "2024-08-28T21:28:27.266Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ad/2e/36097c0a4d0115b8c7e377c90bab7783ac183bc5cb4071308f8959454311/opentelemetry_util_http-0.48b0-py3-none-any.whl", hash = "sha256:76f598af93aab50328d2a69c786beaedc8b6a7770f7a818cc307eb353debfffb", size = 6946 }, + { url = "https://files.pythonhosted.org/packages/ad/2e/36097c0a4d0115b8c7e377c90bab7783ac183bc5cb4071308f8959454311/opentelemetry_util_http-0.48b0-py3-none-any.whl", hash = "sha256:76f598af93aab50328d2a69c786beaedc8b6a7770f7a818cc307eb353debfffb", size = 6946, upload-time = "2024-08-28T21:27:37.975Z" }, ] [[package]] name = "opik" -version = "1.3.5" +version = "1.7.29" source = { registry = "https://pypi.org/simple" } dependencies = [ + { name = "boto3-stubs", extra = ["bedrock-runtime"] }, { name = "click" }, { name = "httpx" }, + { name = "jinja2" }, { name = "levenshtein" }, { name = "litellm" }, { name = "openai" }, @@ -3589,13 +3725,26 @@ dependencies = [ { name = "pydantic-settings" }, { name = "pytest" }, { name = "rich" }, + { name = "sentry-sdk" }, { name = "tenacity" }, { name = "tqdm" }, { name = "uuid6" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/df/64/99cb7cf300620b092f78d052487966b7c9132a191388e297396b670350ca/opik-1.3.5.tar.gz", hash = "sha256:943e4b636b70e5781f7a6f40b33fadda0935b57ecad0997f195ce909956b68d7", size = 169802 } +sdist = { url = "https://files.pythonhosted.org/packages/48/94/52faf9277891bfa77dc4bac80b5d88d78103d8fad7f6a6c9495ae083e4da/opik-1.7.29.tar.gz", hash = "sha256:5a13692b233d90663a32cbab452937bf8b6fdc2d516c671c102c3beb34301b64", size = 300856, upload-time = "2025-06-02T11:11:27.955Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/fb/be/670ff4b921d529320ccae7fe95751c171fe6ee4708d109b744f83309bec7/opik-1.3.5-py3-none-any.whl", hash = "sha256:c6a195b33851959b8e96ac78fe211b6157288eddc03fa8bfbd1ef53424b702dc", size = 330096 }, + { url = "https://files.pythonhosted.org/packages/2a/43/462b700030c70f525eebf4bc63a226ae46109eca598cd59db081de9a8b21/opik-1.7.29-py3-none-any.whl", hash = "sha256:3eb893e7b612d0b799682060193e4f2eebf55b65811203c4fe46af04cf84fa8c", size = 566635, upload-time = "2025-06-02T11:11:26.225Z" }, +] + +[[package]] +name = "optype" +version = "0.10.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/11/11/5bc1ad8e4dd339783daec5299c9162eaa80ad072aaa1256561b336152981/optype-0.10.0.tar.gz", hash = "sha256:2b89a1b8b48f9d6dd8c4dd4f59e22557185c81823c6e2bfc43c4819776d5a7ca", size = 95630, upload-time = "2025-05-28T22:43:18.799Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2b/98/7f97864d5b6801bc63c24e72c45a58417c344c563ca58134a43249ce8afa/optype-0.10.0-py3-none-any.whl", hash = "sha256:7e9ccc329fb65c326c6bd62c30c2ba03b694c28c378a96c2bcdd18a084f2c96b", size = 83825, upload-time = "2025-05-28T22:43:16.772Z" }, ] [[package]] @@ -3605,53 +3754,56 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cryptography" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/bf/39/712f797b75705c21148fa1d98651f63c2e5cc6876e509a0a9e2f5b406572/oracledb-3.0.0.tar.gz", hash = "sha256:64dc86ee5c032febc556798b06e7b000ef6828bb0252084f6addacad3363db85", size = 840431 } +sdist = { url = "https://files.pythonhosted.org/packages/bf/39/712f797b75705c21148fa1d98651f63c2e5cc6876e509a0a9e2f5b406572/oracledb-3.0.0.tar.gz", hash = "sha256:64dc86ee5c032febc556798b06e7b000ef6828bb0252084f6addacad3363db85", size = 840431, upload-time = "2025-03-03T19:36:12.223Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/fa/bf/d872c4b3fc15cd3261fe0ea72b21d181700c92dbc050160e161654987062/oracledb-3.0.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:52daa9141c63dfa75c07d445e9bb7f69f43bfb3c5a173ecc48c798fe50288d26", size = 4312963 }, - { url = "https://files.pythonhosted.org/packages/b1/ea/01ee29e76a610a53bb34fdc1030f04b7669c3f80b25f661e07850fc6160e/oracledb-3.0.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:af98941789df4c6aaaf4338f5b5f6b7f2c8c3fe6f8d6a9382f177f350868747a", size = 2661536 }, - { url = "https://files.pythonhosted.org/packages/3d/8e/ad380e34a46819224423b4773e58c350bc6269643c8969604097ced8c3bc/oracledb-3.0.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9812bb48865aaec35d73af54cd1746679f2a8a13cbd1412ab371aba2e39b3943", size = 2867461 }, - { url = "https://files.pythonhosted.org/packages/96/09/ecc4384a27fd6e1e4de824ae9c160e4ad3aaebdaade5b4bdcf56a4d1ff63/oracledb-3.0.0-cp311-cp311-win32.whl", hash = "sha256:6c27fe0de64f2652e949eb05b3baa94df9b981a4a45fa7f8a991e1afb450c8e2", size = 1752046 }, - { url = "https://files.pythonhosted.org/packages/62/e8/f34bde24050c6e55eeba46b23b2291f2dd7fd272fa8b322dcbe71be55778/oracledb-3.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:f922709672002f0b40997456f03a95f03e5712a86c61159951c5ce09334325e0", size = 2101210 }, - { url = "https://files.pythonhosted.org/packages/6f/fc/24590c3a3d41e58494bd3c3b447a62835138e5f9b243d9f8da0cfb5da8dc/oracledb-3.0.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:acd0e747227dea01bebe627b07e958bf36588a337539f24db629dc3431d3f7eb", size = 4351993 }, - { url = "https://files.pythonhosted.org/packages/b7/b6/1f3b0b7bb94d53e8857d77b2e8dbdf6da091dd7e377523e24b79dac4fd71/oracledb-3.0.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f8b402f77c22af031cd0051aea2472ecd0635c1b452998f511aa08b7350c90a4", size = 2532640 }, - { url = "https://files.pythonhosted.org/packages/72/1a/1815f6c086ab49c00921cf155ff5eede5267fb29fcec37cb246339a5ce4d/oracledb-3.0.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:378a27782e9a37918bd07a5a1427a77cb6f777d0a5a8eac9c070d786f50120ef", size = 2765949 }, - { url = "https://files.pythonhosted.org/packages/33/8d/208900f8d372909792ee70b2daad3f7361181e55f2217c45ed9dff658b54/oracledb-3.0.0-cp312-cp312-win32.whl", hash = "sha256:54a28c2cb08316a527cd1467740a63771cc1c1164697c932aa834c0967dc4efc", size = 1709373 }, - { url = "https://files.pythonhosted.org/packages/0c/5e/c21754f19c896102793c3afec2277e2180aa7d505e4d7fcca24b52d14e4f/oracledb-3.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:8289bad6d103ce42b140e40576cf0c81633e344d56e2d738b539341eacf65624", size = 2056452 }, + { url = "https://files.pythonhosted.org/packages/fa/bf/d872c4b3fc15cd3261fe0ea72b21d181700c92dbc050160e161654987062/oracledb-3.0.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:52daa9141c63dfa75c07d445e9bb7f69f43bfb3c5a173ecc48c798fe50288d26", size = 4312963, upload-time = "2025-03-03T19:36:32.576Z" }, + { url = "https://files.pythonhosted.org/packages/b1/ea/01ee29e76a610a53bb34fdc1030f04b7669c3f80b25f661e07850fc6160e/oracledb-3.0.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:af98941789df4c6aaaf4338f5b5f6b7f2c8c3fe6f8d6a9382f177f350868747a", size = 2661536, upload-time = "2025-03-03T19:36:34.904Z" }, + { url = "https://files.pythonhosted.org/packages/3d/8e/ad380e34a46819224423b4773e58c350bc6269643c8969604097ced8c3bc/oracledb-3.0.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9812bb48865aaec35d73af54cd1746679f2a8a13cbd1412ab371aba2e39b3943", size = 2867461, upload-time = "2025-03-03T19:36:36.508Z" }, + { url = "https://files.pythonhosted.org/packages/96/09/ecc4384a27fd6e1e4de824ae9c160e4ad3aaebdaade5b4bdcf56a4d1ff63/oracledb-3.0.0-cp311-cp311-win32.whl", hash = "sha256:6c27fe0de64f2652e949eb05b3baa94df9b981a4a45fa7f8a991e1afb450c8e2", size = 1752046, upload-time = "2025-03-03T19:36:38.313Z" }, + { url = "https://files.pythonhosted.org/packages/62/e8/f34bde24050c6e55eeba46b23b2291f2dd7fd272fa8b322dcbe71be55778/oracledb-3.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:f922709672002f0b40997456f03a95f03e5712a86c61159951c5ce09334325e0", size = 2101210, upload-time = "2025-03-03T19:36:40.669Z" }, + { url = "https://files.pythonhosted.org/packages/6f/fc/24590c3a3d41e58494bd3c3b447a62835138e5f9b243d9f8da0cfb5da8dc/oracledb-3.0.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:acd0e747227dea01bebe627b07e958bf36588a337539f24db629dc3431d3f7eb", size = 4351993, upload-time = "2025-03-03T19:36:42.577Z" }, + { url = "https://files.pythonhosted.org/packages/b7/b6/1f3b0b7bb94d53e8857d77b2e8dbdf6da091dd7e377523e24b79dac4fd71/oracledb-3.0.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f8b402f77c22af031cd0051aea2472ecd0635c1b452998f511aa08b7350c90a4", size = 2532640, upload-time = "2025-03-03T19:36:45.066Z" }, + { url = "https://files.pythonhosted.org/packages/72/1a/1815f6c086ab49c00921cf155ff5eede5267fb29fcec37cb246339a5ce4d/oracledb-3.0.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:378a27782e9a37918bd07a5a1427a77cb6f777d0a5a8eac9c070d786f50120ef", size = 2765949, upload-time = "2025-03-03T19:36:47.47Z" }, + { url = "https://files.pythonhosted.org/packages/33/8d/208900f8d372909792ee70b2daad3f7361181e55f2217c45ed9dff658b54/oracledb-3.0.0-cp312-cp312-win32.whl", hash = "sha256:54a28c2cb08316a527cd1467740a63771cc1c1164697c932aa834c0967dc4efc", size = 1709373, upload-time = "2025-03-03T19:36:49.67Z" }, + { url = "https://files.pythonhosted.org/packages/0c/5e/c21754f19c896102793c3afec2277e2180aa7d505e4d7fcca24b52d14e4f/oracledb-3.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:8289bad6d103ce42b140e40576cf0c81633e344d56e2d738b539341eacf65624", size = 2056452, upload-time = "2025-03-03T19:36:51.363Z" }, ] [[package]] name = "orjson" -version = "3.10.16" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/98/c7/03913cc4332174071950acf5b0735463e3f63760c80585ef369270c2b372/orjson-3.10.16.tar.gz", hash = "sha256:d2aaa5c495e11d17b9b93205f5fa196737ee3202f000aaebf028dc9a73750f10", size = 5410415 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/97/29/43f91a5512b5d2535594438eb41c5357865fd5e64dec745d90a588820c75/orjson-3.10.16-cp311-cp311-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:44fcbe1a1884f8bc9e2e863168b0f84230c3d634afe41c678637d2728ea8e739", size = 249180 }, - { url = "https://files.pythonhosted.org/packages/0c/36/2a72d55e266473c19a86d97b7363bb8bf558ab450f75205689a287d5ce61/orjson-3.10.16-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:78177bf0a9d0192e0b34c3d78bcff7fe21d1b5d84aeb5ebdfe0dbe637b885225", size = 138510 }, - { url = "https://files.pythonhosted.org/packages/bb/ad/f86d6f55c1a68b57ff6ea7966bce5f4e5163f2e526ddb7db9fc3c2c8d1c4/orjson-3.10.16-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:12824073a010a754bb27330cad21d6e9b98374f497f391b8707752b96f72e741", size = 132373 }, - { url = "https://files.pythonhosted.org/packages/5e/8b/d18f2711493a809f3082a88fda89342bc8e16767743b909cd3c34989fba3/orjson-3.10.16-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ddd41007e56284e9867864aa2f29f3136bb1dd19a49ca43c0b4eda22a579cf53", size = 136773 }, - { url = "https://files.pythonhosted.org/packages/a1/dc/ce025f002f8e0749e3f057c4d773a4d4de32b7b4c1fc5a50b429e7532586/orjson-3.10.16-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0877c4d35de639645de83666458ca1f12560d9fa7aa9b25d8bb8f52f61627d14", size = 138029 }, - { url = "https://files.pythonhosted.org/packages/0e/1b/cf9df85852b91160029d9f26014230366a2b4deb8cc51fabe68e250a8c1a/orjson-3.10.16-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9a09a539e9cc3beead3e7107093b4ac176d015bec64f811afb5965fce077a03c", size = 142677 }, - { url = "https://files.pythonhosted.org/packages/92/18/5b1e1e995bffad49dc4311a0bdfd874bc6f135fd20f0e1f671adc2c9910e/orjson-3.10.16-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:31b98bc9b40610fec971d9a4d67bb2ed02eec0a8ae35f8ccd2086320c28526ca", size = 132800 }, - { url = "https://files.pythonhosted.org/packages/d6/eb/467f25b580e942fcca1344adef40633b7f05ac44a65a63fc913f9a805d58/orjson-3.10.16-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0ce243f5a8739f3a18830bc62dc2e05b69a7545bafd3e3249f86668b2bcd8e50", size = 135451 }, - { url = "https://files.pythonhosted.org/packages/8d/4b/9d10888038975cb375982e9339d9495bac382d5c976c500b8d6f2c8e2e4e/orjson-3.10.16-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:64792c0025bae049b3074c6abe0cf06f23c8e9f5a445f4bab31dc5ca23dbf9e1", size = 412358 }, - { url = "https://files.pythonhosted.org/packages/3b/e2/cfbcfcc4fbe619e0ca9bdbbfccb2d62b540bbfe41e0ee77d44a628594f59/orjson-3.10.16-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:ea53f7e68eec718b8e17e942f7ca56c6bd43562eb19db3f22d90d75e13f0431d", size = 152772 }, - { url = "https://files.pythonhosted.org/packages/b9/d6/627a1b00569be46173007c11dde3da4618c9bfe18409325b0e3e2a82fe29/orjson-3.10.16-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a741ba1a9488c92227711bde8c8c2b63d7d3816883268c808fbeada00400c164", size = 137225 }, - { url = "https://files.pythonhosted.org/packages/0a/7b/a73c67b505021af845b9f05c7c848793258ea141fa2058b52dd9b067c2b4/orjson-3.10.16-cp311-cp311-win32.whl", hash = "sha256:c7ed2c61bb8226384c3fdf1fb01c51b47b03e3f4536c985078cccc2fd19f1619", size = 141733 }, - { url = "https://files.pythonhosted.org/packages/f4/22/5e8217c48d68c0adbfb181e749d6a733761074e598b083c69a1383d18147/orjson-3.10.16-cp311-cp311-win_amd64.whl", hash = "sha256:cd67d8b3e0e56222a2e7b7f7da9031e30ecd1fe251c023340b9f12caca85ab60", size = 133784 }, - { url = "https://files.pythonhosted.org/packages/5d/15/67ce9d4c959c83f112542222ea3b9209c1d424231d71d74c4890ea0acd2b/orjson-3.10.16-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:6d3444abbfa71ba21bb042caa4b062535b122248259fdb9deea567969140abca", size = 249325 }, - { url = "https://files.pythonhosted.org/packages/da/2c/1426b06f30a1b9ada74b6f512c1ddf9d2760f53f61cdb59efeb9ad342133/orjson-3.10.16-cp312-cp312-macosx_15_0_arm64.whl", hash = "sha256:30245c08d818fdcaa48b7d5b81499b8cae09acabb216fe61ca619876b128e184", size = 133621 }, - { url = "https://files.pythonhosted.org/packages/9e/88/18d26130954bc73bee3be10f95371ea1dfb8679e0e2c46b0f6d8c6289402/orjson-3.10.16-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0ba1d0baa71bf7579a4ccdcf503e6f3098ef9542106a0eca82395898c8a500a", size = 138270 }, - { url = "https://files.pythonhosted.org/packages/4f/f9/6d8b64fcd58fae072e80ee7981be8ba0d7c26ace954e5cd1d027fc80518f/orjson-3.10.16-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:eb0beefa5ef3af8845f3a69ff2a4aa62529b5acec1cfe5f8a6b4141033fd46ef", size = 132346 }, - { url = "https://files.pythonhosted.org/packages/16/3f/2513fd5bc786f40cd12af569c23cae6381aeddbefeed2a98f0a666eb5d0d/orjson-3.10.16-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6daa0e1c9bf2e030e93c98394de94506f2a4d12e1e9dadd7c53d5e44d0f9628e", size = 136845 }, - { url = "https://files.pythonhosted.org/packages/6d/42/b0e7b36720f5ab722b48e8ccf06514d4f769358dd73c51abd8728ef58d0b/orjson-3.10.16-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9da9019afb21e02410ef600e56666652b73eb3e4d213a0ec919ff391a7dd52aa", size = 138078 }, - { url = "https://files.pythonhosted.org/packages/a3/a8/d220afb8a439604be74fc755dbc740bded5ed14745ca536b304ed32eb18a/orjson-3.10.16-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:daeb3a1ee17b69981d3aae30c3b4e786b0f8c9e6c71f2b48f1aef934f63f38f4", size = 142712 }, - { url = "https://files.pythonhosted.org/packages/8c/88/7e41e9883c00f84f92fe357a8371edae816d9d7ef39c67b5106960c20389/orjson-3.10.16-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80fed80eaf0e20a31942ae5d0728849862446512769692474be5e6b73123a23b", size = 133136 }, - { url = "https://files.pythonhosted.org/packages/e9/ca/61116095307ad0be828ea26093febaf59e38596d84a9c8d765c3c5e4934f/orjson-3.10.16-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:73390ed838f03764540a7bdc4071fe0123914c2cc02fb6abf35182d5fd1b7a42", size = 135258 }, - { url = "https://files.pythonhosted.org/packages/dc/1b/09493cf7d801505f094c9295f79c98c1e0af2ac01c7ed8d25b30fcb19ada/orjson-3.10.16-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:a22bba012a0c94ec02a7768953020ab0d3e2b884760f859176343a36c01adf87", size = 412326 }, - { url = "https://files.pythonhosted.org/packages/ea/02/125d7bbd7f7a500190ddc8ae5d2d3c39d87ed3ed28f5b37cfe76962c678d/orjson-3.10.16-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:5385bbfdbc90ff5b2635b7e6bebf259652db00a92b5e3c45b616df75b9058e88", size = 152800 }, - { url = "https://files.pythonhosted.org/packages/f9/09/7658a9e3e793d5b3b00598023e0fb6935d0e7bbb8ff72311c5415a8ce677/orjson-3.10.16-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:02c6279016346e774dd92625d46c6c40db687b8a0d685aadb91e26e46cc33e1e", size = 137516 }, - { url = "https://files.pythonhosted.org/packages/29/87/32b7a4831e909d347278101a48d4cf9f3f25901b2295e7709df1651f65a1/orjson-3.10.16-cp312-cp312-win32.whl", hash = "sha256:7ca55097a11426db80f79378e873a8c51f4dde9ffc22de44850f9696b7eb0e8c", size = 141759 }, - { url = "https://files.pythonhosted.org/packages/35/ce/81a27e7b439b807bd393585271364cdddf50dc281fc57c4feef7ccb186a6/orjson-3.10.16-cp312-cp312-win_amd64.whl", hash = "sha256:86d127efdd3f9bf5f04809b70faca1e6836556ea3cc46e662b44dab3fe71f3d6", size = 133944 }, +version = "3.10.18" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/81/0b/fea456a3ffe74e70ba30e01ec183a9b26bec4d497f61dcfce1b601059c60/orjson-3.10.18.tar.gz", hash = "sha256:e8da3947d92123eda795b68228cafe2724815621fe35e8e320a9e9593a4bcd53", size = 5422810, upload-time = "2025-04-29T23:30:08.423Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/97/c7/c54a948ce9a4278794f669a353551ce7db4ffb656c69a6e1f2264d563e50/orjson-3.10.18-cp311-cp311-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:e0a183ac3b8e40471e8d843105da6fbe7c070faab023be3b08188ee3f85719b8", size = 248929, upload-time = "2025-04-29T23:28:30.716Z" }, + { url = "https://files.pythonhosted.org/packages/9e/60/a9c674ef1dd8ab22b5b10f9300e7e70444d4e3cda4b8258d6c2488c32143/orjson-3.10.18-cp311-cp311-macosx_15_0_arm64.whl", hash = "sha256:5ef7c164d9174362f85238d0cd4afdeeb89d9e523e4651add6a5d458d6f7d42d", size = 133364, upload-time = "2025-04-29T23:28:32.392Z" }, + { url = "https://files.pythonhosted.org/packages/c1/4e/f7d1bdd983082216e414e6d7ef897b0c2957f99c545826c06f371d52337e/orjson-3.10.18-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:afd14c5d99cdc7bf93f22b12ec3b294931518aa019e2a147e8aa2f31fd3240f7", size = 136995, upload-time = "2025-04-29T23:28:34.024Z" }, + { url = "https://files.pythonhosted.org/packages/17/89/46b9181ba0ea251c9243b0c8ce29ff7c9796fa943806a9c8b02592fce8ea/orjson-3.10.18-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7b672502323b6cd133c4af6b79e3bea36bad2d16bca6c1f645903fce83909a7a", size = 132894, upload-time = "2025-04-29T23:28:35.318Z" }, + { url = "https://files.pythonhosted.org/packages/ca/dd/7bce6fcc5b8c21aef59ba3c67f2166f0a1a9b0317dcca4a9d5bd7934ecfd/orjson-3.10.18-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:51f8c63be6e070ec894c629186b1c0fe798662b8687f3d9fdfa5e401c6bd7679", size = 137016, upload-time = "2025-04-29T23:28:36.674Z" }, + { url = "https://files.pythonhosted.org/packages/1c/4a/b8aea1c83af805dcd31c1f03c95aabb3e19a016b2a4645dd822c5686e94d/orjson-3.10.18-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3f9478ade5313d724e0495d167083c6f3be0dd2f1c9c8a38db9a9e912cdaf947", size = 138290, upload-time = "2025-04-29T23:28:38.3Z" }, + { url = "https://files.pythonhosted.org/packages/36/d6/7eb05c85d987b688707f45dcf83c91abc2251e0dd9fb4f7be96514f838b1/orjson-3.10.18-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:187aefa562300a9d382b4b4eb9694806e5848b0cedf52037bb5c228c61bb66d4", size = 142829, upload-time = "2025-04-29T23:28:39.657Z" }, + { url = "https://files.pythonhosted.org/packages/d2/78/ddd3ee7873f2b5f90f016bc04062713d567435c53ecc8783aab3a4d34915/orjson-3.10.18-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9da552683bc9da222379c7a01779bddd0ad39dd699dd6300abaf43eadee38334", size = 132805, upload-time = "2025-04-29T23:28:40.969Z" }, + { url = "https://files.pythonhosted.org/packages/8c/09/c8e047f73d2c5d21ead9c180203e111cddeffc0848d5f0f974e346e21c8e/orjson-3.10.18-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:e450885f7b47a0231979d9c49b567ed1c4e9f69240804621be87c40bc9d3cf17", size = 135008, upload-time = "2025-04-29T23:28:42.284Z" }, + { url = "https://files.pythonhosted.org/packages/0c/4b/dccbf5055ef8fb6eda542ab271955fc1f9bf0b941a058490293f8811122b/orjson-3.10.18-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:5e3c9cc2ba324187cd06287ca24f65528f16dfc80add48dc99fa6c836bb3137e", size = 413419, upload-time = "2025-04-29T23:28:43.673Z" }, + { url = "https://files.pythonhosted.org/packages/8a/f3/1eac0c5e2d6d6790bd2025ebfbefcbd37f0d097103d76f9b3f9302af5a17/orjson-3.10.18-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:50ce016233ac4bfd843ac5471e232b865271d7d9d44cf9d33773bcd883ce442b", size = 153292, upload-time = "2025-04-29T23:28:45.573Z" }, + { url = "https://files.pythonhosted.org/packages/1f/b4/ef0abf64c8f1fabf98791819ab502c2c8c1dc48b786646533a93637d8999/orjson-3.10.18-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b3ceff74a8f7ffde0b2785ca749fc4e80e4315c0fd887561144059fb1c138aa7", size = 137182, upload-time = "2025-04-29T23:28:47.229Z" }, + { url = "https://files.pythonhosted.org/packages/a9/a3/6ea878e7b4a0dc5c888d0370d7752dcb23f402747d10e2257478d69b5e63/orjson-3.10.18-cp311-cp311-win32.whl", hash = "sha256:fdba703c722bd868c04702cac4cb8c6b8ff137af2623bc0ddb3b3e6a2c8996c1", size = 142695, upload-time = "2025-04-29T23:28:48.564Z" }, + { url = "https://files.pythonhosted.org/packages/79/2a/4048700a3233d562f0e90d5572a849baa18ae4e5ce4c3ba6247e4ece57b0/orjson-3.10.18-cp311-cp311-win_amd64.whl", hash = "sha256:c28082933c71ff4bc6ccc82a454a2bffcef6e1d7379756ca567c772e4fb3278a", size = 134603, upload-time = "2025-04-29T23:28:50.442Z" }, + { url = "https://files.pythonhosted.org/packages/03/45/10d934535a4993d27e1c84f1810e79ccf8b1b7418cef12151a22fe9bb1e1/orjson-3.10.18-cp311-cp311-win_arm64.whl", hash = "sha256:a6c7c391beaedd3fa63206e5c2b7b554196f14debf1ec9deb54b5d279b1b46f5", size = 131400, upload-time = "2025-04-29T23:28:51.838Z" }, + { url = "https://files.pythonhosted.org/packages/21/1a/67236da0916c1a192d5f4ccbe10ec495367a726996ceb7614eaa687112f2/orjson-3.10.18-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:50c15557afb7f6d63bc6d6348e0337a880a04eaa9cd7c9d569bcb4e760a24753", size = 249184, upload-time = "2025-04-29T23:28:53.612Z" }, + { url = "https://files.pythonhosted.org/packages/b3/bc/c7f1db3b1d094dc0c6c83ed16b161a16c214aaa77f311118a93f647b32dc/orjson-3.10.18-cp312-cp312-macosx_15_0_arm64.whl", hash = "sha256:356b076f1662c9813d5fa56db7d63ccceef4c271b1fb3dd522aca291375fcf17", size = 133279, upload-time = "2025-04-29T23:28:55.055Z" }, + { url = "https://files.pythonhosted.org/packages/af/84/664657cd14cc11f0d81e80e64766c7ba5c9b7fc1ec304117878cc1b4659c/orjson-3.10.18-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:559eb40a70a7494cd5beab2d73657262a74a2c59aff2068fdba8f0424ec5b39d", size = 136799, upload-time = "2025-04-29T23:28:56.828Z" }, + { url = "https://files.pythonhosted.org/packages/9a/bb/f50039c5bb05a7ab024ed43ba25d0319e8722a0ac3babb0807e543349978/orjson-3.10.18-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f3c29eb9a81e2fbc6fd7ddcfba3e101ba92eaff455b8d602bf7511088bbc0eae", size = 132791, upload-time = "2025-04-29T23:28:58.751Z" }, + { url = "https://files.pythonhosted.org/packages/93/8c/ee74709fc072c3ee219784173ddfe46f699598a1723d9d49cbc78d66df65/orjson-3.10.18-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6612787e5b0756a171c7d81ba245ef63a3533a637c335aa7fcb8e665f4a0966f", size = 137059, upload-time = "2025-04-29T23:29:00.129Z" }, + { url = "https://files.pythonhosted.org/packages/6a/37/e6d3109ee004296c80426b5a62b47bcadd96a3deab7443e56507823588c5/orjson-3.10.18-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7ac6bd7be0dcab5b702c9d43d25e70eb456dfd2e119d512447468f6405b4a69c", size = 138359, upload-time = "2025-04-29T23:29:01.704Z" }, + { url = "https://files.pythonhosted.org/packages/4f/5d/387dafae0e4691857c62bd02839a3bf3fa648eebd26185adfac58d09f207/orjson-3.10.18-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9f72f100cee8dde70100406d5c1abba515a7df926d4ed81e20a9730c062fe9ad", size = 142853, upload-time = "2025-04-29T23:29:03.576Z" }, + { url = "https://files.pythonhosted.org/packages/27/6f/875e8e282105350b9a5341c0222a13419758545ae32ad6e0fcf5f64d76aa/orjson-3.10.18-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9dca85398d6d093dd41dc0983cbf54ab8e6afd1c547b6b8a311643917fbf4e0c", size = 133131, upload-time = "2025-04-29T23:29:05.753Z" }, + { url = "https://files.pythonhosted.org/packages/48/b2/73a1f0b4790dcb1e5a45f058f4f5dcadc8a85d90137b50d6bbc6afd0ae50/orjson-3.10.18-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:22748de2a07fcc8781a70edb887abf801bb6142e6236123ff93d12d92db3d406", size = 134834, upload-time = "2025-04-29T23:29:07.35Z" }, + { url = "https://files.pythonhosted.org/packages/56/f5/7ed133a5525add9c14dbdf17d011dd82206ca6840811d32ac52a35935d19/orjson-3.10.18-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:3a83c9954a4107b9acd10291b7f12a6b29e35e8d43a414799906ea10e75438e6", size = 413368, upload-time = "2025-04-29T23:29:09.301Z" }, + { url = "https://files.pythonhosted.org/packages/11/7c/439654221ed9c3324bbac7bdf94cf06a971206b7b62327f11a52544e4982/orjson-3.10.18-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:303565c67a6c7b1f194c94632a4a39918e067bd6176a48bec697393865ce4f06", size = 153359, upload-time = "2025-04-29T23:29:10.813Z" }, + { url = "https://files.pythonhosted.org/packages/48/e7/d58074fa0cc9dd29a8fa2a6c8d5deebdfd82c6cfef72b0e4277c4017563a/orjson-3.10.18-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:86314fdb5053a2f5a5d881f03fca0219bfdf832912aa88d18676a5175c6916b5", size = 137466, upload-time = "2025-04-29T23:29:12.26Z" }, + { url = "https://files.pythonhosted.org/packages/57/4d/fe17581cf81fb70dfcef44e966aa4003360e4194d15a3f38cbffe873333a/orjson-3.10.18-cp312-cp312-win32.whl", hash = "sha256:187ec33bbec58c76dbd4066340067d9ece6e10067bb0cc074a21ae3300caa84e", size = 142683, upload-time = "2025-04-29T23:29:13.865Z" }, + { url = "https://files.pythonhosted.org/packages/e6/22/469f62d25ab5f0f3aee256ea732e72dc3aab6d73bac777bd6277955bceef/orjson-3.10.18-cp312-cp312-win_amd64.whl", hash = "sha256:f9f94cf6d3f9cd720d641f8399e390e7411487e493962213390d1ae45c7814fc", size = 134754, upload-time = "2025-04-29T23:29:15.338Z" }, + { url = "https://files.pythonhosted.org/packages/10/b0/1040c447fac5b91bc1e9c004b69ee50abb0c1ffd0d24406e1350c58a7fcb/orjson-3.10.18-cp312-cp312-win_arm64.whl", hash = "sha256:3d600be83fe4514944500fa8c2a0a77099025ec6482e8087d7659e891f23058a", size = 131218, upload-time = "2025-04-29T23:29:17.324Z" }, ] [[package]] @@ -3666,24 +3818,24 @@ dependencies = [ { name = "requests" }, { name = "six" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/61/ce/d23a9d44268dc992ae1a878d24341dddaea4de4ae374c261209bb6e9554b/oss2-2.18.5.tar.gz", hash = "sha256:555c857f4441ae42a2c0abab8fc9482543fba35d65a4a4be73101c959a2b4011", size = 283388 } +sdist = { url = "https://files.pythonhosted.org/packages/61/ce/d23a9d44268dc992ae1a878d24341dddaea4de4ae374c261209bb6e9554b/oss2-2.18.5.tar.gz", hash = "sha256:555c857f4441ae42a2c0abab8fc9482543fba35d65a4a4be73101c959a2b4011", size = 283388, upload-time = "2024-04-29T12:49:07.686Z" } [[package]] name = "overrides" version = "7.7.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/36/86/b585f53236dec60aba864e050778b25045f857e17f6e5ea0ae95fe80edd2/overrides-7.7.0.tar.gz", hash = "sha256:55158fa3d93b98cc75299b1e67078ad9003ca27945c76162c1c0766d6f91820a", size = 22812 } +sdist = { url = "https://files.pythonhosted.org/packages/36/86/b585f53236dec60aba864e050778b25045f857e17f6e5ea0ae95fe80edd2/overrides-7.7.0.tar.gz", hash = "sha256:55158fa3d93b98cc75299b1e67078ad9003ca27945c76162c1c0766d6f91820a", size = 22812, upload-time = "2024-01-27T21:01:33.423Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/2c/ab/fc8290c6a4c722e5514d80f62b2dc4c4df1a68a41d1364e625c35990fcf3/overrides-7.7.0-py3-none-any.whl", hash = "sha256:c7ed9d062f78b8e4c1a7b70bd8796b35ead4d9f510227ef9c5dc7626c60d7e49", size = 17832 }, + { url = "https://files.pythonhosted.org/packages/2c/ab/fc8290c6a4c722e5514d80f62b2dc4c4df1a68a41d1364e625c35990fcf3/overrides-7.7.0-py3-none-any.whl", hash = "sha256:c7ed9d062f78b8e4c1a7b70bd8796b35ead4d9f510227ef9c5dc7626c60d7e49", size = 17832, upload-time = "2024-01-27T21:01:31.393Z" }, ] [[package]] name = "packaging" version = "24.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d0/63/68dbb6eb2de9cb10ee4c9c14a0148804425e13c4fb20d61cce69f53106da/packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f", size = 163950 } +sdist = { url = "https://files.pythonhosted.org/packages/d0/63/68dbb6eb2de9cb10ee4c9c14a0148804425e13c4fb20d61cce69f53106da/packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f", size = 163950, upload-time = "2024-11-08T09:47:47.202Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/88/ef/eb23f262cca3c0c4eb7ab1933c3b1f03d021f2c48f54763065b6f0e321be/packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759", size = 65451 }, + { url = "https://files.pythonhosted.org/packages/88/ef/eb23f262cca3c0c4eb7ab1933c3b1f03d021f2c48f54763065b6f0e321be/packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759", size = 65451, upload-time = "2024-11-08T09:47:44.722Z" }, ] [[package]] @@ -3696,22 +3848,22 @@ dependencies = [ { name = "pytz" }, { name = "tzdata" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/9c/d6/9f8431bacc2e19dca897724cd097b1bb224a6ad5433784a44b587c7c13af/pandas-2.2.3.tar.gz", hash = "sha256:4f18ba62b61d7e192368b84517265a99b4d7ee8912f8708660fb4a366cc82667", size = 4399213 } +sdist = { url = "https://files.pythonhosted.org/packages/9c/d6/9f8431bacc2e19dca897724cd097b1bb224a6ad5433784a44b587c7c13af/pandas-2.2.3.tar.gz", hash = "sha256:4f18ba62b61d7e192368b84517265a99b4d7ee8912f8708660fb4a366cc82667", size = 4399213, upload-time = "2024-09-20T13:10:04.827Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a8/44/d9502bf0ed197ba9bf1103c9867d5904ddcaf869e52329787fc54ed70cc8/pandas-2.2.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:66108071e1b935240e74525006034333f98bcdb87ea116de573a6a0dccb6c039", size = 12602222 }, - { url = "https://files.pythonhosted.org/packages/52/11/9eac327a38834f162b8250aab32a6781339c69afe7574368fffe46387edf/pandas-2.2.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7c2875855b0ff77b2a64a0365e24455d9990730d6431b9e0ee18ad8acee13dbd", size = 11321274 }, - { url = "https://files.pythonhosted.org/packages/45/fb/c4beeb084718598ba19aa9f5abbc8aed8b42f90930da861fcb1acdb54c3a/pandas-2.2.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cd8d0c3be0515c12fed0bdbae072551c8b54b7192c7b1fda0ba56059a0179698", size = 15579836 }, - { url = "https://files.pythonhosted.org/packages/cd/5f/4dba1d39bb9c38d574a9a22548c540177f78ea47b32f99c0ff2ec499fac5/pandas-2.2.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c124333816c3a9b03fbeef3a9f230ba9a737e9e5bb4060aa2107a86cc0a497fc", size = 13058505 }, - { url = "https://files.pythonhosted.org/packages/b9/57/708135b90391995361636634df1f1130d03ba456e95bcf576fada459115a/pandas-2.2.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:63cc132e40a2e084cf01adf0775b15ac515ba905d7dcca47e9a251819c575ef3", size = 16744420 }, - { url = "https://files.pythonhosted.org/packages/86/4a/03ed6b7ee323cf30404265c284cee9c65c56a212e0a08d9ee06984ba2240/pandas-2.2.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:29401dbfa9ad77319367d36940cd8a0b3a11aba16063e39632d98b0e931ddf32", size = 14440457 }, - { url = "https://files.pythonhosted.org/packages/ed/8c/87ddf1fcb55d11f9f847e3c69bb1c6f8e46e2f40ab1a2d2abadb2401b007/pandas-2.2.3-cp311-cp311-win_amd64.whl", hash = "sha256:3fc6873a41186404dad67245896a6e440baacc92f5b716ccd1bc9ed2995ab2c5", size = 11617166 }, - { url = "https://files.pythonhosted.org/packages/17/a3/fb2734118db0af37ea7433f57f722c0a56687e14b14690edff0cdb4b7e58/pandas-2.2.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b1d432e8d08679a40e2a6d8b2f9770a5c21793a6f9f47fdd52c5ce1948a5a8a9", size = 12529893 }, - { url = "https://files.pythonhosted.org/packages/e1/0c/ad295fd74bfac85358fd579e271cded3ac969de81f62dd0142c426b9da91/pandas-2.2.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a5a1595fe639f5988ba6a8e5bc9649af3baf26df3998a0abe56c02609392e0a4", size = 11363475 }, - { url = "https://files.pythonhosted.org/packages/c6/2a/4bba3f03f7d07207481fed47f5b35f556c7441acddc368ec43d6643c5777/pandas-2.2.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5de54125a92bb4d1c051c0659e6fcb75256bf799a732a87184e5ea503965bce3", size = 15188645 }, - { url = "https://files.pythonhosted.org/packages/38/f8/d8fddee9ed0d0c0f4a2132c1dfcf0e3e53265055da8df952a53e7eaf178c/pandas-2.2.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fffb8ae78d8af97f849404f21411c95062db1496aeb3e56f146f0355c9989319", size = 12739445 }, - { url = "https://files.pythonhosted.org/packages/20/e8/45a05d9c39d2cea61ab175dbe6a2de1d05b679e8de2011da4ee190d7e748/pandas-2.2.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6dfcb5ee8d4d50c06a51c2fffa6cff6272098ad6540aed1a76d15fb9318194d8", size = 16359235 }, - { url = "https://files.pythonhosted.org/packages/1d/99/617d07a6a5e429ff90c90da64d428516605a1ec7d7bea494235e1c3882de/pandas-2.2.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:062309c1b9ea12a50e8ce661145c6aab431b1e99530d3cd60640e255778bd43a", size = 14056756 }, - { url = "https://files.pythonhosted.org/packages/29/d4/1244ab8edf173a10fd601f7e13b9566c1b525c4f365d6bee918e68381889/pandas-2.2.3-cp312-cp312-win_amd64.whl", hash = "sha256:59ef3764d0fe818125a5097d2ae867ca3fa64df032331b7e0917cf5d7bf66b13", size = 11504248 }, + { url = "https://files.pythonhosted.org/packages/a8/44/d9502bf0ed197ba9bf1103c9867d5904ddcaf869e52329787fc54ed70cc8/pandas-2.2.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:66108071e1b935240e74525006034333f98bcdb87ea116de573a6a0dccb6c039", size = 12602222, upload-time = "2024-09-20T13:08:56.254Z" }, + { url = "https://files.pythonhosted.org/packages/52/11/9eac327a38834f162b8250aab32a6781339c69afe7574368fffe46387edf/pandas-2.2.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7c2875855b0ff77b2a64a0365e24455d9990730d6431b9e0ee18ad8acee13dbd", size = 11321274, upload-time = "2024-09-20T13:08:58.645Z" }, + { url = "https://files.pythonhosted.org/packages/45/fb/c4beeb084718598ba19aa9f5abbc8aed8b42f90930da861fcb1acdb54c3a/pandas-2.2.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cd8d0c3be0515c12fed0bdbae072551c8b54b7192c7b1fda0ba56059a0179698", size = 15579836, upload-time = "2024-09-20T19:01:57.571Z" }, + { url = "https://files.pythonhosted.org/packages/cd/5f/4dba1d39bb9c38d574a9a22548c540177f78ea47b32f99c0ff2ec499fac5/pandas-2.2.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c124333816c3a9b03fbeef3a9f230ba9a737e9e5bb4060aa2107a86cc0a497fc", size = 13058505, upload-time = "2024-09-20T13:09:01.501Z" }, + { url = "https://files.pythonhosted.org/packages/b9/57/708135b90391995361636634df1f1130d03ba456e95bcf576fada459115a/pandas-2.2.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:63cc132e40a2e084cf01adf0775b15ac515ba905d7dcca47e9a251819c575ef3", size = 16744420, upload-time = "2024-09-20T19:02:00.678Z" }, + { url = "https://files.pythonhosted.org/packages/86/4a/03ed6b7ee323cf30404265c284cee9c65c56a212e0a08d9ee06984ba2240/pandas-2.2.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:29401dbfa9ad77319367d36940cd8a0b3a11aba16063e39632d98b0e931ddf32", size = 14440457, upload-time = "2024-09-20T13:09:04.105Z" }, + { url = "https://files.pythonhosted.org/packages/ed/8c/87ddf1fcb55d11f9f847e3c69bb1c6f8e46e2f40ab1a2d2abadb2401b007/pandas-2.2.3-cp311-cp311-win_amd64.whl", hash = "sha256:3fc6873a41186404dad67245896a6e440baacc92f5b716ccd1bc9ed2995ab2c5", size = 11617166, upload-time = "2024-09-20T13:09:06.917Z" }, + { url = "https://files.pythonhosted.org/packages/17/a3/fb2734118db0af37ea7433f57f722c0a56687e14b14690edff0cdb4b7e58/pandas-2.2.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b1d432e8d08679a40e2a6d8b2f9770a5c21793a6f9f47fdd52c5ce1948a5a8a9", size = 12529893, upload-time = "2024-09-20T13:09:09.655Z" }, + { url = "https://files.pythonhosted.org/packages/e1/0c/ad295fd74bfac85358fd579e271cded3ac969de81f62dd0142c426b9da91/pandas-2.2.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a5a1595fe639f5988ba6a8e5bc9649af3baf26df3998a0abe56c02609392e0a4", size = 11363475, upload-time = "2024-09-20T13:09:14.718Z" }, + { url = "https://files.pythonhosted.org/packages/c6/2a/4bba3f03f7d07207481fed47f5b35f556c7441acddc368ec43d6643c5777/pandas-2.2.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5de54125a92bb4d1c051c0659e6fcb75256bf799a732a87184e5ea503965bce3", size = 15188645, upload-time = "2024-09-20T19:02:03.88Z" }, + { url = "https://files.pythonhosted.org/packages/38/f8/d8fddee9ed0d0c0f4a2132c1dfcf0e3e53265055da8df952a53e7eaf178c/pandas-2.2.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fffb8ae78d8af97f849404f21411c95062db1496aeb3e56f146f0355c9989319", size = 12739445, upload-time = "2024-09-20T13:09:17.621Z" }, + { url = "https://files.pythonhosted.org/packages/20/e8/45a05d9c39d2cea61ab175dbe6a2de1d05b679e8de2011da4ee190d7e748/pandas-2.2.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6dfcb5ee8d4d50c06a51c2fffa6cff6272098ad6540aed1a76d15fb9318194d8", size = 16359235, upload-time = "2024-09-20T19:02:07.094Z" }, + { url = "https://files.pythonhosted.org/packages/1d/99/617d07a6a5e429ff90c90da64d428516605a1ec7d7bea494235e1c3882de/pandas-2.2.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:062309c1b9ea12a50e8ce661145c6aab431b1e99530d3cd60640e255778bd43a", size = 14056756, upload-time = "2024-09-20T13:09:20.474Z" }, + { url = "https://files.pythonhosted.org/packages/29/d4/1244ab8edf173a10fd601f7e13b9566c1b525c4f365d6bee918e68381889/pandas-2.2.3-cp312-cp312-win_amd64.whl", hash = "sha256:59ef3764d0fe818125a5097d2ae867ca3fa64df032331b7e0917cf5d7bf66b13", size = 11504248, upload-time = "2024-09-20T13:09:23.137Z" }, ] [package.optional-dependencies] @@ -3735,15 +3887,15 @@ performance = [ [[package]] name = "pandas-stubs" -version = "2.2.3.250308" +version = "2.2.3.250527" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "numpy" }, { name = "types-pytz" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/2e/5a/261f5c67a73e46df2d5984fe7129d66a3ed4864fd7aa9d8721abb3fc802e/pandas_stubs-2.2.3.250308.tar.gz", hash = "sha256:3a6e9daf161f00b85c83772ed3d5cff9522028f07a94817472c07b91f46710fd", size = 103986 } +sdist = { url = "https://files.pythonhosted.org/packages/5f/0d/5fe7f7f3596eb1c2526fea151e9470f86b379183d8b9debe44b2098651ca/pandas_stubs-2.2.3.250527.tar.gz", hash = "sha256:e2d694c4e72106055295ad143664e5c99e5815b07190d1ff85b73b13ff019e63", size = 106312, upload-time = "2025-05-27T15:24:29.716Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ba/64/ab61d9ca06ff66c07eb804ec27dec1a2be1978b3c3767caaa91e363438cc/pandas_stubs-2.2.3.250308-py3-none-any.whl", hash = "sha256:a377edff3b61f8b268c82499fdbe7c00fdeed13235b8b71d6a1dc347aeddc74d", size = 158053 }, + { url = "https://files.pythonhosted.org/packages/ec/f8/46141ba8c9d7064dc5008bfb4a6ae5bd3c30e4c61c28b5c5ed485bf358ba/pandas_stubs-2.2.3.250527-py3-none-any.whl", hash = "sha256:cd0a49a95b8c5f944e605be711042a4dd8550e2c559b43d70ba2c4b524b66163", size = 159683, upload-time = "2025-05-27T15:24:28.4Z" }, ] [[package]] @@ -3754,7 +3906,16 @@ dependencies = [ { name = "plumbum" }, { name = "ply" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/10/9a/e3186e760c57ee5f1c27ea5cea577a0ff9abfca51eefcb4d9a4cd39aff2e/pandoc-2.4.tar.gz", hash = "sha256:ecd1f8cbb7f4180c6b5db4a17a7c1a74df519995f5f186ef81ce72a9cbd0dd9a", size = 34635 } +sdist = { url = "https://files.pythonhosted.org/packages/10/9a/e3186e760c57ee5f1c27ea5cea577a0ff9abfca51eefcb4d9a4cd39aff2e/pandoc-2.4.tar.gz", hash = "sha256:ecd1f8cbb7f4180c6b5db4a17a7c1a74df519995f5f186ef81ce72a9cbd0dd9a", size = 34635, upload-time = "2024-08-07T14:33:58.016Z" } + +[[package]] +name = "pathspec" +version = "0.12.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ca/bc/f35b8446f4531a7cb215605d100cd88b7ac6f44ab3fc94870c120ab3adbf/pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712", size = 51043, upload-time = "2023-12-10T22:30:45Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cc/20/ff623b09d963f88bfde16306a54e12ee5ea43e9b597108672ff3a408aad6/pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08", size = 31191, upload-time = "2023-12-10T22:30:43.14Z" }, +] [[package]] name = "pgvecto-rs" @@ -3764,9 +3925,9 @@ dependencies = [ { name = "numpy" }, { name = "toml" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/01/09/c0be8f54386367159fd22495635fba65ac6bbc436a34502bc2849d89f6ab/pgvecto_rs-0.2.2.tar.gz", hash = "sha256:edaa913d1747152b1407cbdf6337d51ac852547b54953ef38997433be3a75a3b", size = 28561 } +sdist = { url = "https://files.pythonhosted.org/packages/01/09/c0be8f54386367159fd22495635fba65ac6bbc436a34502bc2849d89f6ab/pgvecto_rs-0.2.2.tar.gz", hash = "sha256:edaa913d1747152b1407cbdf6337d51ac852547b54953ef38997433be3a75a3b", size = 28561, upload-time = "2024-10-08T02:01:15.678Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ba/dc/a39ceb4fe4b72f889228119b91e0ef7fcaaf9ec662ab19acdacb74cd5eaf/pgvecto_rs-0.2.2-py3-none-any.whl", hash = "sha256:5f3f7f806813de408c45dc10a9eb418b986c4d7b7723e8fce9298f2f7d8fbbd5", size = 30779 }, + { url = "https://files.pythonhosted.org/packages/ba/dc/a39ceb4fe4b72f889228119b91e0ef7fcaaf9ec662ab19acdacb74cd5eaf/pgvecto_rs-0.2.2-py3-none-any.whl", hash = "sha256:5f3f7f806813de408c45dc10a9eb418b986c4d7b7723e8fce9298f2f7d8fbbd5", size = 30779, upload-time = "2024-10-08T02:01:14.669Z" }, ] [package.optional-dependencies] @@ -3782,46 +3943,62 @@ dependencies = [ { name = "numpy" }, ] wheels = [ - { url = "https://files.pythonhosted.org/packages/29/bb/4686b1090a7c68fa367e981130a074dc6c1236571d914ffa6e05c882b59d/pgvector-0.2.5-py2.py3-none-any.whl", hash = "sha256:5e5e93ec4d3c45ab1fa388729d56c602f6966296e19deee8878928c6d567e41b", size = 9638 }, + { url = "https://files.pythonhosted.org/packages/29/bb/4686b1090a7c68fa367e981130a074dc6c1236571d914ffa6e05c882b59d/pgvector-0.2.5-py2.py3-none-any.whl", hash = "sha256:5e5e93ec4d3c45ab1fa388729d56c602f6966296e19deee8878928c6d567e41b", size = 9638, upload-time = "2024-02-07T19:35:03.8Z" }, ] [[package]] name = "pillow" -version = "11.1.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f3/af/c097e544e7bd278333db77933e535098c259609c4eb3b85381109602fb5b/pillow-11.1.0.tar.gz", hash = "sha256:368da70808b36d73b4b390a8ffac11069f8a5c85f29eff1f1b01bcf3ef5b2a20", size = 46742715 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/dd/d6/2000bfd8d5414fb70cbbe52c8332f2283ff30ed66a9cde42716c8ecbe22c/pillow-11.1.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:e06695e0326d05b06833b40b7ef477e475d0b1ba3a6d27da1bb48c23209bf457", size = 3229968 }, - { url = "https://files.pythonhosted.org/packages/d9/45/3fe487010dd9ce0a06adf9b8ff4f273cc0a44536e234b0fad3532a42c15b/pillow-11.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:96f82000e12f23e4f29346e42702b6ed9a2f2fea34a740dd5ffffcc8c539eb35", size = 3101806 }, - { url = "https://files.pythonhosted.org/packages/e3/72/776b3629c47d9d5f1c160113158a7a7ad177688d3a1159cd3b62ded5a33a/pillow-11.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a3cd561ded2cf2bbae44d4605837221b987c216cff94f49dfeed63488bb228d2", size = 4322283 }, - { url = "https://files.pythonhosted.org/packages/e4/c2/e25199e7e4e71d64eeb869f5b72c7ddec70e0a87926398785ab944d92375/pillow-11.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f189805c8be5ca5add39e6f899e6ce2ed824e65fb45f3c28cb2841911da19070", size = 4402945 }, - { url = "https://files.pythonhosted.org/packages/c1/ed/51d6136c9d5911f78632b1b86c45241c712c5a80ed7fa7f9120a5dff1eba/pillow-11.1.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:dd0052e9db3474df30433f83a71b9b23bd9e4ef1de13d92df21a52c0303b8ab6", size = 4361228 }, - { url = "https://files.pythonhosted.org/packages/48/a4/fbfe9d5581d7b111b28f1d8c2762dee92e9821bb209af9fa83c940e507a0/pillow-11.1.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:837060a8599b8f5d402e97197d4924f05a2e0d68756998345c829c33186217b1", size = 4484021 }, - { url = "https://files.pythonhosted.org/packages/39/db/0b3c1a5018117f3c1d4df671fb8e47d08937f27519e8614bbe86153b65a5/pillow-11.1.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:aa8dd43daa836b9a8128dbe7d923423e5ad86f50a7a14dc688194b7be5c0dea2", size = 4287449 }, - { url = "https://files.pythonhosted.org/packages/d9/58/bc128da7fea8c89fc85e09f773c4901e95b5936000e6f303222490c052f3/pillow-11.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0a2f91f8a8b367e7a57c6e91cd25af510168091fb89ec5146003e424e1558a96", size = 4419972 }, - { url = "https://files.pythonhosted.org/packages/5f/bb/58f34379bde9fe197f51841c5bbe8830c28bbb6d3801f16a83b8f2ad37df/pillow-11.1.0-cp311-cp311-win32.whl", hash = "sha256:c12fc111ef090845de2bb15009372175d76ac99969bdf31e2ce9b42e4b8cd88f", size = 2291201 }, - { url = "https://files.pythonhosted.org/packages/3a/c6/fce9255272bcf0c39e15abd2f8fd8429a954cf344469eaceb9d0d1366913/pillow-11.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:fbd43429d0d7ed6533b25fc993861b8fd512c42d04514a0dd6337fb3ccf22761", size = 2625686 }, - { url = "https://files.pythonhosted.org/packages/c8/52/8ba066d569d932365509054859f74f2a9abee273edcef5cd75e4bc3e831e/pillow-11.1.0-cp311-cp311-win_arm64.whl", hash = "sha256:f7955ecf5609dee9442cbface754f2c6e541d9e6eda87fad7f7a989b0bdb9d71", size = 2375194 }, - { url = "https://files.pythonhosted.org/packages/95/20/9ce6ed62c91c073fcaa23d216e68289e19d95fb8188b9fb7a63d36771db8/pillow-11.1.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:2062ffb1d36544d42fcaa277b069c88b01bb7298f4efa06731a7fd6cc290b81a", size = 3226818 }, - { url = "https://files.pythonhosted.org/packages/b9/d8/f6004d98579a2596c098d1e30d10b248798cceff82d2b77aa914875bfea1/pillow-11.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a85b653980faad27e88b141348707ceeef8a1186f75ecc600c395dcac19f385b", size = 3101662 }, - { url = "https://files.pythonhosted.org/packages/08/d9/892e705f90051c7a2574d9f24579c9e100c828700d78a63239676f960b74/pillow-11.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9409c080586d1f683df3f184f20e36fb647f2e0bc3988094d4fd8c9f4eb1b3b3", size = 4329317 }, - { url = "https://files.pythonhosted.org/packages/8c/aa/7f29711f26680eab0bcd3ecdd6d23ed6bce180d82e3f6380fb7ae35fcf3b/pillow-11.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7fdadc077553621911f27ce206ffcbec7d3f8d7b50e0da39f10997e8e2bb7f6a", size = 4412999 }, - { url = "https://files.pythonhosted.org/packages/c8/c4/8f0fe3b9e0f7196f6d0bbb151f9fba323d72a41da068610c4c960b16632a/pillow-11.1.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:93a18841d09bcdd774dcdc308e4537e1f867b3dec059c131fde0327899734aa1", size = 4368819 }, - { url = "https://files.pythonhosted.org/packages/38/0d/84200ed6a871ce386ddc82904bfadc0c6b28b0c0ec78176871a4679e40b3/pillow-11.1.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:9aa9aeddeed452b2f616ff5507459e7bab436916ccb10961c4a382cd3e03f47f", size = 4496081 }, - { url = "https://files.pythonhosted.org/packages/84/9c/9bcd66f714d7e25b64118e3952d52841a4babc6d97b6d28e2261c52045d4/pillow-11.1.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3cdcdb0b896e981678eee140d882b70092dac83ac1cdf6b3a60e2216a73f2b91", size = 4296513 }, - { url = "https://files.pythonhosted.org/packages/db/61/ada2a226e22da011b45f7104c95ebda1b63dcbb0c378ad0f7c2a710f8fd2/pillow-11.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:36ba10b9cb413e7c7dfa3e189aba252deee0602c86c309799da5a74009ac7a1c", size = 4431298 }, - { url = "https://files.pythonhosted.org/packages/e7/c4/fc6e86750523f367923522014b821c11ebc5ad402e659d8c9d09b3c9d70c/pillow-11.1.0-cp312-cp312-win32.whl", hash = "sha256:cfd5cd998c2e36a862d0e27b2df63237e67273f2fc78f47445b14e73a810e7e6", size = 2291630 }, - { url = "https://files.pythonhosted.org/packages/08/5c/2104299949b9d504baf3f4d35f73dbd14ef31bbd1ddc2c1b66a5b7dfda44/pillow-11.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:a697cd8ba0383bba3d2d3ada02b34ed268cb548b369943cd349007730c92bddf", size = 2626369 }, - { url = "https://files.pythonhosted.org/packages/37/f3/9b18362206b244167c958984b57c7f70a0289bfb59a530dd8af5f699b910/pillow-11.1.0-cp312-cp312-win_arm64.whl", hash = "sha256:4dd43a78897793f60766563969442020e90eb7847463eca901e41ba186a7d4a5", size = 2375240 }, +version = "11.2.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/af/cb/bb5c01fcd2a69335b86c22142b2bccfc3464087efb7fd382eee5ffc7fdf7/pillow-11.2.1.tar.gz", hash = "sha256:a64dd61998416367b7ef979b73d3a85853ba9bec4c2925f74e588879a58716b6", size = 47026707, upload-time = "2025-04-12T17:50:03.289Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/68/08/3fbf4b98924c73037a8e8b4c2c774784805e0fb4ebca6c5bb60795c40125/pillow-11.2.1-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:35ca289f712ccfc699508c4658a1d14652e8033e9b69839edf83cbdd0ba39e70", size = 3198450, upload-time = "2025-04-12T17:47:37.135Z" }, + { url = "https://files.pythonhosted.org/packages/84/92/6505b1af3d2849d5e714fc75ba9e69b7255c05ee42383a35a4d58f576b16/pillow-11.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e0409af9f829f87a2dfb7e259f78f317a5351f2045158be321fd135973fff7bf", size = 3030550, upload-time = "2025-04-12T17:47:39.345Z" }, + { url = "https://files.pythonhosted.org/packages/3c/8c/ac2f99d2a70ff966bc7eb13dacacfaab57c0549b2ffb351b6537c7840b12/pillow-11.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d4e5c5edee874dce4f653dbe59db7c73a600119fbea8d31f53423586ee2aafd7", size = 4415018, upload-time = "2025-04-12T17:47:41.128Z" }, + { url = "https://files.pythonhosted.org/packages/1f/e3/0a58b5d838687f40891fff9cbaf8669f90c96b64dc8f91f87894413856c6/pillow-11.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b93a07e76d13bff9444f1a029e0af2964e654bfc2e2c2d46bfd080df5ad5f3d8", size = 4498006, upload-time = "2025-04-12T17:47:42.912Z" }, + { url = "https://files.pythonhosted.org/packages/21/f5/6ba14718135f08fbfa33308efe027dd02b781d3f1d5c471444a395933aac/pillow-11.2.1-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:e6def7eed9e7fa90fde255afaf08060dc4b343bbe524a8f69bdd2a2f0018f600", size = 4517773, upload-time = "2025-04-12T17:47:44.611Z" }, + { url = "https://files.pythonhosted.org/packages/20/f2/805ad600fc59ebe4f1ba6129cd3a75fb0da126975c8579b8f57abeb61e80/pillow-11.2.1-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:8f4f3724c068be008c08257207210c138d5f3731af6c155a81c2b09a9eb3a788", size = 4607069, upload-time = "2025-04-12T17:47:46.46Z" }, + { url = "https://files.pythonhosted.org/packages/71/6b/4ef8a288b4bb2e0180cba13ca0a519fa27aa982875882392b65131401099/pillow-11.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a0a6709b47019dff32e678bc12c63008311b82b9327613f534e496dacaefb71e", size = 4583460, upload-time = "2025-04-12T17:47:49.255Z" }, + { url = "https://files.pythonhosted.org/packages/62/ae/f29c705a09cbc9e2a456590816e5c234382ae5d32584f451c3eb41a62062/pillow-11.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f6b0c664ccb879109ee3ca702a9272d877f4fcd21e5eb63c26422fd6e415365e", size = 4661304, upload-time = "2025-04-12T17:47:51.067Z" }, + { url = "https://files.pythonhosted.org/packages/6e/1a/c8217b6f2f73794a5e219fbad087701f412337ae6dbb956db37d69a9bc43/pillow-11.2.1-cp311-cp311-win32.whl", hash = "sha256:cc5d875d56e49f112b6def6813c4e3d3036d269c008bf8aef72cd08d20ca6df6", size = 2331809, upload-time = "2025-04-12T17:47:54.425Z" }, + { url = "https://files.pythonhosted.org/packages/e2/72/25a8f40170dc262e86e90f37cb72cb3de5e307f75bf4b02535a61afcd519/pillow-11.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:0f5c7eda47bf8e3c8a283762cab94e496ba977a420868cb819159980b6709193", size = 2676338, upload-time = "2025-04-12T17:47:56.535Z" }, + { url = "https://files.pythonhosted.org/packages/06/9e/76825e39efee61efea258b479391ca77d64dbd9e5804e4ad0fa453b4ba55/pillow-11.2.1-cp311-cp311-win_arm64.whl", hash = "sha256:4d375eb838755f2528ac8cbc926c3e31cc49ca4ad0cf79cff48b20e30634a4a7", size = 2414918, upload-time = "2025-04-12T17:47:58.217Z" }, + { url = "https://files.pythonhosted.org/packages/c7/40/052610b15a1b8961f52537cc8326ca6a881408bc2bdad0d852edeb6ed33b/pillow-11.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:78afba22027b4accef10dbd5eed84425930ba41b3ea0a86fa8d20baaf19d807f", size = 3190185, upload-time = "2025-04-12T17:48:00.417Z" }, + { url = "https://files.pythonhosted.org/packages/e5/7e/b86dbd35a5f938632093dc40d1682874c33dcfe832558fc80ca56bfcb774/pillow-11.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:78092232a4ab376a35d68c4e6d5e00dfd73454bd12b230420025fbe178ee3b0b", size = 3030306, upload-time = "2025-04-12T17:48:02.391Z" }, + { url = "https://files.pythonhosted.org/packages/a4/5c/467a161f9ed53e5eab51a42923c33051bf8d1a2af4626ac04f5166e58e0c/pillow-11.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:25a5f306095c6780c52e6bbb6109624b95c5b18e40aab1c3041da3e9e0cd3e2d", size = 4416121, upload-time = "2025-04-12T17:48:04.554Z" }, + { url = "https://files.pythonhosted.org/packages/62/73/972b7742e38ae0e2ac76ab137ca6005dcf877480da0d9d61d93b613065b4/pillow-11.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c7b29dbd4281923a2bfe562acb734cee96bbb129e96e6972d315ed9f232bef4", size = 4501707, upload-time = "2025-04-12T17:48:06.831Z" }, + { url = "https://files.pythonhosted.org/packages/e4/3a/427e4cb0b9e177efbc1a84798ed20498c4f233abde003c06d2650a6d60cb/pillow-11.2.1-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:3e645b020f3209a0181a418bffe7b4a93171eef6c4ef6cc20980b30bebf17b7d", size = 4522921, upload-time = "2025-04-12T17:48:09.229Z" }, + { url = "https://files.pythonhosted.org/packages/fe/7c/d8b1330458e4d2f3f45d9508796d7caf0c0d3764c00c823d10f6f1a3b76d/pillow-11.2.1-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:b2dbea1012ccb784a65349f57bbc93730b96e85b42e9bf7b01ef40443db720b4", size = 4612523, upload-time = "2025-04-12T17:48:11.631Z" }, + { url = "https://files.pythonhosted.org/packages/b3/2f/65738384e0b1acf451de5a573d8153fe84103772d139e1e0bdf1596be2ea/pillow-11.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:da3104c57bbd72948d75f6a9389e6727d2ab6333c3617f0a89d72d4940aa0443", size = 4587836, upload-time = "2025-04-12T17:48:13.592Z" }, + { url = "https://files.pythonhosted.org/packages/6a/c5/e795c9f2ddf3debb2dedd0df889f2fe4b053308bb59a3cc02a0cd144d641/pillow-11.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:598174aef4589af795f66f9caab87ba4ff860ce08cd5bb447c6fc553ffee603c", size = 4669390, upload-time = "2025-04-12T17:48:15.938Z" }, + { url = "https://files.pythonhosted.org/packages/96/ae/ca0099a3995976a9fce2f423166f7bff9b12244afdc7520f6ed38911539a/pillow-11.2.1-cp312-cp312-win32.whl", hash = "sha256:1d535df14716e7f8776b9e7fee118576d65572b4aad3ed639be9e4fa88a1cad3", size = 2332309, upload-time = "2025-04-12T17:48:17.885Z" }, + { url = "https://files.pythonhosted.org/packages/7c/18/24bff2ad716257fc03da964c5e8f05d9790a779a8895d6566e493ccf0189/pillow-11.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:14e33b28bf17c7a38eede290f77db7c664e4eb01f7869e37fa98a5aa95978941", size = 2676768, upload-time = "2025-04-12T17:48:19.655Z" }, + { url = "https://files.pythonhosted.org/packages/da/bb/e8d656c9543276517ee40184aaa39dcb41e683bca121022f9323ae11b39d/pillow-11.2.1-cp312-cp312-win_arm64.whl", hash = "sha256:21e1470ac9e5739ff880c211fc3af01e3ae505859392bf65458c224d0bf283eb", size = 2415087, upload-time = "2025-04-12T17:48:21.991Z" }, + { url = "https://files.pythonhosted.org/packages/a4/ad/2613c04633c7257d9481ab21d6b5364b59fc5d75faafd7cb8693523945a3/pillow-11.2.1-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:80f1df8dbe9572b4b7abdfa17eb5d78dd620b1d55d9e25f834efdbee872d3aed", size = 3181734, upload-time = "2025-04-12T17:49:46.789Z" }, + { url = "https://files.pythonhosted.org/packages/a4/fd/dcdda4471ed667de57bb5405bb42d751e6cfdd4011a12c248b455c778e03/pillow-11.2.1-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:ea926cfbc3957090becbcbbb65ad177161a2ff2ad578b5a6ec9bb1e1cd78753c", size = 2999841, upload-time = "2025-04-12T17:49:48.812Z" }, + { url = "https://files.pythonhosted.org/packages/ac/89/8a2536e95e77432833f0db6fd72a8d310c8e4272a04461fb833eb021bf94/pillow-11.2.1-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:738db0e0941ca0376804d4de6a782c005245264edaa253ffce24e5a15cbdc7bd", size = 3437470, upload-time = "2025-04-12T17:49:50.831Z" }, + { url = "https://files.pythonhosted.org/packages/9d/8f/abd47b73c60712f88e9eda32baced7bfc3e9bd6a7619bb64b93acff28c3e/pillow-11.2.1-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9db98ab6565c69082ec9b0d4e40dd9f6181dab0dd236d26f7a50b8b9bfbd5076", size = 3460013, upload-time = "2025-04-12T17:49:53.278Z" }, + { url = "https://files.pythonhosted.org/packages/f6/20/5c0a0aa83b213b7a07ec01e71a3d6ea2cf4ad1d2c686cc0168173b6089e7/pillow-11.2.1-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:036e53f4170e270ddb8797d4c590e6dd14d28e15c7da375c18978045f7e6c37b", size = 3527165, upload-time = "2025-04-12T17:49:55.164Z" }, + { url = "https://files.pythonhosted.org/packages/58/0e/2abab98a72202d91146abc839e10c14f7cf36166f12838ea0c4db3ca6ecb/pillow-11.2.1-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:14f73f7c291279bd65fda51ee87affd7c1e097709f7fdd0188957a16c264601f", size = 3571586, upload-time = "2025-04-12T17:49:57.171Z" }, + { url = "https://files.pythonhosted.org/packages/21/2c/5e05f58658cf49b6667762cca03d6e7d85cededde2caf2ab37b81f80e574/pillow-11.2.1-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:208653868d5c9ecc2b327f9b9ef34e0e42a4cdd172c2988fd81d62d2bc9bc044", size = 2674751, upload-time = "2025-04-12T17:49:59.628Z" }, +] + +[[package]] +name = "platformdirs" +version = "4.3.8" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fe/8b/3c73abc9c759ecd3f1f7ceff6685840859e8070c4d947c93fae71f6a0bf2/platformdirs-4.3.8.tar.gz", hash = "sha256:3d512d96e16bcb959a814c9f348431070822a6496326a4be0911c40b5a74c2bc", size = 21362, upload-time = "2025-05-07T22:47:42.121Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fe/39/979e8e21520d4e47a0bbe349e2713c0aac6f3d853d0e5b34d76206c439aa/platformdirs-4.3.8-py3-none-any.whl", hash = "sha256:ff7059bb7eb1179e2685604f4aaf157cfd9535242bd23742eadc3c13542139b4", size = 18567, upload-time = "2025-05-07T22:47:40.376Z" }, ] [[package]] name = "pluggy" -version = "1.5.0" +version = "1.6.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/96/2d/02d4312c973c6050a18b314a5ad0b3210edb65a906f868e31c111dede4a6/pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1", size = 67955 } +sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/88/5f/e351af9a41f866ac3f1fac4ca0613908d9a41741cfcf2228f4ad853b697d/pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669", size = 20556 }, + { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, ] [[package]] @@ -3831,18 +4008,18 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pywin32", marker = "platform_python_implementation != 'PyPy' and sys_platform == 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/f0/5d/49ba324ad4ae5b1a4caefafbce7a1648540129344481f2ed4ef6bb68d451/plumbum-1.9.0.tar.gz", hash = "sha256:e640062b72642c3873bd5bdc3effed75ba4d3c70ef6b6a7b907357a84d909219", size = 319083 } +sdist = { url = "https://files.pythonhosted.org/packages/f0/5d/49ba324ad4ae5b1a4caefafbce7a1648540129344481f2ed4ef6bb68d451/plumbum-1.9.0.tar.gz", hash = "sha256:e640062b72642c3873bd5bdc3effed75ba4d3c70ef6b6a7b907357a84d909219", size = 319083, upload-time = "2024-10-05T05:59:27.059Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/4f/9d/d03542c93bb3d448406731b80f39c3d5601282f778328c22c77d270f4ed4/plumbum-1.9.0-py3-none-any.whl", hash = "sha256:9fd0d3b0e8d86e4b581af36edf3f3bbe9d1ae15b45b8caab28de1bcb27aaa7f5", size = 127970 }, + { url = "https://files.pythonhosted.org/packages/4f/9d/d03542c93bb3d448406731b80f39c3d5601282f778328c22c77d270f4ed4/plumbum-1.9.0-py3-none-any.whl", hash = "sha256:9fd0d3b0e8d86e4b581af36edf3f3bbe9d1ae15b45b8caab28de1bcb27aaa7f5", size = 127970, upload-time = "2024-10-05T05:59:25.102Z" }, ] [[package]] name = "ply" version = "3.11" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e5/69/882ee5c9d017149285cab114ebeab373308ef0f874fcdac9beb90e0ac4da/ply-3.11.tar.gz", hash = "sha256:00c7c1aaa88358b9c765b6d3000c6eec0ba42abca5351b095321aef446081da3", size = 159130 } +sdist = { url = "https://files.pythonhosted.org/packages/e5/69/882ee5c9d017149285cab114ebeab373308ef0f874fcdac9beb90e0ac4da/ply-3.11.tar.gz", hash = "sha256:00c7c1aaa88358b9c765b6d3000c6eec0ba42abca5351b095321aef446081da3", size = 159130, upload-time = "2018-02-15T19:01:31.097Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a3/58/35da89ee790598a0700ea49b2a66594140f44dec458c07e8e3d4979137fc/ply-3.11-py2.py3-none-any.whl", hash = "sha256:096f9b8350b65ebd2fd1346b12452efe5b9607f7482813ffca50c22722a807ce", size = 49567 }, + { url = "https://files.pythonhosted.org/packages/a3/58/35da89ee790598a0700ea49b2a66594140f44dec458c07e8e3d4979137fc/ply-3.11-py2.py3-none-any.whl", hash = "sha256:096f9b8350b65ebd2fd1346b12452efe5b9607f7482813ffca50c22722a807ce", size = 49567, upload-time = "2018-02-15T19:01:27.172Z" }, ] [[package]] @@ -3852,9 +4029,9 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pywin32", marker = "sys_platform == 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ed/d3/c6c64067759e87af98cc668c1cc75171347d0f1577fab7ca3749134e3cd4/portalocker-2.10.1.tar.gz", hash = "sha256:ef1bf844e878ab08aee7e40184156e1151f228f103aa5c6bd0724cc330960f8f", size = 40891 } +sdist = { url = "https://files.pythonhosted.org/packages/ed/d3/c6c64067759e87af98cc668c1cc75171347d0f1577fab7ca3749134e3cd4/portalocker-2.10.1.tar.gz", hash = "sha256:ef1bf844e878ab08aee7e40184156e1151f228f103aa5c6bd0724cc330960f8f", size = 40891, upload-time = "2024-07-13T23:15:34.86Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/9b/fb/a70a4214956182e0d7a9099ab17d50bfcba1056188e9b14f35b9e2b62a0d/portalocker-2.10.1-py3-none-any.whl", hash = "sha256:53a5984ebc86a025552264b459b46a2086e269b21823cb572f8f28ee759e45bf", size = 18423 }, + { url = "https://files.pythonhosted.org/packages/9b/fb/a70a4214956182e0d7a9099ab17d50bfcba1056188e9b14f35b9e2b62a0d/portalocker-2.10.1-py3-none-any.whl", hash = "sha256:53a5984ebc86a025552264b459b46a2086e269b21823cb572f8f28ee759e45bf", size = 18423, upload-time = "2024-07-13T23:15:32.602Z" }, ] [[package]] @@ -3866,79 +4043,78 @@ dependencies = [ { name = "httpx", extra = ["http2"] }, { name = "pydantic" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d4/4c/1053e2e2571e7f39eef8506db94dbe0a37630db97055228f8bdc2e53651c/postgrest-0.17.2.tar.gz", hash = "sha256:445cd4e4a191e279492549df0c4e827d32f9d01d0852599bb8a6efb0f07fcf78", size = 14604 } +sdist = { url = "https://files.pythonhosted.org/packages/d4/4c/1053e2e2571e7f39eef8506db94dbe0a37630db97055228f8bdc2e53651c/postgrest-0.17.2.tar.gz", hash = "sha256:445cd4e4a191e279492549df0c4e827d32f9d01d0852599bb8a6efb0f07fcf78", size = 14604, upload-time = "2024-10-18T08:58:39.856Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/80/21/3bdf4c51707f50f4a34839bf4431bad53aa603d303ada961dd9e3d943ecc/postgrest-0.17.2-py3-none-any.whl", hash = "sha256:f7c4f448e5a5e2d4c1dcf192edae9d1007c4261e9a6fb5116783a0046846ece2", size = 21669 }, + { url = "https://files.pythonhosted.org/packages/80/21/3bdf4c51707f50f4a34839bf4431bad53aa603d303ada961dd9e3d943ecc/postgrest-0.17.2-py3-none-any.whl", hash = "sha256:f7c4f448e5a5e2d4c1dcf192edae9d1007c4261e9a6fb5116783a0046846ece2", size = 21669, upload-time = "2024-10-18T08:58:38.13Z" }, ] [[package]] name = "posthog" -version = "3.23.0" +version = "4.2.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "backoff" }, { name = "distro" }, - { name = "monotonic" }, { name = "python-dateutil" }, { name = "requests" }, { name = "six" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/5a/dd/30f7d2e992f80fcaedc5b99761e006bbb0b954813243542c480b9576b4be/posthog-3.23.0.tar.gz", hash = "sha256:1ac0305ab6c54a80c4a82c137231f17616bef007bbf474d1a529cda032d808eb", size = 72077 } +sdist = { url = "https://files.pythonhosted.org/packages/ce/5b/2e9890700b7b55a370edbfbe5948eae780d48af9b46ad06ea2e7970576f4/posthog-4.2.0.tar.gz", hash = "sha256:c4abc95de03294be005b3b7e8735e9d7abab88583da26262112bacce64b0c3b5", size = 80727, upload-time = "2025-05-23T23:23:55.943Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/17/1e/aa457f5b15c9a018434dd71567c4a8f09c1701607a1d4daf5f01d6eccb7a/posthog-3.23.0-py2.py3-none-any.whl", hash = "sha256:2b07d06670170ac2e21465dffa8d356722834cc877ab34e583da6e525c1037df", size = 84976 }, + { url = "https://files.pythonhosted.org/packages/51/16/7b6c5844acee2d343d463ee0e3143cd8c7c48a6c0d079a2f7daf0c80b95c/posthog-4.2.0-py2.py3-none-any.whl", hash = "sha256:60c7066caac43e43e326e9196d8c1aadeafc8b0be9e5c108446e352711fa456b", size = 96692, upload-time = "2025-05-23T23:23:54.384Z" }, ] [[package]] name = "prompt-toolkit" -version = "3.0.50" +version = "3.0.51" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "wcwidth" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a1/e1/bd15cb8ffdcfeeb2bdc215de3c3cffca11408d829e4b8416dcfe71ba8854/prompt_toolkit-3.0.50.tar.gz", hash = "sha256:544748f3860a2623ca5cd6d2795e7a14f3d0e1c3c9728359013f79877fc89bab", size = 429087 } +sdist = { url = "https://files.pythonhosted.org/packages/bb/6e/9d084c929dfe9e3bfe0c6a47e31f78a25c54627d64a66e884a8bf5474f1c/prompt_toolkit-3.0.51.tar.gz", hash = "sha256:931a162e3b27fc90c86f1b48bb1fb2c528c2761475e57c9c06de13311c7b54ed", size = 428940, upload-time = "2025-04-15T09:18:47.731Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e4/ea/d836f008d33151c7a1f62caf3d8dd782e4d15f6a43897f64480c2b8de2ad/prompt_toolkit-3.0.50-py3-none-any.whl", hash = "sha256:9b6427eb19e479d98acff65196a307c555eb567989e6d88ebbb1b509d9779198", size = 387816 }, + { url = "https://files.pythonhosted.org/packages/ce/4f/5249960887b1fbe561d9ff265496d170b55a735b76724f10ef19f9e40716/prompt_toolkit-3.0.51-py3-none-any.whl", hash = "sha256:52742911fde84e2d423e2f9a4cf1de7d7ac4e51958f648d9540e0fb8db077b07", size = 387810, upload-time = "2025-04-15T09:18:44.753Z" }, ] [[package]] name = "propcache" version = "0.3.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/07/c8/fdc6686a986feae3541ea23dcaa661bd93972d3940460646c6bb96e21c40/propcache-0.3.1.tar.gz", hash = "sha256:40d980c33765359098837527e18eddefc9a24cea5b45e078a7f3bb5b032c6ecf", size = 43651 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/90/0f/5a5319ee83bd651f75311fcb0c492c21322a7fc8f788e4eef23f44243427/propcache-0.3.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7f30241577d2fef2602113b70ef7231bf4c69a97e04693bde08ddab913ba0ce5", size = 80243 }, - { url = "https://files.pythonhosted.org/packages/ce/84/3db5537e0879942783e2256616ff15d870a11d7ac26541336fe1b673c818/propcache-0.3.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:43593c6772aa12abc3af7784bff4a41ffa921608dd38b77cf1dfd7f5c4e71371", size = 46503 }, - { url = "https://files.pythonhosted.org/packages/e2/c8/b649ed972433c3f0d827d7f0cf9ea47162f4ef8f4fe98c5f3641a0bc63ff/propcache-0.3.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a75801768bbe65499495660b777e018cbe90c7980f07f8aa57d6be79ea6f71da", size = 45934 }, - { url = "https://files.pythonhosted.org/packages/59/f9/4c0a5cf6974c2c43b1a6810c40d889769cc8f84cea676cbe1e62766a45f8/propcache-0.3.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f6f1324db48f001c2ca26a25fa25af60711e09b9aaf4b28488602776f4f9a744", size = 233633 }, - { url = "https://files.pythonhosted.org/packages/e7/64/66f2f4d1b4f0007c6e9078bd95b609b633d3957fe6dd23eac33ebde4b584/propcache-0.3.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5cdb0f3e1eb6dfc9965d19734d8f9c481b294b5274337a8cb5cb01b462dcb7e0", size = 241124 }, - { url = "https://files.pythonhosted.org/packages/aa/bf/7b8c9fd097d511638fa9b6af3d986adbdf567598a567b46338c925144c1b/propcache-0.3.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1eb34d90aac9bfbced9a58b266f8946cb5935869ff01b164573a7634d39fbcb5", size = 240283 }, - { url = "https://files.pythonhosted.org/packages/fa/c9/e85aeeeaae83358e2a1ef32d6ff50a483a5d5248bc38510d030a6f4e2816/propcache-0.3.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f35c7070eeec2cdaac6fd3fe245226ed2a6292d3ee8c938e5bb645b434c5f256", size = 232498 }, - { url = "https://files.pythonhosted.org/packages/8e/66/acb88e1f30ef5536d785c283af2e62931cb934a56a3ecf39105887aa8905/propcache-0.3.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b23c11c2c9e6d4e7300c92e022046ad09b91fd00e36e83c44483df4afa990073", size = 221486 }, - { url = "https://files.pythonhosted.org/packages/f5/f9/233ddb05ffdcaee4448508ee1d70aa7deff21bb41469ccdfcc339f871427/propcache-0.3.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:3e19ea4ea0bf46179f8a3652ac1426e6dcbaf577ce4b4f65be581e237340420d", size = 222675 }, - { url = "https://files.pythonhosted.org/packages/98/b8/eb977e28138f9e22a5a789daf608d36e05ed93093ef12a12441030da800a/propcache-0.3.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:bd39c92e4c8f6cbf5f08257d6360123af72af9f4da75a690bef50da77362d25f", size = 215727 }, - { url = "https://files.pythonhosted.org/packages/89/2d/5f52d9c579f67b8ee1edd9ec073c91b23cc5b7ff7951a1e449e04ed8fdf3/propcache-0.3.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:b0313e8b923b3814d1c4a524c93dfecea5f39fa95601f6a9b1ac96cd66f89ea0", size = 217878 }, - { url = "https://files.pythonhosted.org/packages/7a/fd/5283e5ed8a82b00c7a989b99bb6ea173db1ad750bf0bf8dff08d3f4a4e28/propcache-0.3.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:e861ad82892408487be144906a368ddbe2dc6297074ade2d892341b35c59844a", size = 230558 }, - { url = "https://files.pythonhosted.org/packages/90/38/ab17d75938ef7ac87332c588857422ae126b1c76253f0f5b1242032923ca/propcache-0.3.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:61014615c1274df8da5991a1e5da85a3ccb00c2d4701ac6f3383afd3ca47ab0a", size = 233754 }, - { url = "https://files.pythonhosted.org/packages/06/5d/3b921b9c60659ae464137508d3b4c2b3f52f592ceb1964aa2533b32fcf0b/propcache-0.3.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:71ebe3fe42656a2328ab08933d420df5f3ab121772eef78f2dc63624157f0ed9", size = 226088 }, - { url = "https://files.pythonhosted.org/packages/54/6e/30a11f4417d9266b5a464ac5a8c5164ddc9dd153dfa77bf57918165eb4ae/propcache-0.3.1-cp311-cp311-win32.whl", hash = "sha256:58aa11f4ca8b60113d4b8e32d37e7e78bd8af4d1a5b5cb4979ed856a45e62005", size = 40859 }, - { url = "https://files.pythonhosted.org/packages/1d/3a/8a68dd867da9ca2ee9dfd361093e9cb08cb0f37e5ddb2276f1b5177d7731/propcache-0.3.1-cp311-cp311-win_amd64.whl", hash = "sha256:9532ea0b26a401264b1365146c440a6d78269ed41f83f23818d4b79497aeabe7", size = 45153 }, - { url = "https://files.pythonhosted.org/packages/41/aa/ca78d9be314d1e15ff517b992bebbed3bdfef5b8919e85bf4940e57b6137/propcache-0.3.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:f78eb8422acc93d7b69964012ad7048764bb45a54ba7a39bb9e146c72ea29723", size = 80430 }, - { url = "https://files.pythonhosted.org/packages/1a/d8/f0c17c44d1cda0ad1979af2e593ea290defdde9eaeb89b08abbe02a5e8e1/propcache-0.3.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:89498dd49c2f9a026ee057965cdf8192e5ae070ce7d7a7bd4b66a8e257d0c976", size = 46637 }, - { url = "https://files.pythonhosted.org/packages/ae/bd/c1e37265910752e6e5e8a4c1605d0129e5b7933c3dc3cf1b9b48ed83b364/propcache-0.3.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:09400e98545c998d57d10035ff623266927cb784d13dd2b31fd33b8a5316b85b", size = 46123 }, - { url = "https://files.pythonhosted.org/packages/d4/b0/911eda0865f90c0c7e9f0415d40a5bf681204da5fd7ca089361a64c16b28/propcache-0.3.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aa8efd8c5adc5a2c9d3b952815ff8f7710cefdcaf5f2c36d26aff51aeca2f12f", size = 243031 }, - { url = "https://files.pythonhosted.org/packages/0a/06/0da53397c76a74271621807265b6eb61fb011451b1ddebf43213df763669/propcache-0.3.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c2fe5c910f6007e716a06d269608d307b4f36e7babee5f36533722660e8c4a70", size = 249100 }, - { url = "https://files.pythonhosted.org/packages/f1/eb/13090e05bf6b963fc1653cdc922133ced467cb4b8dab53158db5a37aa21e/propcache-0.3.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a0ab8cf8cdd2194f8ff979a43ab43049b1df0b37aa64ab7eca04ac14429baeb7", size = 250170 }, - { url = "https://files.pythonhosted.org/packages/3b/4c/f72c9e1022b3b043ec7dc475a0f405d4c3e10b9b1d378a7330fecf0652da/propcache-0.3.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:563f9d8c03ad645597b8d010ef4e9eab359faeb11a0a2ac9f7b4bc8c28ebef25", size = 245000 }, - { url = "https://files.pythonhosted.org/packages/e8/fd/970ca0e22acc829f1adf5de3724085e778c1ad8a75bec010049502cb3a86/propcache-0.3.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fb6e0faf8cb6b4beea5d6ed7b5a578254c6d7df54c36ccd3d8b3eb00d6770277", size = 230262 }, - { url = "https://files.pythonhosted.org/packages/c4/42/817289120c6b9194a44f6c3e6b2c3277c5b70bbad39e7df648f177cc3634/propcache-0.3.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1c5c7ab7f2bb3f573d1cb921993006ba2d39e8621019dffb1c5bc94cdbae81e8", size = 236772 }, - { url = "https://files.pythonhosted.org/packages/7c/9c/3b3942b302badd589ad6b672da3ca7b660a6c2f505cafd058133ddc73918/propcache-0.3.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:050b571b2e96ec942898f8eb46ea4bfbb19bd5502424747e83badc2d4a99a44e", size = 231133 }, - { url = "https://files.pythonhosted.org/packages/98/a1/75f6355f9ad039108ff000dfc2e19962c8dea0430da9a1428e7975cf24b2/propcache-0.3.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:e1c4d24b804b3a87e9350f79e2371a705a188d292fd310e663483af6ee6718ee", size = 230741 }, - { url = "https://files.pythonhosted.org/packages/67/0c/3e82563af77d1f8731132166da69fdfd95e71210e31f18edce08a1eb11ea/propcache-0.3.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:e4fe2a6d5ce975c117a6bb1e8ccda772d1e7029c1cca1acd209f91d30fa72815", size = 244047 }, - { url = "https://files.pythonhosted.org/packages/f7/50/9fb7cca01532a08c4d5186d7bb2da6c4c587825c0ae134b89b47c7d62628/propcache-0.3.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:feccd282de1f6322f56f6845bf1207a537227812f0a9bf5571df52bb418d79d5", size = 246467 }, - { url = "https://files.pythonhosted.org/packages/a9/02/ccbcf3e1c604c16cc525309161d57412c23cf2351523aedbb280eb7c9094/propcache-0.3.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ec314cde7314d2dd0510c6787326bbffcbdc317ecee6b7401ce218b3099075a7", size = 241022 }, - { url = "https://files.pythonhosted.org/packages/db/19/e777227545e09ca1e77a6e21274ae9ec45de0f589f0ce3eca2a41f366220/propcache-0.3.1-cp312-cp312-win32.whl", hash = "sha256:7d2d5a0028d920738372630870e7d9644ce437142197f8c827194fca404bf03b", size = 40647 }, - { url = "https://files.pythonhosted.org/packages/24/bb/3b1b01da5dd04c77a204c84e538ff11f624e31431cfde7201d9110b092b1/propcache-0.3.1-cp312-cp312-win_amd64.whl", hash = "sha256:88c423efef9d7a59dae0614eaed718449c09a5ac79a5f224a8b9664d603f04a3", size = 44784 }, - { url = "https://files.pythonhosted.org/packages/b8/d3/c3cb8f1d6ae3b37f83e1de806713a9b3642c5895f0215a62e1a4bd6e5e34/propcache-0.3.1-py3-none-any.whl", hash = "sha256:9a8ecf38de50a7f518c21568c80f985e776397b902f1ce0b01f799aba1608b40", size = 12376 }, +sdist = { url = "https://files.pythonhosted.org/packages/07/c8/fdc6686a986feae3541ea23dcaa661bd93972d3940460646c6bb96e21c40/propcache-0.3.1.tar.gz", hash = "sha256:40d980c33765359098837527e18eddefc9a24cea5b45e078a7f3bb5b032c6ecf", size = 43651, upload-time = "2025-03-26T03:06:12.05Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/90/0f/5a5319ee83bd651f75311fcb0c492c21322a7fc8f788e4eef23f44243427/propcache-0.3.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7f30241577d2fef2602113b70ef7231bf4c69a97e04693bde08ddab913ba0ce5", size = 80243, upload-time = "2025-03-26T03:04:01.912Z" }, + { url = "https://files.pythonhosted.org/packages/ce/84/3db5537e0879942783e2256616ff15d870a11d7ac26541336fe1b673c818/propcache-0.3.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:43593c6772aa12abc3af7784bff4a41ffa921608dd38b77cf1dfd7f5c4e71371", size = 46503, upload-time = "2025-03-26T03:04:03.704Z" }, + { url = "https://files.pythonhosted.org/packages/e2/c8/b649ed972433c3f0d827d7f0cf9ea47162f4ef8f4fe98c5f3641a0bc63ff/propcache-0.3.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a75801768bbe65499495660b777e018cbe90c7980f07f8aa57d6be79ea6f71da", size = 45934, upload-time = "2025-03-26T03:04:05.257Z" }, + { url = "https://files.pythonhosted.org/packages/59/f9/4c0a5cf6974c2c43b1a6810c40d889769cc8f84cea676cbe1e62766a45f8/propcache-0.3.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f6f1324db48f001c2ca26a25fa25af60711e09b9aaf4b28488602776f4f9a744", size = 233633, upload-time = "2025-03-26T03:04:07.044Z" }, + { url = "https://files.pythonhosted.org/packages/e7/64/66f2f4d1b4f0007c6e9078bd95b609b633d3957fe6dd23eac33ebde4b584/propcache-0.3.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5cdb0f3e1eb6dfc9965d19734d8f9c481b294b5274337a8cb5cb01b462dcb7e0", size = 241124, upload-time = "2025-03-26T03:04:08.676Z" }, + { url = "https://files.pythonhosted.org/packages/aa/bf/7b8c9fd097d511638fa9b6af3d986adbdf567598a567b46338c925144c1b/propcache-0.3.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1eb34d90aac9bfbced9a58b266f8946cb5935869ff01b164573a7634d39fbcb5", size = 240283, upload-time = "2025-03-26T03:04:10.172Z" }, + { url = "https://files.pythonhosted.org/packages/fa/c9/e85aeeeaae83358e2a1ef32d6ff50a483a5d5248bc38510d030a6f4e2816/propcache-0.3.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f35c7070eeec2cdaac6fd3fe245226ed2a6292d3ee8c938e5bb645b434c5f256", size = 232498, upload-time = "2025-03-26T03:04:11.616Z" }, + { url = "https://files.pythonhosted.org/packages/8e/66/acb88e1f30ef5536d785c283af2e62931cb934a56a3ecf39105887aa8905/propcache-0.3.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b23c11c2c9e6d4e7300c92e022046ad09b91fd00e36e83c44483df4afa990073", size = 221486, upload-time = "2025-03-26T03:04:13.102Z" }, + { url = "https://files.pythonhosted.org/packages/f5/f9/233ddb05ffdcaee4448508ee1d70aa7deff21bb41469ccdfcc339f871427/propcache-0.3.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:3e19ea4ea0bf46179f8a3652ac1426e6dcbaf577ce4b4f65be581e237340420d", size = 222675, upload-time = "2025-03-26T03:04:14.658Z" }, + { url = "https://files.pythonhosted.org/packages/98/b8/eb977e28138f9e22a5a789daf608d36e05ed93093ef12a12441030da800a/propcache-0.3.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:bd39c92e4c8f6cbf5f08257d6360123af72af9f4da75a690bef50da77362d25f", size = 215727, upload-time = "2025-03-26T03:04:16.207Z" }, + { url = "https://files.pythonhosted.org/packages/89/2d/5f52d9c579f67b8ee1edd9ec073c91b23cc5b7ff7951a1e449e04ed8fdf3/propcache-0.3.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:b0313e8b923b3814d1c4a524c93dfecea5f39fa95601f6a9b1ac96cd66f89ea0", size = 217878, upload-time = "2025-03-26T03:04:18.11Z" }, + { url = "https://files.pythonhosted.org/packages/7a/fd/5283e5ed8a82b00c7a989b99bb6ea173db1ad750bf0bf8dff08d3f4a4e28/propcache-0.3.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:e861ad82892408487be144906a368ddbe2dc6297074ade2d892341b35c59844a", size = 230558, upload-time = "2025-03-26T03:04:19.562Z" }, + { url = "https://files.pythonhosted.org/packages/90/38/ab17d75938ef7ac87332c588857422ae126b1c76253f0f5b1242032923ca/propcache-0.3.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:61014615c1274df8da5991a1e5da85a3ccb00c2d4701ac6f3383afd3ca47ab0a", size = 233754, upload-time = "2025-03-26T03:04:21.065Z" }, + { url = "https://files.pythonhosted.org/packages/06/5d/3b921b9c60659ae464137508d3b4c2b3f52f592ceb1964aa2533b32fcf0b/propcache-0.3.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:71ebe3fe42656a2328ab08933d420df5f3ab121772eef78f2dc63624157f0ed9", size = 226088, upload-time = "2025-03-26T03:04:22.718Z" }, + { url = "https://files.pythonhosted.org/packages/54/6e/30a11f4417d9266b5a464ac5a8c5164ddc9dd153dfa77bf57918165eb4ae/propcache-0.3.1-cp311-cp311-win32.whl", hash = "sha256:58aa11f4ca8b60113d4b8e32d37e7e78bd8af4d1a5b5cb4979ed856a45e62005", size = 40859, upload-time = "2025-03-26T03:04:24.039Z" }, + { url = "https://files.pythonhosted.org/packages/1d/3a/8a68dd867da9ca2ee9dfd361093e9cb08cb0f37e5ddb2276f1b5177d7731/propcache-0.3.1-cp311-cp311-win_amd64.whl", hash = "sha256:9532ea0b26a401264b1365146c440a6d78269ed41f83f23818d4b79497aeabe7", size = 45153, upload-time = "2025-03-26T03:04:25.211Z" }, + { url = "https://files.pythonhosted.org/packages/41/aa/ca78d9be314d1e15ff517b992bebbed3bdfef5b8919e85bf4940e57b6137/propcache-0.3.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:f78eb8422acc93d7b69964012ad7048764bb45a54ba7a39bb9e146c72ea29723", size = 80430, upload-time = "2025-03-26T03:04:26.436Z" }, + { url = "https://files.pythonhosted.org/packages/1a/d8/f0c17c44d1cda0ad1979af2e593ea290defdde9eaeb89b08abbe02a5e8e1/propcache-0.3.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:89498dd49c2f9a026ee057965cdf8192e5ae070ce7d7a7bd4b66a8e257d0c976", size = 46637, upload-time = "2025-03-26T03:04:27.932Z" }, + { url = "https://files.pythonhosted.org/packages/ae/bd/c1e37265910752e6e5e8a4c1605d0129e5b7933c3dc3cf1b9b48ed83b364/propcache-0.3.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:09400e98545c998d57d10035ff623266927cb784d13dd2b31fd33b8a5316b85b", size = 46123, upload-time = "2025-03-26T03:04:30.659Z" }, + { url = "https://files.pythonhosted.org/packages/d4/b0/911eda0865f90c0c7e9f0415d40a5bf681204da5fd7ca089361a64c16b28/propcache-0.3.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aa8efd8c5adc5a2c9d3b952815ff8f7710cefdcaf5f2c36d26aff51aeca2f12f", size = 243031, upload-time = "2025-03-26T03:04:31.977Z" }, + { url = "https://files.pythonhosted.org/packages/0a/06/0da53397c76a74271621807265b6eb61fb011451b1ddebf43213df763669/propcache-0.3.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c2fe5c910f6007e716a06d269608d307b4f36e7babee5f36533722660e8c4a70", size = 249100, upload-time = "2025-03-26T03:04:33.45Z" }, + { url = "https://files.pythonhosted.org/packages/f1/eb/13090e05bf6b963fc1653cdc922133ced467cb4b8dab53158db5a37aa21e/propcache-0.3.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a0ab8cf8cdd2194f8ff979a43ab43049b1df0b37aa64ab7eca04ac14429baeb7", size = 250170, upload-time = "2025-03-26T03:04:35.542Z" }, + { url = "https://files.pythonhosted.org/packages/3b/4c/f72c9e1022b3b043ec7dc475a0f405d4c3e10b9b1d378a7330fecf0652da/propcache-0.3.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:563f9d8c03ad645597b8d010ef4e9eab359faeb11a0a2ac9f7b4bc8c28ebef25", size = 245000, upload-time = "2025-03-26T03:04:37.501Z" }, + { url = "https://files.pythonhosted.org/packages/e8/fd/970ca0e22acc829f1adf5de3724085e778c1ad8a75bec010049502cb3a86/propcache-0.3.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fb6e0faf8cb6b4beea5d6ed7b5a578254c6d7df54c36ccd3d8b3eb00d6770277", size = 230262, upload-time = "2025-03-26T03:04:39.532Z" }, + { url = "https://files.pythonhosted.org/packages/c4/42/817289120c6b9194a44f6c3e6b2c3277c5b70bbad39e7df648f177cc3634/propcache-0.3.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1c5c7ab7f2bb3f573d1cb921993006ba2d39e8621019dffb1c5bc94cdbae81e8", size = 236772, upload-time = "2025-03-26T03:04:41.109Z" }, + { url = "https://files.pythonhosted.org/packages/7c/9c/3b3942b302badd589ad6b672da3ca7b660a6c2f505cafd058133ddc73918/propcache-0.3.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:050b571b2e96ec942898f8eb46ea4bfbb19bd5502424747e83badc2d4a99a44e", size = 231133, upload-time = "2025-03-26T03:04:42.544Z" }, + { url = "https://files.pythonhosted.org/packages/98/a1/75f6355f9ad039108ff000dfc2e19962c8dea0430da9a1428e7975cf24b2/propcache-0.3.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:e1c4d24b804b3a87e9350f79e2371a705a188d292fd310e663483af6ee6718ee", size = 230741, upload-time = "2025-03-26T03:04:44.06Z" }, + { url = "https://files.pythonhosted.org/packages/67/0c/3e82563af77d1f8731132166da69fdfd95e71210e31f18edce08a1eb11ea/propcache-0.3.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:e4fe2a6d5ce975c117a6bb1e8ccda772d1e7029c1cca1acd209f91d30fa72815", size = 244047, upload-time = "2025-03-26T03:04:45.983Z" }, + { url = "https://files.pythonhosted.org/packages/f7/50/9fb7cca01532a08c4d5186d7bb2da6c4c587825c0ae134b89b47c7d62628/propcache-0.3.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:feccd282de1f6322f56f6845bf1207a537227812f0a9bf5571df52bb418d79d5", size = 246467, upload-time = "2025-03-26T03:04:47.699Z" }, + { url = "https://files.pythonhosted.org/packages/a9/02/ccbcf3e1c604c16cc525309161d57412c23cf2351523aedbb280eb7c9094/propcache-0.3.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ec314cde7314d2dd0510c6787326bbffcbdc317ecee6b7401ce218b3099075a7", size = 241022, upload-time = "2025-03-26T03:04:49.195Z" }, + { url = "https://files.pythonhosted.org/packages/db/19/e777227545e09ca1e77a6e21274ae9ec45de0f589f0ce3eca2a41f366220/propcache-0.3.1-cp312-cp312-win32.whl", hash = "sha256:7d2d5a0028d920738372630870e7d9644ce437142197f8c827194fca404bf03b", size = 40647, upload-time = "2025-03-26T03:04:50.595Z" }, + { url = "https://files.pythonhosted.org/packages/24/bb/3b1b01da5dd04c77a204c84e538ff11f624e31431cfde7201d9110b092b1/propcache-0.3.1-cp312-cp312-win_amd64.whl", hash = "sha256:88c423efef9d7a59dae0614eaed718449c09a5ac79a5f224a8b9664d603f04a3", size = 44784, upload-time = "2025-03-26T03:04:51.791Z" }, + { url = "https://files.pythonhosted.org/packages/b8/d3/c3cb8f1d6ae3b37f83e1de806713a9b3642c5895f0215a62e1a4bd6e5e34/propcache-0.3.1-py3-none-any.whl", hash = "sha256:9a8ecf38de50a7f518c21568c80f985e776397b902f1ce0b01f799aba1608b40", size = 12376, upload-time = "2025-03-26T03:06:10.5Z" }, ] [[package]] @@ -3948,103 +4124,103 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "protobuf" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/f4/ac/87285f15f7cce6d4a008f33f1757fb5a13611ea8914eb58c3d0d26243468/proto_plus-1.26.1.tar.gz", hash = "sha256:21a515a4c4c0088a773899e23c7bbade3d18f9c66c73edd4c7ee3816bc96a012", size = 56142 } +sdist = { url = "https://files.pythonhosted.org/packages/f4/ac/87285f15f7cce6d4a008f33f1757fb5a13611ea8914eb58c3d0d26243468/proto_plus-1.26.1.tar.gz", hash = "sha256:21a515a4c4c0088a773899e23c7bbade3d18f9c66c73edd4c7ee3816bc96a012", size = 56142, upload-time = "2025-03-10T15:54:38.843Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/4e/6d/280c4c2ce28b1593a19ad5239c8b826871fc6ec275c21afc8e1820108039/proto_plus-1.26.1-py3-none-any.whl", hash = "sha256:13285478c2dcf2abb829db158e1047e2f1e8d63a077d94263c2b88b043c75a66", size = 50163 }, + { url = "https://files.pythonhosted.org/packages/4e/6d/280c4c2ce28b1593a19ad5239c8b826871fc6ec275c21afc8e1820108039/proto_plus-1.26.1-py3-none-any.whl", hash = "sha256:13285478c2dcf2abb829db158e1047e2f1e8d63a077d94263c2b88b043c75a66", size = 50163, upload-time = "2025-03-10T15:54:37.335Z" }, ] [[package]] name = "protobuf" -version = "4.25.6" +version = "4.25.8" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/48/d5/cccc7e82bbda9909ced3e7a441a24205ea07fea4ce23a772743c0c7611fa/protobuf-4.25.6.tar.gz", hash = "sha256:f8cfbae7c5afd0d0eaccbe73267339bff605a2315860bb1ba08eb66670a9a91f", size = 380631 } +sdist = { url = "https://files.pythonhosted.org/packages/df/01/34c8d2b6354906d728703cb9d546a0e534de479e25f1b581e4094c4a85cc/protobuf-4.25.8.tar.gz", hash = "sha256:6135cf8affe1fc6f76cced2641e4ea8d3e59518d1f24ae41ba97bcad82d397cd", size = 380920, upload-time = "2025-05-28T14:22:25.153Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/42/41/0ff3559d9a0fbdb37c9452f2b84e61f7784d8d7b9850182c7ef493f523ee/protobuf-4.25.6-cp310-abi3-win32.whl", hash = "sha256:61df6b5786e2b49fc0055f636c1e8f0aff263808bb724b95b164685ac1bcc13a", size = 392454 }, - { url = "https://files.pythonhosted.org/packages/79/84/c700d6c3f3be770495b08a1c035e330497a31420e4a39a24c22c02cefc6c/protobuf-4.25.6-cp310-abi3-win_amd64.whl", hash = "sha256:b8f837bfb77513fe0e2f263250f423217a173b6d85135be4d81e96a4653bcd3c", size = 413443 }, - { url = "https://files.pythonhosted.org/packages/b7/03/361e87cc824452376c2abcef0eabd18da78a7439479ec6541cf29076a4dc/protobuf-4.25.6-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:6d4381f2417606d7e01750e2729fe6fbcda3f9883aa0c32b51d23012bded6c91", size = 394246 }, - { url = "https://files.pythonhosted.org/packages/64/d5/7dbeb69b74fa88f297c6d8f11b7c9cef0c2e2fb1fdf155c2ca5775cfa998/protobuf-4.25.6-cp37-abi3-manylinux2014_aarch64.whl", hash = "sha256:5dd800da412ba7f6f26d2c08868a5023ce624e1fdb28bccca2dc957191e81fb5", size = 293714 }, - { url = "https://files.pythonhosted.org/packages/d4/f0/6d5c100f6b18d973e86646aa5fc09bc12ee88a28684a56fd95511bceee68/protobuf-4.25.6-cp37-abi3-manylinux2014_x86_64.whl", hash = "sha256:4434ff8bb5576f9e0c78f47c41cdf3a152c0b44de475784cd3fd170aef16205a", size = 294634 }, - { url = "https://files.pythonhosted.org/packages/71/eb/be11a1244d0e58ee04c17a1f939b100199063e26ecca8262c04827fe0bf5/protobuf-4.25.6-py3-none-any.whl", hash = "sha256:07972021c8e30b870cfc0863409d033af940213e0e7f64e27fe017b929d2c9f7", size = 156466 }, + { url = "https://files.pythonhosted.org/packages/45/ff/05f34305fe6b85bbfbecbc559d423a5985605cad5eda4f47eae9e9c9c5c5/protobuf-4.25.8-cp310-abi3-win32.whl", hash = "sha256:504435d831565f7cfac9f0714440028907f1975e4bed228e58e72ecfff58a1e0", size = 392745, upload-time = "2025-05-28T14:22:10.524Z" }, + { url = "https://files.pythonhosted.org/packages/08/35/8b8a8405c564caf4ba835b1fdf554da869954712b26d8f2a98c0e434469b/protobuf-4.25.8-cp310-abi3-win_amd64.whl", hash = "sha256:bd551eb1fe1d7e92c1af1d75bdfa572eff1ab0e5bf1736716814cdccdb2360f9", size = 413736, upload-time = "2025-05-28T14:22:13.156Z" }, + { url = "https://files.pythonhosted.org/packages/28/d7/ab27049a035b258dab43445eb6ec84a26277b16105b277cbe0a7698bdc6c/protobuf-4.25.8-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:ca809b42f4444f144f2115c4c1a747b9a404d590f18f37e9402422033e464e0f", size = 394537, upload-time = "2025-05-28T14:22:14.768Z" }, + { url = "https://files.pythonhosted.org/packages/bd/6d/a4a198b61808dd3d1ee187082ccc21499bc949d639feb948961b48be9a7e/protobuf-4.25.8-cp37-abi3-manylinux2014_aarch64.whl", hash = "sha256:9ad7ef62d92baf5a8654fbb88dac7fa5594cfa70fd3440488a5ca3bfc6d795a7", size = 294005, upload-time = "2025-05-28T14:22:16.052Z" }, + { url = "https://files.pythonhosted.org/packages/d6/c6/c9deaa6e789b6fc41b88ccbdfe7a42d2b82663248b715f55aa77fbc00724/protobuf-4.25.8-cp37-abi3-manylinux2014_x86_64.whl", hash = "sha256:83e6e54e93d2b696a92cad6e6efc924f3850f82b52e1563778dfab8b355101b0", size = 294924, upload-time = "2025-05-28T14:22:17.105Z" }, + { url = "https://files.pythonhosted.org/packages/0c/c1/6aece0ab5209981a70cd186f164c133fdba2f51e124ff92b73de7fd24d78/protobuf-4.25.8-py3-none-any.whl", hash = "sha256:15a0af558aa3b13efef102ae6e4f3efac06f1eea11afb3a57db2901447d9fb59", size = 156757, upload-time = "2025-05-28T14:22:24.135Z" }, ] [[package]] name = "psutil" version = "7.0.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/2a/80/336820c1ad9286a4ded7e845b2eccfcb27851ab8ac6abece774a6ff4d3de/psutil-7.0.0.tar.gz", hash = "sha256:7be9c3eba38beccb6495ea33afd982a44074b78f28c434a1f51cc07fd315c456", size = 497003 } +sdist = { url = "https://files.pythonhosted.org/packages/2a/80/336820c1ad9286a4ded7e845b2eccfcb27851ab8ac6abece774a6ff4d3de/psutil-7.0.0.tar.gz", hash = "sha256:7be9c3eba38beccb6495ea33afd982a44074b78f28c434a1f51cc07fd315c456", size = 497003, upload-time = "2025-02-13T21:54:07.946Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ed/e6/2d26234410f8b8abdbf891c9da62bee396583f713fb9f3325a4760875d22/psutil-7.0.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:101d71dc322e3cffd7cea0650b09b3d08b8e7c4109dd6809fe452dfd00e58b25", size = 238051 }, - { url = "https://files.pythonhosted.org/packages/04/8b/30f930733afe425e3cbfc0e1468a30a18942350c1a8816acfade80c005c4/psutil-7.0.0-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:39db632f6bb862eeccf56660871433e111b6ea58f2caea825571951d4b6aa3da", size = 239535 }, - { url = "https://files.pythonhosted.org/packages/2a/ed/d362e84620dd22876b55389248e522338ed1bf134a5edd3b8231d7207f6d/psutil-7.0.0-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1fcee592b4c6f146991ca55919ea3d1f8926497a713ed7faaf8225e174581e91", size = 275004 }, - { url = "https://files.pythonhosted.org/packages/bf/b9/b0eb3f3cbcb734d930fdf839431606844a825b23eaf9a6ab371edac8162c/psutil-7.0.0-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b1388a4f6875d7e2aff5c4ca1cc16c545ed41dd8bb596cefea80111db353a34", size = 277986 }, - { url = "https://files.pythonhosted.org/packages/eb/a2/709e0fe2f093556c17fbafda93ac032257242cabcc7ff3369e2cb76a97aa/psutil-7.0.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5f098451abc2828f7dc6b58d44b532b22f2088f4999a937557b603ce72b1993", size = 279544 }, - { url = "https://files.pythonhosted.org/packages/50/e6/eecf58810b9d12e6427369784efe814a1eec0f492084ce8eb8f4d89d6d61/psutil-7.0.0-cp37-abi3-win32.whl", hash = "sha256:ba3fcef7523064a6c9da440fc4d6bd07da93ac726b5733c29027d7dc95b39d99", size = 241053 }, - { url = "https://files.pythonhosted.org/packages/50/1b/6921afe68c74868b4c9fa424dad3be35b095e16687989ebbb50ce4fceb7c/psutil-7.0.0-cp37-abi3-win_amd64.whl", hash = "sha256:4cf3d4eb1aa9b348dec30105c55cd9b7d4629285735a102beb4441e38db90553", size = 244885 }, + { url = "https://files.pythonhosted.org/packages/ed/e6/2d26234410f8b8abdbf891c9da62bee396583f713fb9f3325a4760875d22/psutil-7.0.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:101d71dc322e3cffd7cea0650b09b3d08b8e7c4109dd6809fe452dfd00e58b25", size = 238051, upload-time = "2025-02-13T21:54:12.36Z" }, + { url = "https://files.pythonhosted.org/packages/04/8b/30f930733afe425e3cbfc0e1468a30a18942350c1a8816acfade80c005c4/psutil-7.0.0-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:39db632f6bb862eeccf56660871433e111b6ea58f2caea825571951d4b6aa3da", size = 239535, upload-time = "2025-02-13T21:54:16.07Z" }, + { url = "https://files.pythonhosted.org/packages/2a/ed/d362e84620dd22876b55389248e522338ed1bf134a5edd3b8231d7207f6d/psutil-7.0.0-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1fcee592b4c6f146991ca55919ea3d1f8926497a713ed7faaf8225e174581e91", size = 275004, upload-time = "2025-02-13T21:54:18.662Z" }, + { url = "https://files.pythonhosted.org/packages/bf/b9/b0eb3f3cbcb734d930fdf839431606844a825b23eaf9a6ab371edac8162c/psutil-7.0.0-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b1388a4f6875d7e2aff5c4ca1cc16c545ed41dd8bb596cefea80111db353a34", size = 277986, upload-time = "2025-02-13T21:54:21.811Z" }, + { url = "https://files.pythonhosted.org/packages/eb/a2/709e0fe2f093556c17fbafda93ac032257242cabcc7ff3369e2cb76a97aa/psutil-7.0.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5f098451abc2828f7dc6b58d44b532b22f2088f4999a937557b603ce72b1993", size = 279544, upload-time = "2025-02-13T21:54:24.68Z" }, + { url = "https://files.pythonhosted.org/packages/50/e6/eecf58810b9d12e6427369784efe814a1eec0f492084ce8eb8f4d89d6d61/psutil-7.0.0-cp37-abi3-win32.whl", hash = "sha256:ba3fcef7523064a6c9da440fc4d6bd07da93ac726b5733c29027d7dc95b39d99", size = 241053, upload-time = "2025-02-13T21:54:34.31Z" }, + { url = "https://files.pythonhosted.org/packages/50/1b/6921afe68c74868b4c9fa424dad3be35b095e16687989ebbb50ce4fceb7c/psutil-7.0.0-cp37-abi3-win_amd64.whl", hash = "sha256:4cf3d4eb1aa9b348dec30105c55cd9b7d4629285735a102beb4441e38db90553", size = 244885, upload-time = "2025-02-13T21:54:37.486Z" }, ] [[package]] name = "psycogreen" version = "1.0.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/eb/72/4a7965cf54e341006ad74cdc72cd6572c789bc4f4e3fadc78672f1fbcfbd/psycogreen-1.0.2.tar.gz", hash = "sha256:c429845a8a49cf2f76b71265008760bcd7c7c77d80b806db4dc81116dbcd130d", size = 5411 } +sdist = { url = "https://files.pythonhosted.org/packages/eb/72/4a7965cf54e341006ad74cdc72cd6572c789bc4f4e3fadc78672f1fbcfbd/psycogreen-1.0.2.tar.gz", hash = "sha256:c429845a8a49cf2f76b71265008760bcd7c7c77d80b806db4dc81116dbcd130d", size = 5411, upload-time = "2020-02-22T19:55:22.02Z" } [[package]] name = "psycopg2-binary" version = "2.9.10" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/cb/0e/bdc8274dc0585090b4e3432267d7be4dfbfd8971c0fa59167c711105a6bf/psycopg2-binary-2.9.10.tar.gz", hash = "sha256:4b3df0e6990aa98acda57d983942eff13d824135fe2250e6522edaa782a06de2", size = 385764 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/9c/8f/9feb01291d0d7a0a4c6a6bab24094135c2b59c6a81943752f632c75896d6/psycopg2_binary-2.9.10-cp311-cp311-macosx_12_0_x86_64.whl", hash = "sha256:04392983d0bb89a8717772a193cfaac58871321e3ec69514e1c4e0d4957b5aff", size = 3043397 }, - { url = "https://files.pythonhosted.org/packages/15/30/346e4683532011561cd9c8dfeac6a8153dd96452fee0b12666058ab7893c/psycopg2_binary-2.9.10-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:1a6784f0ce3fec4edc64e985865c17778514325074adf5ad8f80636cd029ef7c", size = 3274806 }, - { url = "https://files.pythonhosted.org/packages/66/6e/4efebe76f76aee7ec99166b6c023ff8abdc4e183f7b70913d7c047701b79/psycopg2_binary-2.9.10-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b5f86c56eeb91dc3135b3fd8a95dc7ae14c538a2f3ad77a19645cf55bab1799c", size = 2851370 }, - { url = "https://files.pythonhosted.org/packages/7f/fd/ff83313f86b50f7ca089b161b8e0a22bb3c319974096093cd50680433fdb/psycopg2_binary-2.9.10-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2b3d2491d4d78b6b14f76881905c7a8a8abcf974aad4a8a0b065273a0ed7a2cb", size = 3080780 }, - { url = "https://files.pythonhosted.org/packages/e6/c4/bfadd202dcda8333a7ccafdc51c541dbdfce7c2c7cda89fa2374455d795f/psycopg2_binary-2.9.10-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2286791ececda3a723d1910441c793be44625d86d1a4e79942751197f4d30341", size = 3264583 }, - { url = "https://files.pythonhosted.org/packages/5d/f1/09f45ac25e704ac954862581f9f9ae21303cc5ded3d0b775532b407f0e90/psycopg2_binary-2.9.10-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:512d29bb12608891e349af6a0cccedce51677725a921c07dba6342beaf576f9a", size = 3019831 }, - { url = "https://files.pythonhosted.org/packages/9e/2e/9beaea078095cc558f215e38f647c7114987d9febfc25cb2beed7c3582a5/psycopg2_binary-2.9.10-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:5a507320c58903967ef7384355a4da7ff3f28132d679aeb23572753cbf2ec10b", size = 2871822 }, - { url = "https://files.pythonhosted.org/packages/01/9e/ef93c5d93f3dc9fc92786ffab39e323b9aed066ba59fdc34cf85e2722271/psycopg2_binary-2.9.10-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:6d4fa1079cab9018f4d0bd2db307beaa612b0d13ba73b5c6304b9fe2fb441ff7", size = 2820975 }, - { url = "https://files.pythonhosted.org/packages/a5/f0/049e9631e3268fe4c5a387f6fc27e267ebe199acf1bc1bc9cbde4bd6916c/psycopg2_binary-2.9.10-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:851485a42dbb0bdc1edcdabdb8557c09c9655dfa2ca0460ff210522e073e319e", size = 2919320 }, - { url = "https://files.pythonhosted.org/packages/dc/9a/bcb8773b88e45fb5a5ea8339e2104d82c863a3b8558fbb2aadfe66df86b3/psycopg2_binary-2.9.10-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:35958ec9e46432d9076286dda67942ed6d968b9c3a6a2fd62b48939d1d78bf68", size = 2957617 }, - { url = "https://files.pythonhosted.org/packages/e2/6b/144336a9bf08a67d217b3af3246abb1d027095dab726f0687f01f43e8c03/psycopg2_binary-2.9.10-cp311-cp311-win32.whl", hash = "sha256:ecced182e935529727401b24d76634a357c71c9275b356efafd8a2a91ec07392", size = 1024618 }, - { url = "https://files.pythonhosted.org/packages/61/69/3b3d7bd583c6d3cbe5100802efa5beacaacc86e37b653fc708bf3d6853b8/psycopg2_binary-2.9.10-cp311-cp311-win_amd64.whl", hash = "sha256:ee0e8c683a7ff25d23b55b11161c2663d4b099770f6085ff0a20d4505778d6b4", size = 1163816 }, - { url = "https://files.pythonhosted.org/packages/49/7d/465cc9795cf76f6d329efdafca74693714556ea3891813701ac1fee87545/psycopg2_binary-2.9.10-cp312-cp312-macosx_12_0_x86_64.whl", hash = "sha256:880845dfe1f85d9d5f7c412efea7a08946a46894537e4e5d091732eb1d34d9a0", size = 3044771 }, - { url = "https://files.pythonhosted.org/packages/8b/31/6d225b7b641a1a2148e3ed65e1aa74fc86ba3fee850545e27be9e1de893d/psycopg2_binary-2.9.10-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:9440fa522a79356aaa482aa4ba500b65f28e5d0e63b801abf6aa152a29bd842a", size = 3275336 }, - { url = "https://files.pythonhosted.org/packages/30/b7/a68c2b4bff1cbb1728e3ec864b2d92327c77ad52edcd27922535a8366f68/psycopg2_binary-2.9.10-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e3923c1d9870c49a2d44f795df0c889a22380d36ef92440ff618ec315757e539", size = 2851637 }, - { url = "https://files.pythonhosted.org/packages/0b/b1/cfedc0e0e6f9ad61f8657fd173b2f831ce261c02a08c0b09c652b127d813/psycopg2_binary-2.9.10-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7b2c956c028ea5de47ff3a8d6b3cc3330ab45cf0b7c3da35a2d6ff8420896526", size = 3082097 }, - { url = "https://files.pythonhosted.org/packages/18/ed/0a8e4153c9b769f59c02fb5e7914f20f0b2483a19dae7bf2db54b743d0d0/psycopg2_binary-2.9.10-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f758ed67cab30b9a8d2833609513ce4d3bd027641673d4ebc9c067e4d208eec1", size = 3264776 }, - { url = "https://files.pythonhosted.org/packages/10/db/d09da68c6a0cdab41566b74e0a6068a425f077169bed0946559b7348ebe9/psycopg2_binary-2.9.10-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8cd9b4f2cfab88ed4a9106192de509464b75a906462fb846b936eabe45c2063e", size = 3020968 }, - { url = "https://files.pythonhosted.org/packages/94/28/4d6f8c255f0dfffb410db2b3f9ac5218d959a66c715c34cac31081e19b95/psycopg2_binary-2.9.10-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6dc08420625b5a20b53551c50deae6e231e6371194fa0651dbe0fb206452ae1f", size = 2872334 }, - { url = "https://files.pythonhosted.org/packages/05/f7/20d7bf796593c4fea95e12119d6cc384ff1f6141a24fbb7df5a668d29d29/psycopg2_binary-2.9.10-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:d7cd730dfa7c36dbe8724426bf5612798734bff2d3c3857f36f2733f5bfc7c00", size = 2822722 }, - { url = "https://files.pythonhosted.org/packages/4d/e4/0c407ae919ef626dbdb32835a03b6737013c3cc7240169843965cada2bdf/psycopg2_binary-2.9.10-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:155e69561d54d02b3c3209545fb08938e27889ff5a10c19de8d23eb5a41be8a5", size = 2920132 }, - { url = "https://files.pythonhosted.org/packages/2d/70/aa69c9f69cf09a01da224909ff6ce8b68faeef476f00f7ec377e8f03be70/psycopg2_binary-2.9.10-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c3cc28a6fd5a4a26224007712e79b81dbaee2ffb90ff406256158ec4d7b52b47", size = 2959312 }, - { url = "https://files.pythonhosted.org/packages/d3/bd/213e59854fafe87ba47814bf413ace0dcee33a89c8c8c814faca6bc7cf3c/psycopg2_binary-2.9.10-cp312-cp312-win32.whl", hash = "sha256:ec8a77f521a17506a24a5f626cb2aee7850f9b69a0afe704586f63a464f3cd64", size = 1025191 }, - { url = "https://files.pythonhosted.org/packages/92/29/06261ea000e2dc1e22907dbbc483a1093665509ea586b29b8986a0e56733/psycopg2_binary-2.9.10-cp312-cp312-win_amd64.whl", hash = "sha256:18c5ee682b9c6dd3696dad6e54cc7ff3a1a9020df6a5c0f861ef8bfd338c3ca0", size = 1164031 }, +sdist = { url = "https://files.pythonhosted.org/packages/cb/0e/bdc8274dc0585090b4e3432267d7be4dfbfd8971c0fa59167c711105a6bf/psycopg2-binary-2.9.10.tar.gz", hash = "sha256:4b3df0e6990aa98acda57d983942eff13d824135fe2250e6522edaa782a06de2", size = 385764, upload-time = "2024-10-16T11:24:58.126Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9c/8f/9feb01291d0d7a0a4c6a6bab24094135c2b59c6a81943752f632c75896d6/psycopg2_binary-2.9.10-cp311-cp311-macosx_12_0_x86_64.whl", hash = "sha256:04392983d0bb89a8717772a193cfaac58871321e3ec69514e1c4e0d4957b5aff", size = 3043397, upload-time = "2024-10-16T11:19:40.033Z" }, + { url = "https://files.pythonhosted.org/packages/15/30/346e4683532011561cd9c8dfeac6a8153dd96452fee0b12666058ab7893c/psycopg2_binary-2.9.10-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:1a6784f0ce3fec4edc64e985865c17778514325074adf5ad8f80636cd029ef7c", size = 3274806, upload-time = "2024-10-16T11:19:43.5Z" }, + { url = "https://files.pythonhosted.org/packages/66/6e/4efebe76f76aee7ec99166b6c023ff8abdc4e183f7b70913d7c047701b79/psycopg2_binary-2.9.10-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b5f86c56eeb91dc3135b3fd8a95dc7ae14c538a2f3ad77a19645cf55bab1799c", size = 2851370, upload-time = "2024-10-16T11:19:46.986Z" }, + { url = "https://files.pythonhosted.org/packages/7f/fd/ff83313f86b50f7ca089b161b8e0a22bb3c319974096093cd50680433fdb/psycopg2_binary-2.9.10-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2b3d2491d4d78b6b14f76881905c7a8a8abcf974aad4a8a0b065273a0ed7a2cb", size = 3080780, upload-time = "2024-10-16T11:19:50.242Z" }, + { url = "https://files.pythonhosted.org/packages/e6/c4/bfadd202dcda8333a7ccafdc51c541dbdfce7c2c7cda89fa2374455d795f/psycopg2_binary-2.9.10-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2286791ececda3a723d1910441c793be44625d86d1a4e79942751197f4d30341", size = 3264583, upload-time = "2024-10-16T11:19:54.424Z" }, + { url = "https://files.pythonhosted.org/packages/5d/f1/09f45ac25e704ac954862581f9f9ae21303cc5ded3d0b775532b407f0e90/psycopg2_binary-2.9.10-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:512d29bb12608891e349af6a0cccedce51677725a921c07dba6342beaf576f9a", size = 3019831, upload-time = "2024-10-16T11:19:57.762Z" }, + { url = "https://files.pythonhosted.org/packages/9e/2e/9beaea078095cc558f215e38f647c7114987d9febfc25cb2beed7c3582a5/psycopg2_binary-2.9.10-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:5a507320c58903967ef7384355a4da7ff3f28132d679aeb23572753cbf2ec10b", size = 2871822, upload-time = "2024-10-16T11:20:04.693Z" }, + { url = "https://files.pythonhosted.org/packages/01/9e/ef93c5d93f3dc9fc92786ffab39e323b9aed066ba59fdc34cf85e2722271/psycopg2_binary-2.9.10-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:6d4fa1079cab9018f4d0bd2db307beaa612b0d13ba73b5c6304b9fe2fb441ff7", size = 2820975, upload-time = "2024-10-16T11:20:11.401Z" }, + { url = "https://files.pythonhosted.org/packages/a5/f0/049e9631e3268fe4c5a387f6fc27e267ebe199acf1bc1bc9cbde4bd6916c/psycopg2_binary-2.9.10-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:851485a42dbb0bdc1edcdabdb8557c09c9655dfa2ca0460ff210522e073e319e", size = 2919320, upload-time = "2024-10-16T11:20:17.959Z" }, + { url = "https://files.pythonhosted.org/packages/dc/9a/bcb8773b88e45fb5a5ea8339e2104d82c863a3b8558fbb2aadfe66df86b3/psycopg2_binary-2.9.10-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:35958ec9e46432d9076286dda67942ed6d968b9c3a6a2fd62b48939d1d78bf68", size = 2957617, upload-time = "2024-10-16T11:20:24.711Z" }, + { url = "https://files.pythonhosted.org/packages/e2/6b/144336a9bf08a67d217b3af3246abb1d027095dab726f0687f01f43e8c03/psycopg2_binary-2.9.10-cp311-cp311-win32.whl", hash = "sha256:ecced182e935529727401b24d76634a357c71c9275b356efafd8a2a91ec07392", size = 1024618, upload-time = "2024-10-16T11:20:27.718Z" }, + { url = "https://files.pythonhosted.org/packages/61/69/3b3d7bd583c6d3cbe5100802efa5beacaacc86e37b653fc708bf3d6853b8/psycopg2_binary-2.9.10-cp311-cp311-win_amd64.whl", hash = "sha256:ee0e8c683a7ff25d23b55b11161c2663d4b099770f6085ff0a20d4505778d6b4", size = 1163816, upload-time = "2024-10-16T11:20:30.777Z" }, + { url = "https://files.pythonhosted.org/packages/49/7d/465cc9795cf76f6d329efdafca74693714556ea3891813701ac1fee87545/psycopg2_binary-2.9.10-cp312-cp312-macosx_12_0_x86_64.whl", hash = "sha256:880845dfe1f85d9d5f7c412efea7a08946a46894537e4e5d091732eb1d34d9a0", size = 3044771, upload-time = "2024-10-16T11:20:35.234Z" }, + { url = "https://files.pythonhosted.org/packages/8b/31/6d225b7b641a1a2148e3ed65e1aa74fc86ba3fee850545e27be9e1de893d/psycopg2_binary-2.9.10-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:9440fa522a79356aaa482aa4ba500b65f28e5d0e63b801abf6aa152a29bd842a", size = 3275336, upload-time = "2024-10-16T11:20:38.742Z" }, + { url = "https://files.pythonhosted.org/packages/30/b7/a68c2b4bff1cbb1728e3ec864b2d92327c77ad52edcd27922535a8366f68/psycopg2_binary-2.9.10-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e3923c1d9870c49a2d44f795df0c889a22380d36ef92440ff618ec315757e539", size = 2851637, upload-time = "2024-10-16T11:20:42.145Z" }, + { url = "https://files.pythonhosted.org/packages/0b/b1/cfedc0e0e6f9ad61f8657fd173b2f831ce261c02a08c0b09c652b127d813/psycopg2_binary-2.9.10-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7b2c956c028ea5de47ff3a8d6b3cc3330ab45cf0b7c3da35a2d6ff8420896526", size = 3082097, upload-time = "2024-10-16T11:20:46.185Z" }, + { url = "https://files.pythonhosted.org/packages/18/ed/0a8e4153c9b769f59c02fb5e7914f20f0b2483a19dae7bf2db54b743d0d0/psycopg2_binary-2.9.10-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f758ed67cab30b9a8d2833609513ce4d3bd027641673d4ebc9c067e4d208eec1", size = 3264776, upload-time = "2024-10-16T11:20:50.879Z" }, + { url = "https://files.pythonhosted.org/packages/10/db/d09da68c6a0cdab41566b74e0a6068a425f077169bed0946559b7348ebe9/psycopg2_binary-2.9.10-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8cd9b4f2cfab88ed4a9106192de509464b75a906462fb846b936eabe45c2063e", size = 3020968, upload-time = "2024-10-16T11:20:56.819Z" }, + { url = "https://files.pythonhosted.org/packages/94/28/4d6f8c255f0dfffb410db2b3f9ac5218d959a66c715c34cac31081e19b95/psycopg2_binary-2.9.10-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6dc08420625b5a20b53551c50deae6e231e6371194fa0651dbe0fb206452ae1f", size = 2872334, upload-time = "2024-10-16T11:21:02.411Z" }, + { url = "https://files.pythonhosted.org/packages/05/f7/20d7bf796593c4fea95e12119d6cc384ff1f6141a24fbb7df5a668d29d29/psycopg2_binary-2.9.10-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:d7cd730dfa7c36dbe8724426bf5612798734bff2d3c3857f36f2733f5bfc7c00", size = 2822722, upload-time = "2024-10-16T11:21:09.01Z" }, + { url = "https://files.pythonhosted.org/packages/4d/e4/0c407ae919ef626dbdb32835a03b6737013c3cc7240169843965cada2bdf/psycopg2_binary-2.9.10-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:155e69561d54d02b3c3209545fb08938e27889ff5a10c19de8d23eb5a41be8a5", size = 2920132, upload-time = "2024-10-16T11:21:16.339Z" }, + { url = "https://files.pythonhosted.org/packages/2d/70/aa69c9f69cf09a01da224909ff6ce8b68faeef476f00f7ec377e8f03be70/psycopg2_binary-2.9.10-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c3cc28a6fd5a4a26224007712e79b81dbaee2ffb90ff406256158ec4d7b52b47", size = 2959312, upload-time = "2024-10-16T11:21:25.584Z" }, + { url = "https://files.pythonhosted.org/packages/d3/bd/213e59854fafe87ba47814bf413ace0dcee33a89c8c8c814faca6bc7cf3c/psycopg2_binary-2.9.10-cp312-cp312-win32.whl", hash = "sha256:ec8a77f521a17506a24a5f626cb2aee7850f9b69a0afe704586f63a464f3cd64", size = 1025191, upload-time = "2024-10-16T11:21:29.912Z" }, + { url = "https://files.pythonhosted.org/packages/92/29/06261ea000e2dc1e22907dbbc483a1093665509ea586b29b8986a0e56733/psycopg2_binary-2.9.10-cp312-cp312-win_amd64.whl", hash = "sha256:18c5ee682b9c6dd3696dad6e54cc7ff3a1a9020df6a5c0f861ef8bfd338c3ca0", size = 1164031, upload-time = "2024-10-16T11:21:34.211Z" }, ] [[package]] name = "py" version = "1.11.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/98/ff/fec109ceb715d2a6b4c4a85a61af3b40c723a961e8828319fbcb15b868dc/py-1.11.0.tar.gz", hash = "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719", size = 207796 } +sdist = { url = "https://files.pythonhosted.org/packages/98/ff/fec109ceb715d2a6b4c4a85a61af3b40c723a961e8828319fbcb15b868dc/py-1.11.0.tar.gz", hash = "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719", size = 207796, upload-time = "2021-11-04T17:17:01.377Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f6/f0/10642828a8dfb741e5f3fbaac830550a518a775c7fff6f04a007259b0548/py-1.11.0-py2.py3-none-any.whl", hash = "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378", size = 98708 }, + { url = "https://files.pythonhosted.org/packages/f6/f0/10642828a8dfb741e5f3fbaac830550a518a775c7fff6f04a007259b0548/py-1.11.0-py2.py3-none-any.whl", hash = "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378", size = 98708, upload-time = "2021-11-04T17:17:00.152Z" }, ] [[package]] name = "py-cpuinfo" version = "9.0.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/37/a8/d832f7293ebb21690860d2e01d8115e5ff6f2ae8bbdc953f0eb0fa4bd2c7/py-cpuinfo-9.0.0.tar.gz", hash = "sha256:3cdbbf3fac90dc6f118bfd64384f309edeadd902d7c8fb17f02ffa1fc3f49690", size = 104716 } +sdist = { url = "https://files.pythonhosted.org/packages/37/a8/d832f7293ebb21690860d2e01d8115e5ff6f2ae8bbdc953f0eb0fa4bd2c7/py-cpuinfo-9.0.0.tar.gz", hash = "sha256:3cdbbf3fac90dc6f118bfd64384f309edeadd902d7c8fb17f02ffa1fc3f49690", size = 104716, upload-time = "2022-10-25T20:38:06.303Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e0/a9/023730ba63db1e494a271cb018dcd361bd2c917ba7004c3e49d5daf795a2/py_cpuinfo-9.0.0-py3-none-any.whl", hash = "sha256:859625bc251f64e21f077d099d4162689c762b5d6a4c3c97553d56241c9674d5", size = 22335 }, + { url = "https://files.pythonhosted.org/packages/e0/a9/023730ba63db1e494a271cb018dcd361bd2c917ba7004c3e49d5daf795a2/py_cpuinfo-9.0.0-py3-none-any.whl", hash = "sha256:859625bc251f64e21f077d099d4162689c762b5d6a4c3c97553d56241c9674d5", size = 22335, upload-time = "2022-10-25T20:38:27.636Z" }, ] [[package]] name = "pyasn1" version = "0.6.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ba/e9/01f1a64245b89f039897cb0130016d79f77d52669aae6ee7b159a6c4c018/pyasn1-0.6.1.tar.gz", hash = "sha256:6f580d2bdd84365380830acf45550f2511469f673cb4a5ae3857a3170128b034", size = 145322 } +sdist = { url = "https://files.pythonhosted.org/packages/ba/e9/01f1a64245b89f039897cb0130016d79f77d52669aae6ee7b159a6c4c018/pyasn1-0.6.1.tar.gz", hash = "sha256:6f580d2bdd84365380830acf45550f2511469f673cb4a5ae3857a3170128b034", size = 145322, upload-time = "2024-09-10T22:41:42.55Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c8/f1/d6a797abb14f6283c0ddff96bbdd46937f64122b8c925cab503dd37f8214/pyasn1-0.6.1-py3-none-any.whl", hash = "sha256:0d632f46f2ba09143da3a8afe9e33fb6f92fa2320ab7e886e2d0f7672af84629", size = 83135 }, + { url = "https://files.pythonhosted.org/packages/c8/f1/d6a797abb14f6283c0ddff96bbdd46937f64122b8c925cab503dd37f8214/pyasn1-0.6.1-py3-none-any.whl", hash = "sha256:0d632f46f2ba09143da3a8afe9e33fb6f92fa2320ab7e886e2d0f7672af84629", size = 83135, upload-time = "2024-09-11T16:00:36.122Z" }, ] [[package]] @@ -4054,130 +4230,144 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyasn1" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/e9/e6/78ebbb10a8c8e4b61a59249394a4a594c1a7af95593dc933a349c8d00964/pyasn1_modules-0.4.2.tar.gz", hash = "sha256:677091de870a80aae844b1ca6134f54652fa2c8c5a52aa396440ac3106e941e6", size = 307892 } +sdist = { url = "https://files.pythonhosted.org/packages/e9/e6/78ebbb10a8c8e4b61a59249394a4a594c1a7af95593dc933a349c8d00964/pyasn1_modules-0.4.2.tar.gz", hash = "sha256:677091de870a80aae844b1ca6134f54652fa2c8c5a52aa396440ac3106e941e6", size = 307892, upload-time = "2025-03-28T02:41:22.17Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/47/8d/d529b5d697919ba8c11ad626e835d4039be708a35b0d22de83a269a6682c/pyasn1_modules-0.4.2-py3-none-any.whl", hash = "sha256:29253a9207ce32b64c3ac6600edc75368f98473906e8fd1043bd6b5b1de2c14a", size = 181259 }, + { url = "https://files.pythonhosted.org/packages/47/8d/d529b5d697919ba8c11ad626e835d4039be708a35b0d22de83a269a6682c/pyasn1_modules-0.4.2-py3-none-any.whl", hash = "sha256:29253a9207ce32b64c3ac6600edc75368f98473906e8fd1043bd6b5b1de2c14a", size = 181259, upload-time = "2025-03-28T02:41:19.028Z" }, ] [[package]] name = "pycparser" version = "2.22" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/1d/b2/31537cf4b1ca988837256c910a668b553fceb8f069bedc4b1c826024b52c/pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6", size = 172736 } +sdist = { url = "https://files.pythonhosted.org/packages/1d/b2/31537cf4b1ca988837256c910a668b553fceb8f069bedc4b1c826024b52c/pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6", size = 172736, upload-time = "2024-03-30T13:22:22.564Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/13/a3/a812df4e2dd5696d1f351d58b8fe16a405b234ad2886a0dab9183fb78109/pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc", size = 117552 }, + { url = "https://files.pythonhosted.org/packages/13/a3/a812df4e2dd5696d1f351d58b8fe16a405b234ad2886a0dab9183fb78109/pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc", size = 117552, upload-time = "2024-03-30T13:22:20.476Z" }, ] [[package]] name = "pycryptodome" version = "3.19.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b1/38/42a8855ff1bf568c61ca6557e2203f318fb7afeadaf2eb8ecfdbde107151/pycryptodome-3.19.1.tar.gz", hash = "sha256:8ae0dd1bcfada451c35f9e29a3e5db385caabc190f98e4a80ad02a61098fb776", size = 4782144 } +sdist = { url = "https://files.pythonhosted.org/packages/b1/38/42a8855ff1bf568c61ca6557e2203f318fb7afeadaf2eb8ecfdbde107151/pycryptodome-3.19.1.tar.gz", hash = "sha256:8ae0dd1bcfada451c35f9e29a3e5db385caabc190f98e4a80ad02a61098fb776", size = 4782144, upload-time = "2023-12-28T06:52:40.741Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a8/ef/4931bc30674f0de0ca0e827b58c8b0c17313a8eae2754976c610b866118b/pycryptodome-3.19.1-cp35-abi3-macosx_10_9_universal2.whl", hash = "sha256:67939a3adbe637281c611596e44500ff309d547e932c449337649921b17b6297", size = 2417027 }, - { url = "https://files.pythonhosted.org/packages/67/e6/238c53267fd8d223029c0a0d3730cb1b6594d60f62e40c4184703dc490b1/pycryptodome-3.19.1-cp35-abi3-macosx_10_9_x86_64.whl", hash = "sha256:11ddf6c9b52116b62223b6a9f4741bc4f62bb265392a4463282f7f34bb287180", size = 1579728 }, - { url = "https://files.pythonhosted.org/packages/7c/87/7181c42c8d5ba89822a4b824830506d0aeec02959bb893614767e3279846/pycryptodome-3.19.1-cp35-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e3e6f89480616781d2a7f981472d0cdb09b9da9e8196f43c1234eff45c915766", size = 2051440 }, - { url = "https://files.pythonhosted.org/packages/34/dd/332c4c0055527d17dac317ed9f9c864fc047b627d82f4b9a56c110afc6fc/pycryptodome-3.19.1-cp35-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:27e1efcb68993b7ce5d1d047a46a601d41281bba9f1971e6be4aa27c69ab8065", size = 2125379 }, - { url = "https://files.pythonhosted.org/packages/24/9e/320b885ea336c218ff54ec2b276cd70ba6904e4f5a14a771ed39a2c47d59/pycryptodome-3.19.1-cp35-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1c6273ca5a03b672e504995529b8bae56da0ebb691d8ef141c4aa68f60765700", size = 2153951 }, - { url = "https://files.pythonhosted.org/packages/f4/54/8ae0c43d1257b41bc9d3277c3f875174fd8ad86b9567f0b8609b99c938ee/pycryptodome-3.19.1-cp35-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:b0bfe61506795877ff974f994397f0c862d037f6f1c0bfc3572195fc00833b96", size = 2044041 }, - { url = "https://files.pythonhosted.org/packages/45/93/f8450a92cc38541c3ba1f4cb4e267e15ae6d6678ca617476d52c3a3764d4/pycryptodome-3.19.1-cp35-abi3-musllinux_1_1_i686.whl", hash = "sha256:f34976c5c8eb79e14c7d970fb097482835be8d410a4220f86260695ede4c3e17", size = 2182446 }, - { url = "https://files.pythonhosted.org/packages/af/cd/ed6e429fb0792ce368f66e83246264dd3a7a045b0b1e63043ed22a063ce5/pycryptodome-3.19.1-cp35-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:7c9e222d0976f68d0cf6409cfea896676ddc1d98485d601e9508f90f60e2b0a2", size = 2144914 }, - { url = "https://files.pythonhosted.org/packages/f6/23/b064bd4cfbf2cc5f25afcde0e7c880df5b20798172793137ba4b62d82e72/pycryptodome-3.19.1-cp35-abi3-win32.whl", hash = "sha256:4805e053571140cb37cf153b5c72cd324bb1e3e837cbe590a19f69b6cf85fd03", size = 1713105 }, - { url = "https://files.pythonhosted.org/packages/7d/e0/ded1968a5257ab34216a0f8db7433897a2337d59e6d03be113713b346ea2/pycryptodome-3.19.1-cp35-abi3-win_amd64.whl", hash = "sha256:a470237ee71a1efd63f9becebc0ad84b88ec28e6784a2047684b693f458f41b7", size = 1749222 }, - { url = "https://files.pythonhosted.org/packages/1d/e3/0c9679cd66cf5604b1f070bdf4525a0c01a15187be287d8348b2eafb718e/pycryptodome-3.19.1-pp27-pypy_73-manylinux2010_x86_64.whl", hash = "sha256:ed932eb6c2b1c4391e166e1a562c9d2f020bfff44a0e1b108f67af38b390ea89", size = 1629005 }, - { url = "https://files.pythonhosted.org/packages/13/75/0d63bf0daafd0580b17202d8a9dd57f28c8487f26146b3e2799b0c5a059c/pycryptodome-3.19.1-pp27-pypy_73-win32.whl", hash = "sha256:81e9d23c0316fc1b45d984a44881b220062336bbdc340aa9218e8d0656587934", size = 1697997 }, + { url = "https://files.pythonhosted.org/packages/a8/ef/4931bc30674f0de0ca0e827b58c8b0c17313a8eae2754976c610b866118b/pycryptodome-3.19.1-cp35-abi3-macosx_10_9_universal2.whl", hash = "sha256:67939a3adbe637281c611596e44500ff309d547e932c449337649921b17b6297", size = 2417027, upload-time = "2023-12-28T06:51:50.138Z" }, + { url = "https://files.pythonhosted.org/packages/67/e6/238c53267fd8d223029c0a0d3730cb1b6594d60f62e40c4184703dc490b1/pycryptodome-3.19.1-cp35-abi3-macosx_10_9_x86_64.whl", hash = "sha256:11ddf6c9b52116b62223b6a9f4741bc4f62bb265392a4463282f7f34bb287180", size = 1579728, upload-time = "2023-12-28T06:51:52.385Z" }, + { url = "https://files.pythonhosted.org/packages/7c/87/7181c42c8d5ba89822a4b824830506d0aeec02959bb893614767e3279846/pycryptodome-3.19.1-cp35-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e3e6f89480616781d2a7f981472d0cdb09b9da9e8196f43c1234eff45c915766", size = 2051440, upload-time = "2023-12-28T06:51:55.751Z" }, + { url = "https://files.pythonhosted.org/packages/34/dd/332c4c0055527d17dac317ed9f9c864fc047b627d82f4b9a56c110afc6fc/pycryptodome-3.19.1-cp35-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:27e1efcb68993b7ce5d1d047a46a601d41281bba9f1971e6be4aa27c69ab8065", size = 2125379, upload-time = "2023-12-28T06:51:58.567Z" }, + { url = "https://files.pythonhosted.org/packages/24/9e/320b885ea336c218ff54ec2b276cd70ba6904e4f5a14a771ed39a2c47d59/pycryptodome-3.19.1-cp35-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1c6273ca5a03b672e504995529b8bae56da0ebb691d8ef141c4aa68f60765700", size = 2153951, upload-time = "2023-12-28T06:52:01.699Z" }, + { url = "https://files.pythonhosted.org/packages/f4/54/8ae0c43d1257b41bc9d3277c3f875174fd8ad86b9567f0b8609b99c938ee/pycryptodome-3.19.1-cp35-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:b0bfe61506795877ff974f994397f0c862d037f6f1c0bfc3572195fc00833b96", size = 2044041, upload-time = "2023-12-28T06:52:03.737Z" }, + { url = "https://files.pythonhosted.org/packages/45/93/f8450a92cc38541c3ba1f4cb4e267e15ae6d6678ca617476d52c3a3764d4/pycryptodome-3.19.1-cp35-abi3-musllinux_1_1_i686.whl", hash = "sha256:f34976c5c8eb79e14c7d970fb097482835be8d410a4220f86260695ede4c3e17", size = 2182446, upload-time = "2023-12-28T06:52:05.588Z" }, + { url = "https://files.pythonhosted.org/packages/af/cd/ed6e429fb0792ce368f66e83246264dd3a7a045b0b1e63043ed22a063ce5/pycryptodome-3.19.1-cp35-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:7c9e222d0976f68d0cf6409cfea896676ddc1d98485d601e9508f90f60e2b0a2", size = 2144914, upload-time = "2023-12-28T06:52:07.44Z" }, + { url = "https://files.pythonhosted.org/packages/f6/23/b064bd4cfbf2cc5f25afcde0e7c880df5b20798172793137ba4b62d82e72/pycryptodome-3.19.1-cp35-abi3-win32.whl", hash = "sha256:4805e053571140cb37cf153b5c72cd324bb1e3e837cbe590a19f69b6cf85fd03", size = 1713105, upload-time = "2023-12-28T06:52:09.585Z" }, + { url = "https://files.pythonhosted.org/packages/7d/e0/ded1968a5257ab34216a0f8db7433897a2337d59e6d03be113713b346ea2/pycryptodome-3.19.1-cp35-abi3-win_amd64.whl", hash = "sha256:a470237ee71a1efd63f9becebc0ad84b88ec28e6784a2047684b693f458f41b7", size = 1749222, upload-time = "2023-12-28T06:52:11.534Z" }, ] [[package]] name = "pydantic" -version = "2.9.2" +version = "2.11.5" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "annotated-types" }, { name = "pydantic-core" }, { name = "typing-extensions" }, + { name = "typing-inspection" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a9/b7/d9e3f12af310e1120c21603644a1cd86f59060e040ec5c3a80b8f05fae30/pydantic-2.9.2.tar.gz", hash = "sha256:d155cef71265d1e9807ed1c32b4c8deec042a44a50a4188b25ac67ecd81a9c0f", size = 769917 } +sdist = { url = "https://files.pythonhosted.org/packages/f0/86/8ce9040065e8f924d642c58e4a344e33163a07f6b57f836d0d734e0ad3fb/pydantic-2.11.5.tar.gz", hash = "sha256:7f853db3d0ce78ce8bbb148c401c2cdd6431b3473c0cdff2755c7690952a7b7a", size = 787102, upload-time = "2025-05-22T21:18:08.761Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/df/e4/ba44652d562cbf0bf320e0f3810206149c8a4e99cdbf66da82e97ab53a15/pydantic-2.9.2-py3-none-any.whl", hash = "sha256:f048cec7b26778210e28a0459867920654d48e5e62db0958433636cde4254f12", size = 434928 }, + { url = "https://files.pythonhosted.org/packages/b5/69/831ed22b38ff9b4b64b66569f0e5b7b97cf3638346eb95a2147fdb49ad5f/pydantic-2.11.5-py3-none-any.whl", hash = "sha256:f9c26ba06f9747749ca1e5c94d6a85cb84254577553c8785576fd38fa64dc0f7", size = 444229, upload-time = "2025-05-22T21:18:06.329Z" }, ] [[package]] name = "pydantic-core" -version = "2.23.4" +version = "2.33.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/e2/aa/6b6a9b9f8537b872f552ddd46dd3da230367754b6f707b8e1e963f515ea3/pydantic_core-2.23.4.tar.gz", hash = "sha256:2584f7cf844ac4d970fba483a717dbe10c1c1c96a969bf65d61ffe94df1b2863", size = 402156 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/5d/30/890a583cd3f2be27ecf32b479d5d615710bb926d92da03e3f7838ff3e58b/pydantic_core-2.23.4-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:77733e3892bb0a7fa797826361ce8a9184d25c8dffaec60b7ffe928153680ba8", size = 1865160 }, - { url = "https://files.pythonhosted.org/packages/1d/9a/b634442e1253bc6889c87afe8bb59447f106ee042140bd57680b3b113ec7/pydantic_core-2.23.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1b84d168f6c48fabd1f2027a3d1bdfe62f92cade1fb273a5d68e621da0e44e6d", size = 1776777 }, - { url = "https://files.pythonhosted.org/packages/75/9a/7816295124a6b08c24c96f9ce73085032d8bcbaf7e5a781cd41aa910c891/pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:df49e7a0861a8c36d089c1ed57d308623d60416dab2647a4a17fe050ba85de0e", size = 1799244 }, - { url = "https://files.pythonhosted.org/packages/a9/8f/89c1405176903e567c5f99ec53387449e62f1121894aa9fc2c4fdc51a59b/pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ff02b6d461a6de369f07ec15e465a88895f3223eb75073ffea56b84d9331f607", size = 1805307 }, - { url = "https://files.pythonhosted.org/packages/d5/a5/1a194447d0da1ef492e3470680c66048fef56fc1f1a25cafbea4bc1d1c48/pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:996a38a83508c54c78a5f41456b0103c30508fed9abcad0a59b876d7398f25fd", size = 2000663 }, - { url = "https://files.pythonhosted.org/packages/13/a5/1df8541651de4455e7d587cf556201b4f7997191e110bca3b589218745a5/pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d97683ddee4723ae8c95d1eddac7c192e8c552da0c73a925a89fa8649bf13eea", size = 2655941 }, - { url = "https://files.pythonhosted.org/packages/44/31/a3899b5ce02c4316865e390107f145089876dff7e1dfc770a231d836aed8/pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:216f9b2d7713eb98cb83c80b9c794de1f6b7e3145eef40400c62e86cee5f4e1e", size = 2052105 }, - { url = "https://files.pythonhosted.org/packages/1b/aa/98e190f8745d5ec831f6d5449344c48c0627ac5fed4e5340a44b74878f8e/pydantic_core-2.23.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6f783e0ec4803c787bcea93e13e9932edab72068f68ecffdf86a99fd5918878b", size = 1919967 }, - { url = "https://files.pythonhosted.org/packages/ae/35/b6e00b6abb2acfee3e8f85558c02a0822e9a8b2f2d812ea8b9079b118ba0/pydantic_core-2.23.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:d0776dea117cf5272382634bd2a5c1b6eb16767c223c6a5317cd3e2a757c61a0", size = 1964291 }, - { url = "https://files.pythonhosted.org/packages/13/46/7bee6d32b69191cd649bbbd2361af79c472d72cb29bb2024f0b6e350ba06/pydantic_core-2.23.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d5f7a395a8cf1621939692dba2a6b6a830efa6b3cee787d82c7de1ad2930de64", size = 2109666 }, - { url = "https://files.pythonhosted.org/packages/39/ef/7b34f1b122a81b68ed0a7d0e564da9ccdc9a2924c8d6c6b5b11fa3a56970/pydantic_core-2.23.4-cp311-none-win32.whl", hash = "sha256:74b9127ffea03643e998e0c5ad9bd3811d3dac8c676e47db17b0ee7c3c3bf35f", size = 1732940 }, - { url = "https://files.pythonhosted.org/packages/2f/76/37b7e76c645843ff46c1d73e046207311ef298d3f7b2f7d8f6ac60113071/pydantic_core-2.23.4-cp311-none-win_amd64.whl", hash = "sha256:98d134c954828488b153d88ba1f34e14259284f256180ce659e8d83e9c05eaa3", size = 1916804 }, - { url = "https://files.pythonhosted.org/packages/74/7b/8e315f80666194b354966ec84b7d567da77ad927ed6323db4006cf915f3f/pydantic_core-2.23.4-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:f3e0da4ebaef65158d4dfd7d3678aad692f7666877df0002b8a522cdf088f231", size = 1856459 }, - { url = "https://files.pythonhosted.org/packages/14/de/866bdce10ed808323d437612aca1ec9971b981e1c52e5e42ad9b8e17a6f6/pydantic_core-2.23.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f69a8e0b033b747bb3e36a44e7732f0c99f7edd5cea723d45bc0d6e95377ffee", size = 1770007 }, - { url = "https://files.pythonhosted.org/packages/dc/69/8edd5c3cd48bb833a3f7ef9b81d7666ccddd3c9a635225214e044b6e8281/pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:723314c1d51722ab28bfcd5240d858512ffd3116449c557a1336cbe3919beb87", size = 1790245 }, - { url = "https://files.pythonhosted.org/packages/80/33/9c24334e3af796ce80d2274940aae38dd4e5676298b4398eff103a79e02d/pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bb2802e667b7051a1bebbfe93684841cc9351004e2badbd6411bf357ab8d5ac8", size = 1801260 }, - { url = "https://files.pythonhosted.org/packages/a5/6f/e9567fd90104b79b101ca9d120219644d3314962caa7948dd8b965e9f83e/pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d18ca8148bebe1b0a382a27a8ee60350091a6ddaf475fa05ef50dc35b5df6327", size = 1996872 }, - { url = "https://files.pythonhosted.org/packages/2d/ad/b5f0fe9e6cfee915dd144edbd10b6e9c9c9c9d7a56b69256d124b8ac682e/pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:33e3d65a85a2a4a0dc3b092b938a4062b1a05f3a9abde65ea93b233bca0e03f2", size = 2661617 }, - { url = "https://files.pythonhosted.org/packages/06/c8/7d4b708f8d05a5cbfda3243aad468052c6e99de7d0937c9146c24d9f12e9/pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:128585782e5bfa515c590ccee4b727fb76925dd04a98864182b22e89a4e6ed36", size = 2071831 }, - { url = "https://files.pythonhosted.org/packages/89/4d/3079d00c47f22c9a9a8220db088b309ad6e600a73d7a69473e3a8e5e3ea3/pydantic_core-2.23.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:68665f4c17edcceecc112dfed5dbe6f92261fb9d6054b47d01bf6371a6196126", size = 1917453 }, - { url = "https://files.pythonhosted.org/packages/e9/88/9df5b7ce880a4703fcc2d76c8c2d8eb9f861f79d0c56f4b8f5f2607ccec8/pydantic_core-2.23.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:20152074317d9bed6b7a95ade3b7d6054845d70584216160860425f4fbd5ee9e", size = 1968793 }, - { url = "https://files.pythonhosted.org/packages/e3/b9/41f7efe80f6ce2ed3ee3c2dcfe10ab7adc1172f778cc9659509a79518c43/pydantic_core-2.23.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:9261d3ce84fa1d38ed649c3638feefeae23d32ba9182963e465d58d62203bd24", size = 2116872 }, - { url = "https://files.pythonhosted.org/packages/63/08/b59b7a92e03dd25554b0436554bf23e7c29abae7cce4b1c459cd92746811/pydantic_core-2.23.4-cp312-none-win32.whl", hash = "sha256:4ba762ed58e8d68657fc1281e9bb72e1c3e79cc5d464be146e260c541ec12d84", size = 1738535 }, - { url = "https://files.pythonhosted.org/packages/88/8d/479293e4d39ab409747926eec4329de5b7129beaedc3786eca070605d07f/pydantic_core-2.23.4-cp312-none-win_amd64.whl", hash = "sha256:97df63000f4fea395b2824da80e169731088656d1818a11b95f3b173747b6cd9", size = 1917992 }, +sdist = { url = "https://files.pythonhosted.org/packages/ad/88/5f2260bdfae97aabf98f1778d43f69574390ad787afb646292a638c923d4/pydantic_core-2.33.2.tar.gz", hash = "sha256:7cb8bc3605c29176e1b105350d2e6474142d7c1bd1d9327c4a9bdb46bf827acc", size = 435195, upload-time = "2025-04-23T18:33:52.104Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3f/8d/71db63483d518cbbf290261a1fc2839d17ff89fce7089e08cad07ccfce67/pydantic_core-2.33.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:4c5b0a576fb381edd6d27f0a85915c6daf2f8138dc5c267a57c08a62900758c7", size = 2028584, upload-time = "2025-04-23T18:31:03.106Z" }, + { url = "https://files.pythonhosted.org/packages/24/2f/3cfa7244ae292dd850989f328722d2aef313f74ffc471184dc509e1e4e5a/pydantic_core-2.33.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e799c050df38a639db758c617ec771fd8fb7a5f8eaaa4b27b101f266b216a246", size = 1855071, upload-time = "2025-04-23T18:31:04.621Z" }, + { url = "https://files.pythonhosted.org/packages/b3/d3/4ae42d33f5e3f50dd467761304be2fa0a9417fbf09735bc2cce003480f2a/pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dc46a01bf8d62f227d5ecee74178ffc448ff4e5197c756331f71efcc66dc980f", size = 1897823, upload-time = "2025-04-23T18:31:06.377Z" }, + { url = "https://files.pythonhosted.org/packages/f4/f3/aa5976e8352b7695ff808599794b1fba2a9ae2ee954a3426855935799488/pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a144d4f717285c6d9234a66778059f33a89096dfb9b39117663fd8413d582dcc", size = 1983792, upload-time = "2025-04-23T18:31:07.93Z" }, + { url = "https://files.pythonhosted.org/packages/d5/7a/cda9b5a23c552037717f2b2a5257e9b2bfe45e687386df9591eff7b46d28/pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:73cf6373c21bc80b2e0dc88444f41ae60b2f070ed02095754eb5a01df12256de", size = 2136338, upload-time = "2025-04-23T18:31:09.283Z" }, + { url = "https://files.pythonhosted.org/packages/2b/9f/b8f9ec8dd1417eb9da784e91e1667d58a2a4a7b7b34cf4af765ef663a7e5/pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3dc625f4aa79713512d1976fe9f0bc99f706a9dee21dfd1810b4bbbf228d0e8a", size = 2730998, upload-time = "2025-04-23T18:31:11.7Z" }, + { url = "https://files.pythonhosted.org/packages/47/bc/cd720e078576bdb8255d5032c5d63ee5c0bf4b7173dd955185a1d658c456/pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:881b21b5549499972441da4758d662aeea93f1923f953e9cbaff14b8b9565aef", size = 2003200, upload-time = "2025-04-23T18:31:13.536Z" }, + { url = "https://files.pythonhosted.org/packages/ca/22/3602b895ee2cd29d11a2b349372446ae9727c32e78a94b3d588a40fdf187/pydantic_core-2.33.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:bdc25f3681f7b78572699569514036afe3c243bc3059d3942624e936ec93450e", size = 2113890, upload-time = "2025-04-23T18:31:15.011Z" }, + { url = "https://files.pythonhosted.org/packages/ff/e6/e3c5908c03cf00d629eb38393a98fccc38ee0ce8ecce32f69fc7d7b558a7/pydantic_core-2.33.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:fe5b32187cbc0c862ee201ad66c30cf218e5ed468ec8dc1cf49dec66e160cc4d", size = 2073359, upload-time = "2025-04-23T18:31:16.393Z" }, + { url = "https://files.pythonhosted.org/packages/12/e7/6a36a07c59ebefc8777d1ffdaf5ae71b06b21952582e4b07eba88a421c79/pydantic_core-2.33.2-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:bc7aee6f634a6f4a95676fcb5d6559a2c2a390330098dba5e5a5f28a2e4ada30", size = 2245883, upload-time = "2025-04-23T18:31:17.892Z" }, + { url = "https://files.pythonhosted.org/packages/16/3f/59b3187aaa6cc0c1e6616e8045b284de2b6a87b027cce2ffcea073adf1d2/pydantic_core-2.33.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:235f45e5dbcccf6bd99f9f472858849f73d11120d76ea8707115415f8e5ebebf", size = 2241074, upload-time = "2025-04-23T18:31:19.205Z" }, + { url = "https://files.pythonhosted.org/packages/e0/ed/55532bb88f674d5d8f67ab121a2a13c385df382de2a1677f30ad385f7438/pydantic_core-2.33.2-cp311-cp311-win32.whl", hash = "sha256:6368900c2d3ef09b69cb0b913f9f8263b03786e5b2a387706c5afb66800efd51", size = 1910538, upload-time = "2025-04-23T18:31:20.541Z" }, + { url = "https://files.pythonhosted.org/packages/fe/1b/25b7cccd4519c0b23c2dd636ad39d381abf113085ce4f7bec2b0dc755eb1/pydantic_core-2.33.2-cp311-cp311-win_amd64.whl", hash = "sha256:1e063337ef9e9820c77acc768546325ebe04ee38b08703244c1309cccc4f1bab", size = 1952909, upload-time = "2025-04-23T18:31:22.371Z" }, + { url = "https://files.pythonhosted.org/packages/49/a9/d809358e49126438055884c4366a1f6227f0f84f635a9014e2deb9b9de54/pydantic_core-2.33.2-cp311-cp311-win_arm64.whl", hash = "sha256:6b99022f1d19bc32a4c2a0d544fc9a76e3be90f0b3f4af413f87d38749300e65", size = 1897786, upload-time = "2025-04-23T18:31:24.161Z" }, + { url = "https://files.pythonhosted.org/packages/18/8a/2b41c97f554ec8c71f2a8a5f85cb56a8b0956addfe8b0efb5b3d77e8bdc3/pydantic_core-2.33.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:a7ec89dc587667f22b6a0b6579c249fca9026ce7c333fc142ba42411fa243cdc", size = 2009000, upload-time = "2025-04-23T18:31:25.863Z" }, + { url = "https://files.pythonhosted.org/packages/a1/02/6224312aacb3c8ecbaa959897af57181fb6cf3a3d7917fd44d0f2917e6f2/pydantic_core-2.33.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3c6db6e52c6d70aa0d00d45cdb9b40f0433b96380071ea80b09277dba021ddf7", size = 1847996, upload-time = "2025-04-23T18:31:27.341Z" }, + { url = "https://files.pythonhosted.org/packages/d6/46/6dcdf084a523dbe0a0be59d054734b86a981726f221f4562aed313dbcb49/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e61206137cbc65e6d5256e1166f88331d3b6238e082d9f74613b9b765fb9025", size = 1880957, upload-time = "2025-04-23T18:31:28.956Z" }, + { url = "https://files.pythonhosted.org/packages/ec/6b/1ec2c03837ac00886ba8160ce041ce4e325b41d06a034adbef11339ae422/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:eb8c529b2819c37140eb51b914153063d27ed88e3bdc31b71198a198e921e011", size = 1964199, upload-time = "2025-04-23T18:31:31.025Z" }, + { url = "https://files.pythonhosted.org/packages/2d/1d/6bf34d6adb9debd9136bd197ca72642203ce9aaaa85cfcbfcf20f9696e83/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c52b02ad8b4e2cf14ca7b3d918f3eb0ee91e63b3167c32591e57c4317e134f8f", size = 2120296, upload-time = "2025-04-23T18:31:32.514Z" }, + { url = "https://files.pythonhosted.org/packages/e0/94/2bd0aaf5a591e974b32a9f7123f16637776c304471a0ab33cf263cf5591a/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:96081f1605125ba0855dfda83f6f3df5ec90c61195421ba72223de35ccfb2f88", size = 2676109, upload-time = "2025-04-23T18:31:33.958Z" }, + { url = "https://files.pythonhosted.org/packages/f9/41/4b043778cf9c4285d59742281a769eac371b9e47e35f98ad321349cc5d61/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f57a69461af2a5fa6e6bbd7a5f60d3b7e6cebb687f55106933188e79ad155c1", size = 2002028, upload-time = "2025-04-23T18:31:39.095Z" }, + { url = "https://files.pythonhosted.org/packages/cb/d5/7bb781bf2748ce3d03af04d5c969fa1308880e1dca35a9bd94e1a96a922e/pydantic_core-2.33.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:572c7e6c8bb4774d2ac88929e3d1f12bc45714ae5ee6d9a788a9fb35e60bb04b", size = 2100044, upload-time = "2025-04-23T18:31:41.034Z" }, + { url = "https://files.pythonhosted.org/packages/fe/36/def5e53e1eb0ad896785702a5bbfd25eed546cdcf4087ad285021a90ed53/pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:db4b41f9bd95fbe5acd76d89920336ba96f03e149097365afe1cb092fceb89a1", size = 2058881, upload-time = "2025-04-23T18:31:42.757Z" }, + { url = "https://files.pythonhosted.org/packages/01/6c/57f8d70b2ee57fc3dc8b9610315949837fa8c11d86927b9bb044f8705419/pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:fa854f5cf7e33842a892e5c73f45327760bc7bc516339fda888c75ae60edaeb6", size = 2227034, upload-time = "2025-04-23T18:31:44.304Z" }, + { url = "https://files.pythonhosted.org/packages/27/b9/9c17f0396a82b3d5cbea4c24d742083422639e7bb1d5bf600e12cb176a13/pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:5f483cfb75ff703095c59e365360cb73e00185e01aaea067cd19acffd2ab20ea", size = 2234187, upload-time = "2025-04-23T18:31:45.891Z" }, + { url = "https://files.pythonhosted.org/packages/b0/6a/adf5734ffd52bf86d865093ad70b2ce543415e0e356f6cacabbc0d9ad910/pydantic_core-2.33.2-cp312-cp312-win32.whl", hash = "sha256:9cb1da0f5a471435a7bc7e439b8a728e8b61e59784b2af70d7c169f8dd8ae290", size = 1892628, upload-time = "2025-04-23T18:31:47.819Z" }, + { url = "https://files.pythonhosted.org/packages/43/e4/5479fecb3606c1368d496a825d8411e126133c41224c1e7238be58b87d7e/pydantic_core-2.33.2-cp312-cp312-win_amd64.whl", hash = "sha256:f941635f2a3d96b2973e867144fde513665c87f13fe0e193c158ac51bfaaa7b2", size = 1955866, upload-time = "2025-04-23T18:31:49.635Z" }, + { url = "https://files.pythonhosted.org/packages/0d/24/8b11e8b3e2be9dd82df4b11408a67c61bb4dc4f8e11b5b0fc888b38118b5/pydantic_core-2.33.2-cp312-cp312-win_arm64.whl", hash = "sha256:cca3868ddfaccfbc4bfb1d608e2ccaaebe0ae628e1416aeb9c4d88c001bb45ab", size = 1888894, upload-time = "2025-04-23T18:31:51.609Z" }, + { url = "https://files.pythonhosted.org/packages/7b/27/d4ae6487d73948d6f20dddcd94be4ea43e74349b56eba82e9bdee2d7494c/pydantic_core-2.33.2-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:dd14041875d09cc0f9308e37a6f8b65f5585cf2598a53aa0123df8b129d481f8", size = 2025200, upload-time = "2025-04-23T18:33:14.199Z" }, + { url = "https://files.pythonhosted.org/packages/f1/b8/b3cb95375f05d33801024079b9392a5ab45267a63400bf1866e7ce0f0de4/pydantic_core-2.33.2-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:d87c561733f66531dced0da6e864f44ebf89a8fba55f31407b00c2f7f9449593", size = 1859123, upload-time = "2025-04-23T18:33:16.555Z" }, + { url = "https://files.pythonhosted.org/packages/05/bc/0d0b5adeda59a261cd30a1235a445bf55c7e46ae44aea28f7bd6ed46e091/pydantic_core-2.33.2-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2f82865531efd18d6e07a04a17331af02cb7a651583c418df8266f17a63c6612", size = 1892852, upload-time = "2025-04-23T18:33:18.513Z" }, + { url = "https://files.pythonhosted.org/packages/3e/11/d37bdebbda2e449cb3f519f6ce950927b56d62f0b84fd9cb9e372a26a3d5/pydantic_core-2.33.2-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2bfb5112df54209d820d7bf9317c7a6c9025ea52e49f46b6a2060104bba37de7", size = 2067484, upload-time = "2025-04-23T18:33:20.475Z" }, + { url = "https://files.pythonhosted.org/packages/8c/55/1f95f0a05ce72ecb02a8a8a1c3be0579bbc29b1d5ab68f1378b7bebc5057/pydantic_core-2.33.2-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:64632ff9d614e5eecfb495796ad51b0ed98c453e447a76bcbeeb69615079fc7e", size = 2108896, upload-time = "2025-04-23T18:33:22.501Z" }, + { url = "https://files.pythonhosted.org/packages/53/89/2b2de6c81fa131f423246a9109d7b2a375e83968ad0800d6e57d0574629b/pydantic_core-2.33.2-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:f889f7a40498cc077332c7ab6b4608d296d852182211787d4f3ee377aaae66e8", size = 2069475, upload-time = "2025-04-23T18:33:24.528Z" }, + { url = "https://files.pythonhosted.org/packages/b8/e9/1f7efbe20d0b2b10f6718944b5d8ece9152390904f29a78e68d4e7961159/pydantic_core-2.33.2-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:de4b83bb311557e439b9e186f733f6c645b9417c84e2eb8203f3f820a4b988bf", size = 2239013, upload-time = "2025-04-23T18:33:26.621Z" }, + { url = "https://files.pythonhosted.org/packages/3c/b2/5309c905a93811524a49b4e031e9851a6b00ff0fb668794472ea7746b448/pydantic_core-2.33.2-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:82f68293f055f51b51ea42fafc74b6aad03e70e191799430b90c13d643059ebb", size = 2238715, upload-time = "2025-04-23T18:33:28.656Z" }, + { url = "https://files.pythonhosted.org/packages/32/56/8a7ca5d2cd2cda1d245d34b1c9a942920a718082ae8e54e5f3e5a58b7add/pydantic_core-2.33.2-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:329467cecfb529c925cf2bbd4d60d2c509bc2fb52a20c1045bf09bb70971a9c1", size = 2066757, upload-time = "2025-04-23T18:33:30.645Z" }, ] [[package]] name = "pydantic-extra-types" -version = "2.9.0" +version = "2.10.5" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pydantic" }, + { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/fb/95/d61dcadd933cb34461adc271c13bbe14a7080b9922b9e0dc3c1d18b421cb/pydantic_extra_types-2.9.0.tar.gz", hash = "sha256:e061c01636188743bb69f368dcd391f327b8cfbfede2fe1cbb1211b06601ba3b", size = 39578 } +sdist = { url = "https://files.pythonhosted.org/packages/7e/ba/4178111ec4116c54e1dc7ecd2a1ff8f54256cdbd250e576882911e8f710a/pydantic_extra_types-2.10.5.tar.gz", hash = "sha256:1dcfa2c0cf741a422f088e0dbb4690e7bfadaaf050da3d6f80d6c3cf58a2bad8", size = 138429, upload-time = "2025-06-02T09:31:52.713Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/37/2f/efc4877d1a1536ec76ca0114c3e9dee7d0a10a262c53d384d50163f5684c/pydantic_extra_types-2.9.0-py3-none-any.whl", hash = "sha256:f0bb975508572ba7bf3390b7337807588463b7248587e69f43b1ad7c797530d0", size = 30544 }, + { url = "https://files.pythonhosted.org/packages/70/1a/5f4fd9e7285f10c44095a4f9fe17d0f358d1702a7c74a9278c794e8a7537/pydantic_extra_types-2.10.5-py3-none-any.whl", hash = "sha256:b60c4e23d573a69a4f1a16dd92888ecc0ef34fb0e655b4f305530377fa70e7a8", size = 38315, upload-time = "2025-06-02T09:31:51.229Z" }, ] [[package]] name = "pydantic-settings" -version = "2.6.1" +version = "2.9.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pydantic" }, { name = "python-dotenv" }, + { name = "typing-inspection" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/b5/d4/9dfbe238f45ad8b168f5c96ee49a3df0598ce18a0795a983b419949ce65b/pydantic_settings-2.6.1.tar.gz", hash = "sha256:e0f92546d8a9923cb8941689abf85d6601a8c19a23e97a34b2964a2e3f813ca0", size = 75646 } +sdist = { url = "https://files.pythonhosted.org/packages/67/1d/42628a2c33e93f8e9acbde0d5d735fa0850f3e6a2f8cb1eb6c40b9a732ac/pydantic_settings-2.9.1.tar.gz", hash = "sha256:c509bf79d27563add44e8446233359004ed85066cd096d8b510f715e6ef5d268", size = 163234, upload-time = "2025-04-18T16:44:48.265Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/5e/f9/ff95fd7d760af42f647ea87f9b8a383d891cdb5e5dbd4613edaeb094252a/pydantic_settings-2.6.1-py3-none-any.whl", hash = "sha256:7fb0637c786a558d3103436278a7c4f1cfd29ba8973238a50c5bb9a55387da87", size = 28595 }, + { url = "https://files.pythonhosted.org/packages/b6/5f/d6d641b490fd3ec2c4c13b4244d68deea3a1b970a97be64f34fb5504ff72/pydantic_settings-2.9.1-py3-none-any.whl", hash = "sha256:59b4f431b1defb26fe620c71a7d3968a710d719f5f4cdbbdb7926edeb770f6ef", size = 44356, upload-time = "2025-04-18T16:44:46.617Z" }, ] [[package]] name = "pygments" version = "2.19.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/7c/2d/c3338d48ea6cc0feb8446d8e6937e1408088a72a39937982cc6111d17f84/pygments-2.19.1.tar.gz", hash = "sha256:61c16d2a8576dc0649d9f39e089b5f02bcd27fba10d8fb4dcc28173f7a45151f", size = 4968581 } +sdist = { url = "https://files.pythonhosted.org/packages/7c/2d/c3338d48ea6cc0feb8446d8e6937e1408088a72a39937982cc6111d17f84/pygments-2.19.1.tar.gz", hash = "sha256:61c16d2a8576dc0649d9f39e089b5f02bcd27fba10d8fb4dcc28173f7a45151f", size = 4968581, upload-time = "2025-01-06T17:26:30.443Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/8a/0b/9fcc47d19c48b59121088dd6da2488a49d5f72dacf8262e2790a1d2c7d15/pygments-2.19.1-py3-none-any.whl", hash = "sha256:9ea1544ad55cecf4b8242fab6dd35a93bbce657034b0611ee383099054ab6d8c", size = 1225293 }, + { url = "https://files.pythonhosted.org/packages/8a/0b/9fcc47d19c48b59121088dd6da2488a49d5f72dacf8262e2790a1d2c7d15/pygments-2.19.1-py3-none-any.whl", hash = "sha256:9ea1544ad55cecf4b8242fab6dd35a93bbce657034b0611ee383099054ab6d8c", size = 1225293, upload-time = "2025-01-06T17:26:25.553Z" }, ] [[package]] name = "pyjwt" version = "2.8.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/30/72/8259b2bccfe4673330cea843ab23f86858a419d8f1493f66d413a76c7e3b/PyJWT-2.8.0.tar.gz", hash = "sha256:57e28d156e3d5c10088e0c68abb90bfac3df82b40a71bd0daa20c65ccd5c23de", size = 78313 } +sdist = { url = "https://files.pythonhosted.org/packages/30/72/8259b2bccfe4673330cea843ab23f86858a419d8f1493f66d413a76c7e3b/PyJWT-2.8.0.tar.gz", hash = "sha256:57e28d156e3d5c10088e0c68abb90bfac3df82b40a71bd0daa20c65ccd5c23de", size = 78313, upload-time = "2023-07-18T20:02:22.594Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/2b/4f/e04a8067c7c96c364cef7ef73906504e2f40d690811c021e1a1901473a19/PyJWT-2.8.0-py3-none-any.whl", hash = "sha256:59127c392cc44c2da5bb3192169a91f429924e17aff6534d70fdc02ab3e04320", size = 22591 }, + { url = "https://files.pythonhosted.org/packages/2b/4f/e04a8067c7c96c364cef7ef73906504e2f40d690811c021e1a1901473a19/PyJWT-2.8.0-py3-none-any.whl", hash = "sha256:59127c392cc44c2da5bb3192169a91f429924e17aff6534d70fdc02ab3e04320", size = 22591, upload-time = "2023-07-18T20:02:21.561Z" }, ] [package.optional-dependencies] @@ -4187,7 +4377,7 @@ crypto = [ [[package]] name = "pymilvus" -version = "2.5.6" +version = "2.5.10" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "grpcio" }, @@ -4198,9 +4388,9 @@ dependencies = [ { name = "setuptools" }, { name = "ujson" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/43/02/85ce4bc154d5f75e466d92e8112f09f67534fe82aaedf7dd22c6cb70433d/pymilvus-2.5.6.tar.gz", hash = "sha256:2bea0b03ed9ac3daadb1b2df8e38aa5c8f4aabd00b23a4999abb4adaebf54f59", size = 1256288 } +sdist = { url = "https://files.pythonhosted.org/packages/da/e2/88f126a08d8eefba7341e3eb323406a227146094aab7137a2b91d882e98d/pymilvus-2.5.10.tar.gz", hash = "sha256:cc44ad776aeab781ee4c4a4d334b73e746066ab2fb6722c5311f02efa6fc54a2", size = 1260364, upload-time = "2025-05-23T06:08:06.992Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/06/4f/169ffe72bc628bf89be7770f2ef60f90fd73588c6ceb25d47f1bb00ade02/pymilvus-2.5.6-py3-none-any.whl", hash = "sha256:19796f328278974f04632a1531e602070614f6ab0865cc97e27755f622e50a6d", size = 223410 }, + { url = "https://files.pythonhosted.org/packages/b0/4b/847704930ad8ddd0d0975e9a3a5e3fe704f642debe97454135c2b9ee7081/pymilvus-2.5.10-py3-none-any.whl", hash = "sha256:7da540f93068871cda3941602c55227aeaafb66f2f0d9c05e8f9db783716b100", size = 227635, upload-time = "2025-05-23T06:08:05.397Z" }, ] [[package]] @@ -4212,116 +4402,106 @@ dependencies = [ { name = "orjson" }, { name = "requests" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/cc/da/3027eeeaf7a7db9b0ca761079de4e676a002e1cc2c4260dab0ce812972b8/pymochow-1.3.1.tar.gz", hash = "sha256:1693d10cd0bb7bce45327890a90adafb503155922ccc029acb257699a73a20ba", size = 30800 } +sdist = { url = "https://files.pythonhosted.org/packages/cc/da/3027eeeaf7a7db9b0ca761079de4e676a002e1cc2c4260dab0ce812972b8/pymochow-1.3.1.tar.gz", hash = "sha256:1693d10cd0bb7bce45327890a90adafb503155922ccc029acb257699a73a20ba", size = 30800, upload-time = "2024-09-11T12:06:37.88Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/6b/74/4b6227717f6baa37e7288f53e0fd55764939abc4119342eed4924a98f477/pymochow-1.3.1-py3-none-any.whl", hash = "sha256:a7f3b34fd6ea5d1d8413650bb6678365aa148fc396ae945e4ccb4f2365a52327", size = 42697 }, + { url = "https://files.pythonhosted.org/packages/6b/74/4b6227717f6baa37e7288f53e0fd55764939abc4119342eed4924a98f477/pymochow-1.3.1-py3-none-any.whl", hash = "sha256:a7f3b34fd6ea5d1d8413650bb6678365aa148fc396ae945e4ccb4f2365a52327", size = 42697, upload-time = "2024-09-11T12:06:36.114Z" }, ] [[package]] name = "pymysql" version = "1.1.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b3/8f/ce59b5e5ed4ce8512f879ff1fa5ab699d211ae2495f1adaa5fbba2a1eada/pymysql-1.1.1.tar.gz", hash = "sha256:e127611aaf2b417403c60bf4dc570124aeb4a57f5f37b8e95ae399a42f904cd0", size = 47678 } +sdist = { url = "https://files.pythonhosted.org/packages/b3/8f/ce59b5e5ed4ce8512f879ff1fa5ab699d211ae2495f1adaa5fbba2a1eada/pymysql-1.1.1.tar.gz", hash = "sha256:e127611aaf2b417403c60bf4dc570124aeb4a57f5f37b8e95ae399a42f904cd0", size = 47678, upload-time = "2024-05-21T11:03:43.722Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/0c/94/e4181a1f6286f545507528c78016e00065ea913276888db2262507693ce5/PyMySQL-1.1.1-py3-none-any.whl", hash = "sha256:4de15da4c61dc132f4fb9ab763063e693d521a80fd0e87943b9a453dd4c19d6c", size = 44972 }, + { url = "https://files.pythonhosted.org/packages/0c/94/e4181a1f6286f545507528c78016e00065ea913276888db2262507693ce5/PyMySQL-1.1.1-py3-none-any.whl", hash = "sha256:4de15da4c61dc132f4fb9ab763063e693d521a80fd0e87943b9a453dd4c19d6c", size = 44972, upload-time = "2024-05-21T11:03:41.216Z" }, ] [[package]] name = "pyobvector" -version = "0.1.18" +version = "0.1.20" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "aiomysql" }, { name = "numpy" }, + { name = "pydantic" }, { name = "pymysql" }, { name = "sqlalchemy" }, + { name = "sqlglot" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/7c/ea/beefee7acff5e677cd5a28eb6f9b003e6a8d52d06d0c5cf276e3e24de686/pyobvector-0.1.18.tar.gz", hash = "sha256:0497764dc8f60ab2ce8b8d738b05dea946df5679e773049620da5a339091ed92", size = 27777 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/42/29/f9ca51e5ca104cccbcb3d58f897338eb0110dd2e7e35fecb7e4af2b49504/pyobvector-0.1.18-py3-none-any.whl", hash = "sha256:9ca4098fd58f87e9c6ff1cd4a5631c666d51d0607933dd3656b7274eacc36428", size = 36210 }, -] - -[[package]] -name = "pyopenssl" -version = "24.3.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "cryptography" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/c1/d4/1067b82c4fc674d6f6e9e8d26b3dff978da46d351ca3bac171544693e085/pyopenssl-24.3.0.tar.gz", hash = "sha256:49f7a019577d834746bc55c5fce6ecbcec0f2b4ec5ce1cf43a9a173b8138bb36", size = 178944 } +sdist = { url = "https://files.pythonhosted.org/packages/13/f7/fcc99e41bac6aee0822667809957800b2f512f2d96d1fa52c283cb58db16/pyobvector-0.1.20.tar.gz", hash = "sha256:26e8155d5b933333a2ab21b08e51df84e374d188f5fb26520347450e72f34c6e", size = 35256, upload-time = "2025-03-17T12:32:16.66Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/42/22/40f9162e943f86f0fc927ebc648078be87def360d9d8db346619fb97df2b/pyOpenSSL-24.3.0-py3-none-any.whl", hash = "sha256:e474f5a473cd7f92221cc04976e48f4d11502804657a08a989fb3be5514c904a", size = 56111 }, + { url = "https://files.pythonhosted.org/packages/e6/7d/00cd33c18814bec0a4f6f4a89becdbe2c6c28d78f87d765d0259a22b117e/pyobvector-0.1.20-py3-none-any.whl", hash = "sha256:1a991f8b9f53e1a749fc396ccb90c7591e64b2c5679930023f00789c62e7af72", size = 46264, upload-time = "2025-03-17T12:32:15.356Z" }, ] [[package]] name = "pypandoc" version = "1.15" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e1/88/26e650d053df5f3874aa3c05901a14166ce3271f58bfe114fd776987efbd/pypandoc-1.15.tar.gz", hash = "sha256:ea25beebe712ae41d63f7410c08741a3cab0e420f6703f95bc9b3a749192ce13", size = 32940 } +sdist = { url = "https://files.pythonhosted.org/packages/e1/88/26e650d053df5f3874aa3c05901a14166ce3271f58bfe114fd776987efbd/pypandoc-1.15.tar.gz", hash = "sha256:ea25beebe712ae41d63f7410c08741a3cab0e420f6703f95bc9b3a749192ce13", size = 32940, upload-time = "2025-01-08T17:39:58.705Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/61/06/0763e0ccc81754d3eadb21b2cb86cf21bdedc9b52698c2ad6785db7f0a4e/pypandoc-1.15-py3-none-any.whl", hash = "sha256:4ededcc76c8770f27aaca6dff47724578428eca84212a31479403a9731fc2b16", size = 21321 }, + { url = "https://files.pythonhosted.org/packages/61/06/0763e0ccc81754d3eadb21b2cb86cf21bdedc9b52698c2ad6785db7f0a4e/pypandoc-1.15-py3-none-any.whl", hash = "sha256:4ededcc76c8770f27aaca6dff47724578428eca84212a31479403a9731fc2b16", size = 21321, upload-time = "2025-01-08T17:39:09.928Z" }, ] [[package]] name = "pyparsing" version = "3.2.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/bb/22/f1129e69d94ffff626bdb5c835506b3a5b4f3d070f17ea295e12c2c6f60f/pyparsing-3.2.3.tar.gz", hash = "sha256:b9c13f1ab8b3b542f72e28f634bad4de758ab3ce4546e4301970ad6fa77c38be", size = 1088608 } +sdist = { url = "https://files.pythonhosted.org/packages/bb/22/f1129e69d94ffff626bdb5c835506b3a5b4f3d070f17ea295e12c2c6f60f/pyparsing-3.2.3.tar.gz", hash = "sha256:b9c13f1ab8b3b542f72e28f634bad4de758ab3ce4546e4301970ad6fa77c38be", size = 1088608, upload-time = "2025-03-25T05:01:28.114Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/05/e7/df2285f3d08fee213f2d041540fa4fc9ca6c2d44cf36d3a035bf2a8d2bcc/pyparsing-3.2.3-py3-none-any.whl", hash = "sha256:a749938e02d6fd0b59b356ca504a24982314bb090c383e3cf201c95ef7e2bfcf", size = 111120 }, + { url = "https://files.pythonhosted.org/packages/05/e7/df2285f3d08fee213f2d041540fa4fc9ca6c2d44cf36d3a035bf2a8d2bcc/pyparsing-3.2.3-py3-none-any.whl", hash = "sha256:a749938e02d6fd0b59b356ca504a24982314bb090c383e3cf201c95ef7e2bfcf", size = 111120, upload-time = "2025-03-25T05:01:24.908Z" }, ] [[package]] name = "pypdf" -version = "5.4.0" +version = "5.6.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f9/43/4026f6ee056306d0e0eb04fcb9f2122a0f1a5c57ad9dc5e0d67399e47194/pypdf-5.4.0.tar.gz", hash = "sha256:9af476a9dc30fcb137659b0dec747ea94aa954933c52cf02ee33e39a16fe9175", size = 5012492 } +sdist = { url = "https://files.pythonhosted.org/packages/40/46/67de1d7a65412aa1c896e6b280829b70b57d203fadae6859b690006b8e0a/pypdf-5.6.0.tar.gz", hash = "sha256:a4b6538b77fc796622000db7127e4e58039ec5e6afd292f8e9bf42e2e985a749", size = 5023749, upload-time = "2025-06-01T12:19:40.101Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/0b/27/d83f8f2a03ca5408dc2cc84b49c0bf3fbf059398a6a2ea7c10acfe28859f/pypdf-5.4.0-py3-none-any.whl", hash = "sha256:db994ab47cadc81057ea1591b90e5b543e2b7ef2d0e31ef41a9bfe763c119dab", size = 302306 }, + { url = "https://files.pythonhosted.org/packages/71/8b/dc3a72d98c22be7a4cbd664ad14c5a3e6295c2dbdf572865ed61e24b5e38/pypdf-5.6.0-py3-none-any.whl", hash = "sha256:ca6bf446bfb0a2d8d71d6d6bb860798d864c36a29b3d9ae8d7fc7958c59f88e7", size = 304208, upload-time = "2025-06-01T12:19:38.003Z" }, ] [[package]] name = "pypdfium2" -version = "4.30.1" +version = "4.30.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/55/d4/905e621c62598a08168c272b42fc00136c8861cfce97afb2a1ecbd99487a/pypdfium2-4.30.1.tar.gz", hash = "sha256:5f5c7c6d03598e107d974f66b220a49436aceb191da34cda5f692be098a814ce", size = 164854 } +sdist = { url = "https://files.pythonhosted.org/packages/a1/14/838b3ba247a0ba92e4df5d23f2bea9478edcfd72b78a39d6ca36ccd84ad2/pypdfium2-4.30.0.tar.gz", hash = "sha256:48b5b7e5566665bc1015b9d69c1ebabe21f6aee468b509531c3c8318eeee2e16", size = 140239, upload-time = "2024-05-09T18:33:17.552Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/30/8e/3ce0856b3af0f058dd3655ce57d31d1dbde4d4bd0e172022ffbf1b58a4b9/pypdfium2-4.30.1-py3-none-macosx_10_13_x86_64.whl", hash = "sha256:e07c47633732cc18d890bb7e965ad28a9c5a932e548acb928596f86be2e5ae37", size = 2889836 }, - { url = "https://files.pythonhosted.org/packages/c2/6a/f6995b21f9c6c155487ce7df70632a2df1ba49efcb291b9943ea45f28b15/pypdfium2-4.30.1-py3-none-macosx_11_0_arm64.whl", hash = "sha256:5ea2d44e96d361123b67b00f527017aa9c847c871b5714e013c01c3eb36a79fe", size = 2769232 }, - { url = "https://files.pythonhosted.org/packages/53/91/79060923148e6d380b8a299b32bba46d70aac5fe1cd4f04320bcbd1a48d3/pypdfium2-4.30.1-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1de7a3a36803171b3f66911131046d65a732f9e7834438191cb58235e6163c4e", size = 2847531 }, - { url = "https://files.pythonhosted.org/packages/a8/6c/93507f87c159e747eaab54352c0fccbaec3f1b3749d0bb9085a47899f898/pypdfium2-4.30.1-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b8a4231efb13170354f568c722d6540b8d5b476b08825586d48ef70c40d16e03", size = 2636266 }, - { url = "https://files.pythonhosted.org/packages/24/dc/d56f74a092f2091e328d6485f16562e2fc51cffb0ad6d5c616d80c1eb53c/pypdfium2-4.30.1-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6f434a4934e8244aa95343ffcf24e9ad9f120dbb4785f631bb40a88c39292493", size = 2919296 }, - { url = "https://files.pythonhosted.org/packages/be/d9/a2f1ee03d47fbeb48bcfde47ed7155772739622cfadf7135a84ba6a97824/pypdfium2-4.30.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f454032a0bc7681900170f67d8711b3942824531e765f91c2f5ce7937f999794", size = 2866119 }, - { url = "https://files.pythonhosted.org/packages/01/47/6aa019c32aa39d3f33347c458c0c5887e84096cbe444456402bc97e66704/pypdfium2-4.30.1-py3-none-musllinux_1_1_aarch64.whl", hash = "sha256:bbf9130a72370ee9d602e39949b902db669a2a1c24746a91e5586eb829055d9f", size = 6228684 }, - { url = "https://files.pythonhosted.org/packages/4c/07/2954c15b3f7c85ceb80cad36757fd41b3aba0dd14e68f4bed9ce3f2e7e74/pypdfium2-4.30.1-py3-none-musllinux_1_1_i686.whl", hash = "sha256:5cb52884b1583b96e94fd78542c63bb42e06df5e8f9e52f8f31f5ad5a1e53367", size = 6231815 }, - { url = "https://files.pythonhosted.org/packages/b4/9b/b4667e95754624f4af5a912001abba90c046e1c80d4a4e887f0af664ffec/pypdfium2-4.30.1-py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:1a9e372bd4867ff223cc8c338e33fe11055dad12f22885950fc27646cc8d9122", size = 6313429 }, - { url = "https://files.pythonhosted.org/packages/43/38/f9e77cf55ba5546a39fa659404b78b97de2ca344848271e7731efb0954cd/pypdfium2-4.30.1-py3-none-win32.whl", hash = "sha256:421f1cf205e213e07c1f2934905779547f4f4a2ff2f59dde29da3d511d3fc806", size = 2834989 }, - { url = "https://files.pythonhosted.org/packages/a4/f3/8d3a350efb4286b5ebdabcf6736f51d8e3b10dbe68804c6930b00f5cf329/pypdfium2-4.30.1-py3-none-win_amd64.whl", hash = "sha256:598a7f20264ab5113853cba6d86c4566e4356cad037d7d1f849c8c9021007e05", size = 2960157 }, - { url = "https://files.pythonhosted.org/packages/e1/6b/2706497c86e8d69fb76afe5ea857fe1794621aa0f3b1d863feb953fe0f22/pypdfium2-4.30.1-py3-none-win_arm64.whl", hash = "sha256:c2b6d63f6d425d9416c08d2511822b54b8e3ac38e639fc41164b1d75584b3a8c", size = 2814810 }, + { url = "https://files.pythonhosted.org/packages/c7/9a/c8ff5cc352c1b60b0b97642ae734f51edbab6e28b45b4fcdfe5306ee3c83/pypdfium2-4.30.0-py3-none-macosx_10_13_x86_64.whl", hash = "sha256:b33ceded0b6ff5b2b93bc1fe0ad4b71aa6b7e7bd5875f1ca0cdfb6ba6ac01aab", size = 2837254, upload-time = "2024-05-09T18:32:48.653Z" }, + { url = "https://files.pythonhosted.org/packages/21/8b/27d4d5409f3c76b985f4ee4afe147b606594411e15ac4dc1c3363c9a9810/pypdfium2-4.30.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:4e55689f4b06e2d2406203e771f78789bd4f190731b5d57383d05cf611d829de", size = 2707624, upload-time = "2024-05-09T18:32:51.458Z" }, + { url = "https://files.pythonhosted.org/packages/11/63/28a73ca17c24b41a205d658e177d68e198d7dde65a8c99c821d231b6ee3d/pypdfium2-4.30.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e6e50f5ce7f65a40a33d7c9edc39f23140c57e37144c2d6d9e9262a2a854854", size = 2793126, upload-time = "2024-05-09T18:32:53.581Z" }, + { url = "https://files.pythonhosted.org/packages/d1/96/53b3ebf0955edbd02ac6da16a818ecc65c939e98fdeb4e0958362bd385c8/pypdfium2-4.30.0-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3d0dd3ecaffd0b6dbda3da663220e705cb563918249bda26058c6036752ba3a2", size = 2591077, upload-time = "2024-05-09T18:32:55.99Z" }, + { url = "https://files.pythonhosted.org/packages/ec/ee/0394e56e7cab8b5b21f744d988400948ef71a9a892cbeb0b200d324ab2c7/pypdfium2-4.30.0-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cc3bf29b0db8c76cdfaac1ec1cde8edf211a7de7390fbf8934ad2aa9b4d6dfad", size = 2864431, upload-time = "2024-05-09T18:32:57.911Z" }, + { url = "https://files.pythonhosted.org/packages/65/cd/3f1edf20a0ef4a212a5e20a5900e64942c5a374473671ac0780eaa08ea80/pypdfium2-4.30.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f1f78d2189e0ddf9ac2b7a9b9bd4f0c66f54d1389ff6c17e9fd9dc034d06eb3f", size = 2812008, upload-time = "2024-05-09T18:32:59.886Z" }, + { url = "https://files.pythonhosted.org/packages/c8/91/2d517db61845698f41a2a974de90762e50faeb529201c6b3574935969045/pypdfium2-4.30.0-py3-none-musllinux_1_1_aarch64.whl", hash = "sha256:5eda3641a2da7a7a0b2f4dbd71d706401a656fea521b6b6faa0675b15d31a163", size = 6181543, upload-time = "2024-05-09T18:33:02.597Z" }, + { url = "https://files.pythonhosted.org/packages/ba/c4/ed1315143a7a84b2c7616569dfb472473968d628f17c231c39e29ae9d780/pypdfium2-4.30.0-py3-none-musllinux_1_1_i686.whl", hash = "sha256:0dfa61421b5eb68e1188b0b2231e7ba35735aef2d867d86e48ee6cab6975195e", size = 6175911, upload-time = "2024-05-09T18:33:05.376Z" }, + { url = "https://files.pythonhosted.org/packages/7a/c4/9e62d03f414e0e3051c56d5943c3bf42aa9608ede4e19dc96438364e9e03/pypdfium2-4.30.0-py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:f33bd79e7a09d5f7acca3b0b69ff6c8a488869a7fab48fdf400fec6e20b9c8be", size = 6267430, upload-time = "2024-05-09T18:33:08.067Z" }, + { url = "https://files.pythonhosted.org/packages/90/47/eda4904f715fb98561e34012826e883816945934a851745570521ec89520/pypdfium2-4.30.0-py3-none-win32.whl", hash = "sha256:ee2410f15d576d976c2ab2558c93d392a25fb9f6635e8dd0a8a3a5241b275e0e", size = 2775951, upload-time = "2024-05-09T18:33:10.567Z" }, + { url = "https://files.pythonhosted.org/packages/25/bd/56d9ec6b9f0fc4e0d95288759f3179f0fcd34b1a1526b75673d2f6d5196f/pypdfium2-4.30.0-py3-none-win_amd64.whl", hash = "sha256:90dbb2ac07be53219f56be09961eb95cf2473f834d01a42d901d13ccfad64b4c", size = 2892098, upload-time = "2024-05-09T18:33:13.107Z" }, + { url = "https://files.pythonhosted.org/packages/be/7a/097801205b991bc3115e8af1edb850d30aeaf0118520b016354cf5ccd3f6/pypdfium2-4.30.0-py3-none-win_arm64.whl", hash = "sha256:119b2969a6d6b1e8d55e99caaf05290294f2d0fe49c12a3f17102d01c441bd29", size = 2752118, upload-time = "2024-05-09T18:33:15.489Z" }, ] [[package]] name = "pypika" version = "0.48.9" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/c7/2c/94ed7b91db81d61d7096ac8f2d325ec562fc75e35f3baea8749c85b28784/PyPika-0.48.9.tar.gz", hash = "sha256:838836a61747e7c8380cd1b7ff638694b7a7335345d0f559b04b2cd832ad5378", size = 67259 } +sdist = { url = "https://files.pythonhosted.org/packages/c7/2c/94ed7b91db81d61d7096ac8f2d325ec562fc75e35f3baea8749c85b28784/PyPika-0.48.9.tar.gz", hash = "sha256:838836a61747e7c8380cd1b7ff638694b7a7335345d0f559b04b2cd832ad5378", size = 67259, upload-time = "2022-03-15T11:22:57.066Z" } [[package]] name = "pyproject-hooks" version = "1.2.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e7/82/28175b2414effca1cdac8dc99f76d660e7a4fb0ceefa4b4ab8f5f6742925/pyproject_hooks-1.2.0.tar.gz", hash = "sha256:1e859bd5c40fae9448642dd871adf459e5e2084186e8d2c2a79a824c970da1f8", size = 19228 } +sdist = { url = "https://files.pythonhosted.org/packages/e7/82/28175b2414effca1cdac8dc99f76d660e7a4fb0ceefa4b4ab8f5f6742925/pyproject_hooks-1.2.0.tar.gz", hash = "sha256:1e859bd5c40fae9448642dd871adf459e5e2084186e8d2c2a79a824c970da1f8", size = 19228, upload-time = "2024-09-29T09:24:13.293Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/bd/24/12818598c362d7f300f18e74db45963dbcb85150324092410c8b49405e42/pyproject_hooks-1.2.0-py3-none-any.whl", hash = "sha256:9e5c6bfa8dcc30091c74b0cf803c81fdd29d94f01992a7707bc97babb1141913", size = 10216 }, + { url = "https://files.pythonhosted.org/packages/bd/24/12818598c362d7f300f18e74db45963dbcb85150324092410c8b49405e42/pyproject_hooks-1.2.0-py3-none-any.whl", hash = "sha256:9e5c6bfa8dcc30091c74b0cf803c81fdd29d94f01992a7707bc97babb1141913", size = 10216, upload-time = "2024-09-29T09:24:11.978Z" }, ] [[package]] name = "pyreadline3" version = "3.5.4" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/0f/49/4cea918a08f02817aabae639e3d0ac046fef9f9180518a3ad394e22da148/pyreadline3-3.5.4.tar.gz", hash = "sha256:8d57d53039a1c75adba8e50dd3d992b28143480816187ea5efbd5c78e6c885b7", size = 99839 } +sdist = { url = "https://files.pythonhosted.org/packages/0f/49/4cea918a08f02817aabae639e3d0ac046fef9f9180518a3ad394e22da148/pyreadline3-3.5.4.tar.gz", hash = "sha256:8d57d53039a1c75adba8e50dd3d992b28143480816187ea5efbd5c78e6c885b7", size = 99839, upload-time = "2024-09-19T02:40:10.062Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/5a/dc/491b7661614ab97483abf2056be1deee4dc2490ecbf7bff9ab5cdbac86e1/pyreadline3-3.5.4-py3-none-any.whl", hash = "sha256:eaf8e6cc3c49bcccf145fc6067ba8643d1df34d604a1ec0eccbf7a18e6d3fae6", size = 83178 }, + { url = "https://files.pythonhosted.org/packages/5a/dc/491b7661614ab97483abf2056be1deee4dc2490ecbf7bff9ab5cdbac86e1/pyreadline3-3.5.4-py3-none-any.whl", hash = "sha256:eaf8e6cc3c49bcccf145fc6067ba8643d1df34d604a1ec0eccbf7a18e6d3fae6", size = 83178, upload-time = "2024-09-19T02:40:08.598Z" }, ] [[package]] @@ -4334,9 +4514,9 @@ dependencies = [ { name = "packaging" }, { name = "pluggy" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ae/3c/c9d525a414d506893f0cd8a8d0de7706446213181570cdbd766691164e40/pytest-8.3.5.tar.gz", hash = "sha256:f4efe70cc14e511565ac476b57c279e12a855b11f48f212af1080ef2263d3845", size = 1450891 } +sdist = { url = "https://files.pythonhosted.org/packages/ae/3c/c9d525a414d506893f0cd8a8d0de7706446213181570cdbd766691164e40/pytest-8.3.5.tar.gz", hash = "sha256:f4efe70cc14e511565ac476b57c279e12a855b11f48f212af1080ef2263d3845", size = 1450891, upload-time = "2025-03-02T12:54:54.503Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/30/3d/64ad57c803f1fa1e963a7946b6e0fea4a70df53c1a7fed304586539c2bac/pytest-8.3.5-py3-none-any.whl", hash = "sha256:c69214aa47deac29fad6c2a4f590b9c4a9fdb16a403176fe154b79c0b4d4d820", size = 343634 }, + { url = "https://files.pythonhosted.org/packages/30/3d/64ad57c803f1fa1e963a7946b6e0fea4a70df53c1a7fed304586539c2bac/pytest-8.3.5-py3-none-any.whl", hash = "sha256:c69214aa47deac29fad6c2a4f590b9c4a9fdb16a403176fe154b79c0b4d4d820", size = 343634, upload-time = "2025-03-02T12:54:52.069Z" }, ] [[package]] @@ -4347,9 +4527,9 @@ dependencies = [ { name = "py-cpuinfo" }, { name = "pytest" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/28/08/e6b0067efa9a1f2a1eb3043ecd8a0c48bfeb60d3255006dcc829d72d5da2/pytest-benchmark-4.0.0.tar.gz", hash = "sha256:fb0785b83efe599a6a956361c0691ae1dbb5318018561af10f3e915caa0048d1", size = 334641 } +sdist = { url = "https://files.pythonhosted.org/packages/28/08/e6b0067efa9a1f2a1eb3043ecd8a0c48bfeb60d3255006dcc829d72d5da2/pytest-benchmark-4.0.0.tar.gz", hash = "sha256:fb0785b83efe599a6a956361c0691ae1dbb5318018561af10f3e915caa0048d1", size = 334641, upload-time = "2022-10-25T21:21:55.686Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/4d/a1/3b70862b5b3f830f0422844f25a823d0470739d994466be9dbbbb414d85a/pytest_benchmark-4.0.0-py3-none-any.whl", hash = "sha256:fdb7db64e31c8b277dff9850d2a2556d8b60bcb0ea6524e36e28ffd7c87f71d6", size = 43951 }, + { url = "https://files.pythonhosted.org/packages/4d/a1/3b70862b5b3f830f0422844f25a823d0470739d994466be9dbbbb414d85a/pytest_benchmark-4.0.0-py3-none-any.whl", hash = "sha256:fdb7db64e31c8b277dff9850d2a2556d8b60bcb0ea6524e36e28ffd7c87f71d6", size = 43951, upload-time = "2022-10-25T21:21:53.208Z" }, ] [[package]] @@ -4360,9 +4540,9 @@ dependencies = [ { name = "coverage", extra = ["toml"] }, { name = "pytest" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/7a/15/da3df99fd551507694a9b01f512a2f6cf1254f33601605843c3775f39460/pytest-cov-4.1.0.tar.gz", hash = "sha256:3904b13dfbfec47f003b8e77fd5b589cd11904a21ddf1ab38a64f204d6a10ef6", size = 63245 } +sdist = { url = "https://files.pythonhosted.org/packages/7a/15/da3df99fd551507694a9b01f512a2f6cf1254f33601605843c3775f39460/pytest-cov-4.1.0.tar.gz", hash = "sha256:3904b13dfbfec47f003b8e77fd5b589cd11904a21ddf1ab38a64f204d6a10ef6", size = 63245, upload-time = "2023-05-24T18:44:56.845Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a7/4b/8b78d126e275efa2379b1c2e09dc52cf70df16fc3b90613ef82531499d73/pytest_cov-4.1.0-py3-none-any.whl", hash = "sha256:6ba70b9e97e69fcc3fb45bfeab2d0a138fb65c4d0d6a41ef33983ad114be8c3a", size = 21949 }, + { url = "https://files.pythonhosted.org/packages/a7/4b/8b78d126e275efa2379b1c2e09dc52cf70df16fc3b90613ef82531499d73/pytest_cov-4.1.0-py3-none-any.whl", hash = "sha256:6ba70b9e97e69fcc3fb45bfeab2d0a138fb65c4d0d6a41ef33983ad114be8c3a", size = 21949, upload-time = "2023-05-24T18:44:54.079Z" }, ] [[package]] @@ -4372,21 +4552,21 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pytest" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/1f/31/27f28431a16b83cab7a636dce59cf397517807d247caa38ee67d65e71ef8/pytest_env-1.1.5.tar.gz", hash = "sha256:91209840aa0e43385073ac464a554ad2947cc2fd663a9debf88d03b01e0cc1cf", size = 8911 } +sdist = { url = "https://files.pythonhosted.org/packages/1f/31/27f28431a16b83cab7a636dce59cf397517807d247caa38ee67d65e71ef8/pytest_env-1.1.5.tar.gz", hash = "sha256:91209840aa0e43385073ac464a554ad2947cc2fd663a9debf88d03b01e0cc1cf", size = 8911, upload-time = "2024-09-17T22:39:18.566Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/de/b8/87cfb16045c9d4092cfcf526135d73b88101aac83bc1adcf82dfb5fd3833/pytest_env-1.1.5-py3-none-any.whl", hash = "sha256:ce90cf8772878515c24b31cd97c7fa1f4481cd68d588419fd45f10ecaee6bc30", size = 6141 }, + { url = "https://files.pythonhosted.org/packages/de/b8/87cfb16045c9d4092cfcf526135d73b88101aac83bc1adcf82dfb5fd3833/pytest_env-1.1.5-py3-none-any.whl", hash = "sha256:ce90cf8772878515c24b31cd97c7fa1f4481cd68d588419fd45f10ecaee6bc30", size = 6141, upload-time = "2024-09-17T22:39:16.942Z" }, ] [[package]] name = "pytest-mock" -version = "3.14.0" +version = "3.14.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pytest" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c6/90/a955c3ab35ccd41ad4de556596fa86685bf4fc5ffcc62d22d856cfd4e29a/pytest-mock-3.14.0.tar.gz", hash = "sha256:2719255a1efeceadbc056d6bf3df3d1c5015530fb40cf347c0f9afac88410bd0", size = 32814 } +sdist = { url = "https://files.pythonhosted.org/packages/71/28/67172c96ba684058a4d24ffe144d64783d2a270d0af0d9e792737bddc75c/pytest_mock-3.14.1.tar.gz", hash = "sha256:159e9edac4c451ce77a5cdb9fc5d1100708d2dd4ba3c3df572f14097351af80e", size = 33241, upload-time = "2025-05-26T13:58:45.167Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f2/3b/b26f90f74e2986a82df6e7ac7e319b8ea7ccece1caec9f8ab6104dc70603/pytest_mock-3.14.0-py3-none-any.whl", hash = "sha256:0b72c38033392a5f4621342fe11e9219ac11ec9d375f8e2a0c164539e0d70f6f", size = 9863 }, + { url = "https://files.pythonhosted.org/packages/b2/05/77b60e520511c53d1c1ca75f1930c7dd8e971d0c4379b7f4b3f9644685ba/pytest_mock-3.14.1-py3-none-any.whl", hash = "sha256:178aefcd11307d874b4cd3100344e7e2d888d9791a6a1d9bfe90fbc1b74fd1d0", size = 9923, upload-time = "2025-05-26T13:58:43.487Z" }, ] [[package]] @@ -4396,34 +4576,34 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "packaging" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/6b/21/387b92059909e741af7837194d84250335d2a057f614752b6364aaaa2f56/python_calamine-0.3.2.tar.gz", hash = "sha256:5cf12f2086373047cdea681711857b672cba77a34a66dd3755d60686fc974e06", size = 117336 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ef/b7/d59863ebe319150739d0c352c6dea2710a2f90254ed32304d52e8349edce/python_calamine-0.3.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:5251746816069c38eafdd1e4eb7b83870e1fe0ff6191ce9a809b187ffba8ce93", size = 830854 }, - { url = "https://files.pythonhosted.org/packages/d3/01/b48c6f2c2e530a1a031199c5c5bf35f7c2cf7f16f3989263e616e3bc86ce/python_calamine-0.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9775dbc93bc635d48f45433f8869a546cca28c2a86512581a05333f97a18337b", size = 809411 }, - { url = "https://files.pythonhosted.org/packages/fe/6d/69c53ffb11b3ee1bf5bd945cc2514848adea492c879a50f38e2ed4424727/python_calamine-0.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6ff4318b72ba78e8a04fb4c45342cfa23eab6f81ecdb85548cdab9f2db8ac9c7", size = 872905 }, - { url = "https://files.pythonhosted.org/packages/be/ec/b02c4bc04c426d153af1f5ff07e797dd81ada6f47c170e0207d07c90b53a/python_calamine-0.3.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0cd8eb1ef8644da71788a33d3de602d1c08ff1c4136942d87e25f09580b512ef", size = 876464 }, - { url = "https://files.pythonhosted.org/packages/46/ef/8403ee595207de5bd277279b56384b31390987df8a61c280b4176802481a/python_calamine-0.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9dcfd560d8f88f39d23b829f666ebae4bd8daeec7ed57adfb9313543f3c5fa35", size = 942289 }, - { url = "https://files.pythonhosted.org/packages/89/97/b4e5b77c70b36613c10f2dbeece75b5d43727335a33bf5176792ec83c3fc/python_calamine-0.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e5e79b9eae4b30c82d045f9952314137c7089c88274e1802947f9e3adb778a59", size = 978699 }, - { url = "https://files.pythonhosted.org/packages/5f/e9/03bbafd6b11cdf70c004f2e856978fc252ec5ea7e77529f14f969134c7a8/python_calamine-0.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce5e8cc518c8e3e5988c5c658f9dcd8229f5541ca63353175bb15b6ad8c456d0", size = 886008 }, - { url = "https://files.pythonhosted.org/packages/7b/20/e18f534e49b403ba0b979a4dfead146001d867f5be846b91f81ed5377972/python_calamine-0.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2a0e596b1346c28b2de15c9f86186cceefa4accb8882992aa0b7499c593446ed", size = 925104 }, - { url = "https://files.pythonhosted.org/packages/54/4c/58933e69a0a7871487d10b958c1f83384bc430d53efbbfbf1dea141a0d85/python_calamine-0.3.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:f521de16a9f3e951ec2e5e35d76752fe004088dbac4cdbf4dd62d0ad2bbf650f", size = 1050448 }, - { url = "https://files.pythonhosted.org/packages/83/95/5c96d093eaaa2d15c63b43bcf8c87708eaab8428c72b6ebdcafc2604aa47/python_calamine-0.3.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:417d6825a36bba526ae17bed1b6ca576fbb54e23dc60c97eeb536c622e77c62f", size = 1056840 }, - { url = "https://files.pythonhosted.org/packages/23/e0/b03cc3ad4f40fd3be0ebac0b71d273864ddf2bf0e611ec309328fdedded9/python_calamine-0.3.2-cp311-cp311-win32.whl", hash = "sha256:cd3ea1ca768139753633f9f0b16997648db5919894579f363d71f914f85f7ade", size = 663268 }, - { url = "https://files.pythonhosted.org/packages/6b/bd/550da64770257fc70a185482f6353c0654a11f381227e146bb0170db040f/python_calamine-0.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:4560100412d8727c49048cca102eadeb004f91cfb9c99ae63cd7d4dc0a61333a", size = 692393 }, - { url = "https://files.pythonhosted.org/packages/be/2e/0b4b7a146c3bb41116fe8e59a2f616340786db12aed51c7a9e75817cfa03/python_calamine-0.3.2-cp311-cp311-win_arm64.whl", hash = "sha256:a2526e6ba79087b1634f49064800339edb7316780dd7e1e86d10a0ca9de4e90f", size = 667312 }, - { url = "https://files.pythonhosted.org/packages/f2/0f/c2e3e3bae774dae47cba6ffa640ff95525bd6a10a13d3cd998f33aeafc7f/python_calamine-0.3.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:7c063b1f783352d6c6792305b2b0123784882e2436b638a9b9a1e97f6d74fa51", size = 825179 }, - { url = "https://files.pythonhosted.org/packages/c7/81/a05285f06d71ea38ab99b09f3119f93f575487c9d24d7a1bab65657b258b/python_calamine-0.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:85016728937e8f5d1810ff3c9603ffd2458d66e34d495202d7759fa8219871cd", size = 804036 }, - { url = "https://files.pythonhosted.org/packages/24/b5/320f366ffd91ee5d5f0f77817d4fb684f62a5a68e438dcdb90e4f5f35137/python_calamine-0.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:81f243323bf712bb0b2baf0b938a2e6d6c9fa3b9902a44c0654474d04f999fac", size = 871527 }, - { url = "https://files.pythonhosted.org/packages/13/19/063afced19620b829697b90329c62ad73274cc38faaa91d9ee41047f5f8c/python_calamine-0.3.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0b719dd2b10237b0cfb2062e3eaf199f220918a5623197e8449f37c8de845a7c", size = 875411 }, - { url = "https://files.pythonhosted.org/packages/d7/6a/c93c52414ec62cc51c4820aff434f03c4a1c69ced15cec3e4b93885e4012/python_calamine-0.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d5158310b9140e8ee8665c9541a11030901e7275eb036988150c93f01c5133bf", size = 943525 }, - { url = "https://files.pythonhosted.org/packages/0a/0a/5bdecee03d235e8d111b1e8ee3ea0c0ed4ae43a402f75cebbe719930cf04/python_calamine-0.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b2c1b248e8bf10194c449cb57e6ccb3f2fe3dc86975a6d746908cf2d37b048cc", size = 976332 }, - { url = "https://files.pythonhosted.org/packages/05/ad/43ff92366856ee34f958e9cf4f5b98e63b0dc219e06ccba4ad6f63463756/python_calamine-0.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f3a13ad8e5b6843a73933b8d1710bc4df39a9152cb57c11227ad51f47b5838a4", size = 885549 }, - { url = "https://files.pythonhosted.org/packages/ff/b9/76afb867e2bb4bfc296446b741cee01ae4ce6a094b43f4ed4eaed5189de4/python_calamine-0.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fe950975a5758423c982ce1e2fdcb5c9c664d1a20b41ea21e619e5003bb4f96b", size = 926005 }, - { url = "https://files.pythonhosted.org/packages/23/cf/5252b237b0e70c263f86741aea02e8e57aedb2bce9898468be1d9d55b9da/python_calamine-0.3.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:8707622ba816d6c26e36f1506ecda66a6a6cf43e55a43a8ef4c3bf8a805d3cfb", size = 1049380 }, - { url = "https://files.pythonhosted.org/packages/1a/4d/f151e8923e53457ca49ceeaa3a34cb23afee7d7b46e6546ab2a29adc9125/python_calamine-0.3.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:e6eac46475c26e162a037f6711b663767f61f8fca3daffeb35aa3fc7ee6267cc", size = 1056720 }, - { url = "https://files.pythonhosted.org/packages/f5/cb/1b5db3e4a8bbaaaa7706b270570d4a65133618fa0ca7efafe5ce680f6cee/python_calamine-0.3.2-cp312-cp312-win32.whl", hash = "sha256:0dee82aedef3db27368a388d6741d69334c1d4d7a8087ddd33f1912166e17e37", size = 663502 }, - { url = "https://files.pythonhosted.org/packages/5a/53/920fa8e7b570647c08da0f1158d781db2e318918b06cb28fe0363c3398ac/python_calamine-0.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:ae09b779718809d31ca5d722464be2776b7d79278b1da56e159bbbe11880eecf", size = 692660 }, - { url = "https://files.pythonhosted.org/packages/a5/ea/5d0ecf5c345c4d78964a5f97e61848bc912965b276a54fb8ae698a9419a8/python_calamine-0.3.2-cp312-cp312-win_arm64.whl", hash = "sha256:435546e401a5821fa70048b6c03a70db3b27d00037e2c4999c2126d8c40b51df", size = 666205 }, +sdist = { url = "https://files.pythonhosted.org/packages/6b/21/387b92059909e741af7837194d84250335d2a057f614752b6364aaaa2f56/python_calamine-0.3.2.tar.gz", hash = "sha256:5cf12f2086373047cdea681711857b672cba77a34a66dd3755d60686fc974e06", size = 117336, upload-time = "2025-04-02T10:06:23.14Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ef/b7/d59863ebe319150739d0c352c6dea2710a2f90254ed32304d52e8349edce/python_calamine-0.3.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:5251746816069c38eafdd1e4eb7b83870e1fe0ff6191ce9a809b187ffba8ce93", size = 830854, upload-time = "2025-04-02T10:04:14.673Z" }, + { url = "https://files.pythonhosted.org/packages/d3/01/b48c6f2c2e530a1a031199c5c5bf35f7c2cf7f16f3989263e616e3bc86ce/python_calamine-0.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9775dbc93bc635d48f45433f8869a546cca28c2a86512581a05333f97a18337b", size = 809411, upload-time = "2025-04-02T10:04:16.067Z" }, + { url = "https://files.pythonhosted.org/packages/fe/6d/69c53ffb11b3ee1bf5bd945cc2514848adea492c879a50f38e2ed4424727/python_calamine-0.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6ff4318b72ba78e8a04fb4c45342cfa23eab6f81ecdb85548cdab9f2db8ac9c7", size = 872905, upload-time = "2025-04-02T10:04:17.487Z" }, + { url = "https://files.pythonhosted.org/packages/be/ec/b02c4bc04c426d153af1f5ff07e797dd81ada6f47c170e0207d07c90b53a/python_calamine-0.3.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0cd8eb1ef8644da71788a33d3de602d1c08ff1c4136942d87e25f09580b512ef", size = 876464, upload-time = "2025-04-02T10:04:19.53Z" }, + { url = "https://files.pythonhosted.org/packages/46/ef/8403ee595207de5bd277279b56384b31390987df8a61c280b4176802481a/python_calamine-0.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9dcfd560d8f88f39d23b829f666ebae4bd8daeec7ed57adfb9313543f3c5fa35", size = 942289, upload-time = "2025-04-02T10:04:20.902Z" }, + { url = "https://files.pythonhosted.org/packages/89/97/b4e5b77c70b36613c10f2dbeece75b5d43727335a33bf5176792ec83c3fc/python_calamine-0.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e5e79b9eae4b30c82d045f9952314137c7089c88274e1802947f9e3adb778a59", size = 978699, upload-time = "2025-04-02T10:04:22.263Z" }, + { url = "https://files.pythonhosted.org/packages/5f/e9/03bbafd6b11cdf70c004f2e856978fc252ec5ea7e77529f14f969134c7a8/python_calamine-0.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce5e8cc518c8e3e5988c5c658f9dcd8229f5541ca63353175bb15b6ad8c456d0", size = 886008, upload-time = "2025-04-02T10:04:23.754Z" }, + { url = "https://files.pythonhosted.org/packages/7b/20/e18f534e49b403ba0b979a4dfead146001d867f5be846b91f81ed5377972/python_calamine-0.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2a0e596b1346c28b2de15c9f86186cceefa4accb8882992aa0b7499c593446ed", size = 925104, upload-time = "2025-04-02T10:04:25.255Z" }, + { url = "https://files.pythonhosted.org/packages/54/4c/58933e69a0a7871487d10b958c1f83384bc430d53efbbfbf1dea141a0d85/python_calamine-0.3.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:f521de16a9f3e951ec2e5e35d76752fe004088dbac4cdbf4dd62d0ad2bbf650f", size = 1050448, upload-time = "2025-04-02T10:04:26.649Z" }, + { url = "https://files.pythonhosted.org/packages/83/95/5c96d093eaaa2d15c63b43bcf8c87708eaab8428c72b6ebdcafc2604aa47/python_calamine-0.3.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:417d6825a36bba526ae17bed1b6ca576fbb54e23dc60c97eeb536c622e77c62f", size = 1056840, upload-time = "2025-04-02T10:04:28.18Z" }, + { url = "https://files.pythonhosted.org/packages/23/e0/b03cc3ad4f40fd3be0ebac0b71d273864ddf2bf0e611ec309328fdedded9/python_calamine-0.3.2-cp311-cp311-win32.whl", hash = "sha256:cd3ea1ca768139753633f9f0b16997648db5919894579f363d71f914f85f7ade", size = 663268, upload-time = "2025-04-02T10:04:29.659Z" }, + { url = "https://files.pythonhosted.org/packages/6b/bd/550da64770257fc70a185482f6353c0654a11f381227e146bb0170db040f/python_calamine-0.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:4560100412d8727c49048cca102eadeb004f91cfb9c99ae63cd7d4dc0a61333a", size = 692393, upload-time = "2025-04-02T10:04:31.534Z" }, + { url = "https://files.pythonhosted.org/packages/be/2e/0b4b7a146c3bb41116fe8e59a2f616340786db12aed51c7a9e75817cfa03/python_calamine-0.3.2-cp311-cp311-win_arm64.whl", hash = "sha256:a2526e6ba79087b1634f49064800339edb7316780dd7e1e86d10a0ca9de4e90f", size = 667312, upload-time = "2025-04-02T10:04:32.911Z" }, + { url = "https://files.pythonhosted.org/packages/f2/0f/c2e3e3bae774dae47cba6ffa640ff95525bd6a10a13d3cd998f33aeafc7f/python_calamine-0.3.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:7c063b1f783352d6c6792305b2b0123784882e2436b638a9b9a1e97f6d74fa51", size = 825179, upload-time = "2025-04-02T10:04:34.377Z" }, + { url = "https://files.pythonhosted.org/packages/c7/81/a05285f06d71ea38ab99b09f3119f93f575487c9d24d7a1bab65657b258b/python_calamine-0.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:85016728937e8f5d1810ff3c9603ffd2458d66e34d495202d7759fa8219871cd", size = 804036, upload-time = "2025-04-02T10:04:35.938Z" }, + { url = "https://files.pythonhosted.org/packages/24/b5/320f366ffd91ee5d5f0f77817d4fb684f62a5a68e438dcdb90e4f5f35137/python_calamine-0.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:81f243323bf712bb0b2baf0b938a2e6d6c9fa3b9902a44c0654474d04f999fac", size = 871527, upload-time = "2025-04-02T10:04:38.272Z" }, + { url = "https://files.pythonhosted.org/packages/13/19/063afced19620b829697b90329c62ad73274cc38faaa91d9ee41047f5f8c/python_calamine-0.3.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0b719dd2b10237b0cfb2062e3eaf199f220918a5623197e8449f37c8de845a7c", size = 875411, upload-time = "2025-04-02T10:04:39.647Z" }, + { url = "https://files.pythonhosted.org/packages/d7/6a/c93c52414ec62cc51c4820aff434f03c4a1c69ced15cec3e4b93885e4012/python_calamine-0.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d5158310b9140e8ee8665c9541a11030901e7275eb036988150c93f01c5133bf", size = 943525, upload-time = "2025-04-02T10:04:41.025Z" }, + { url = "https://files.pythonhosted.org/packages/0a/0a/5bdecee03d235e8d111b1e8ee3ea0c0ed4ae43a402f75cebbe719930cf04/python_calamine-0.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b2c1b248e8bf10194c449cb57e6ccb3f2fe3dc86975a6d746908cf2d37b048cc", size = 976332, upload-time = "2025-04-02T10:04:42.454Z" }, + { url = "https://files.pythonhosted.org/packages/05/ad/43ff92366856ee34f958e9cf4f5b98e63b0dc219e06ccba4ad6f63463756/python_calamine-0.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f3a13ad8e5b6843a73933b8d1710bc4df39a9152cb57c11227ad51f47b5838a4", size = 885549, upload-time = "2025-04-02T10:04:43.869Z" }, + { url = "https://files.pythonhosted.org/packages/ff/b9/76afb867e2bb4bfc296446b741cee01ae4ce6a094b43f4ed4eaed5189de4/python_calamine-0.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fe950975a5758423c982ce1e2fdcb5c9c664d1a20b41ea21e619e5003bb4f96b", size = 926005, upload-time = "2025-04-02T10:04:45.884Z" }, + { url = "https://files.pythonhosted.org/packages/23/cf/5252b237b0e70c263f86741aea02e8e57aedb2bce9898468be1d9d55b9da/python_calamine-0.3.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:8707622ba816d6c26e36f1506ecda66a6a6cf43e55a43a8ef4c3bf8a805d3cfb", size = 1049380, upload-time = "2025-04-02T10:04:49.202Z" }, + { url = "https://files.pythonhosted.org/packages/1a/4d/f151e8923e53457ca49ceeaa3a34cb23afee7d7b46e6546ab2a29adc9125/python_calamine-0.3.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:e6eac46475c26e162a037f6711b663767f61f8fca3daffeb35aa3fc7ee6267cc", size = 1056720, upload-time = "2025-04-02T10:04:51.002Z" }, + { url = "https://files.pythonhosted.org/packages/f5/cb/1b5db3e4a8bbaaaa7706b270570d4a65133618fa0ca7efafe5ce680f6cee/python_calamine-0.3.2-cp312-cp312-win32.whl", hash = "sha256:0dee82aedef3db27368a388d6741d69334c1d4d7a8087ddd33f1912166e17e37", size = 663502, upload-time = "2025-04-02T10:04:52.402Z" }, + { url = "https://files.pythonhosted.org/packages/5a/53/920fa8e7b570647c08da0f1158d781db2e318918b06cb28fe0363c3398ac/python_calamine-0.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:ae09b779718809d31ca5d722464be2776b7d79278b1da56e159bbbe11880eecf", size = 692660, upload-time = "2025-04-02T10:04:53.721Z" }, + { url = "https://files.pythonhosted.org/packages/a5/ea/5d0ecf5c345c4d78964a5f97e61848bc912965b276a54fb8ae698a9419a8/python_calamine-0.3.2-cp312-cp312-win_arm64.whl", hash = "sha256:435546e401a5821fa70048b6c03a70db3b27d00037e2c4999c2126d8c40b51df", size = 666205, upload-time = "2025-04-02T10:04:56.377Z" }, ] [[package]] @@ -4433,9 +4613,9 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "six" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432 } +sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432, upload-time = "2024-03-01T18:36:20.211Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892 }, + { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" }, ] [[package]] @@ -4446,36 +4626,36 @@ dependencies = [ { name = "lxml" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/35/e4/386c514c53684772885009c12b67a7edd526c15157778ac1b138bc75063e/python_docx-1.1.2.tar.gz", hash = "sha256:0cf1f22e95b9002addca7948e16f2cd7acdfd498047f1941ca5d293db7762efd", size = 5656581 } +sdist = { url = "https://files.pythonhosted.org/packages/35/e4/386c514c53684772885009c12b67a7edd526c15157778ac1b138bc75063e/python_docx-1.1.2.tar.gz", hash = "sha256:0cf1f22e95b9002addca7948e16f2cd7acdfd498047f1941ca5d293db7762efd", size = 5656581, upload-time = "2024-05-01T19:41:57.772Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/3e/3d/330d9efbdb816d3f60bf2ad92f05e1708e4a1b9abe80461ac3444c83f749/python_docx-1.1.2-py3-none-any.whl", hash = "sha256:08c20d6058916fb19853fcf080f7f42b6270d89eac9fa5f8c15f691c0017fabe", size = 244315 }, + { url = "https://files.pythonhosted.org/packages/3e/3d/330d9efbdb816d3f60bf2ad92f05e1708e4a1b9abe80461ac3444c83f749/python_docx-1.1.2-py3-none-any.whl", hash = "sha256:08c20d6058916fb19853fcf080f7f42b6270d89eac9fa5f8c15f691c0017fabe", size = 244315, upload-time = "2024-05-01T19:41:47.006Z" }, ] [[package]] name = "python-dotenv" version = "1.0.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/bc/57/e84d88dfe0aec03b7a2d4327012c1627ab5f03652216c63d49846d7a6c58/python-dotenv-1.0.1.tar.gz", hash = "sha256:e324ee90a023d808f1959c46bcbc04446a10ced277783dc6ee09987c37ec10ca", size = 39115 } +sdist = { url = "https://files.pythonhosted.org/packages/bc/57/e84d88dfe0aec03b7a2d4327012c1627ab5f03652216c63d49846d7a6c58/python-dotenv-1.0.1.tar.gz", hash = "sha256:e324ee90a023d808f1959c46bcbc04446a10ced277783dc6ee09987c37ec10ca", size = 39115, upload-time = "2024-01-23T06:33:00.505Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/6a/3e/b68c118422ec867fa7ab88444e1274aa40681c606d59ac27de5a5588f082/python_dotenv-1.0.1-py3-none-any.whl", hash = "sha256:f7b63ef50f1b690dddf550d03497b66d609393b40b564ed0d674909a68ebf16a", size = 19863 }, + { url = "https://files.pythonhosted.org/packages/6a/3e/b68c118422ec867fa7ab88444e1274aa40681c606d59ac27de5a5588f082/python_dotenv-1.0.1-py3-none-any.whl", hash = "sha256:f7b63ef50f1b690dddf550d03497b66d609393b40b564ed0d674909a68ebf16a", size = 19863, upload-time = "2024-01-23T06:32:58.246Z" }, ] [[package]] name = "python-iso639" version = "2025.2.18" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d5/19/45aa1917c7b1f4eb71104795b9b0cbf97169b99ec46cd303445883536549/python_iso639-2025.2.18.tar.gz", hash = "sha256:34e31e8e76eb3fc839629e257b12bcfd957c6edcbd486bbf66ba5185d1f566e8", size = 173552 } +sdist = { url = "https://files.pythonhosted.org/packages/d5/19/45aa1917c7b1f4eb71104795b9b0cbf97169b99ec46cd303445883536549/python_iso639-2025.2.18.tar.gz", hash = "sha256:34e31e8e76eb3fc839629e257b12bcfd957c6edcbd486bbf66ba5185d1f566e8", size = 173552, upload-time = "2025-02-18T13:48:08.607Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/54/a3/3ceaf89a17a1e1d5e7bbdfe5514aa3055d91285b37a5c8fed662969e3d56/python_iso639-2025.2.18-py3-none-any.whl", hash = "sha256:b2d471c37483a26f19248458b20e7bd96492e15368b01053b540126bcc23152f", size = 167631 }, + { url = "https://files.pythonhosted.org/packages/54/a3/3ceaf89a17a1e1d5e7bbdfe5514aa3055d91285b37a5c8fed662969e3d56/python_iso639-2025.2.18-py3-none-any.whl", hash = "sha256:b2d471c37483a26f19248458b20e7bd96492e15368b01053b540126bcc23152f", size = 167631, upload-time = "2025-02-18T13:48:06.602Z" }, ] [[package]] name = "python-magic" version = "0.4.27" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/da/db/0b3e28ac047452d079d375ec6798bf76a036a08182dbb39ed38116a49130/python-magic-0.4.27.tar.gz", hash = "sha256:c1ba14b08e4a5f5c31a302b7721239695b2f0f058d125bd5ce1ee36b9d9d3c3b", size = 14677 } +sdist = { url = "https://files.pythonhosted.org/packages/da/db/0b3e28ac047452d079d375ec6798bf76a036a08182dbb39ed38116a49130/python-magic-0.4.27.tar.gz", hash = "sha256:c1ba14b08e4a5f5c31a302b7721239695b2f0f058d125bd5ce1ee36b9d9d3c3b", size = 14677, upload-time = "2022-06-07T20:16:59.508Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/6c/73/9f872cb81fc5c3bb48f7227872c28975f998f3e7c2b1c16e95e6432bbb90/python_magic-0.4.27-py2.py3-none-any.whl", hash = "sha256:c212960ad306f700aa0d01e5d7a325d20548ff97eb9920dcd29513174f0294d3", size = 13840 }, + { url = "https://files.pythonhosted.org/packages/6c/73/9f872cb81fc5c3bb48f7227872c28975f998f3e7c2b1c16e95e6432bbb90/python_magic-0.4.27-py2.py3-none-any.whl", hash = "sha256:c212960ad306f700aa0d01e5d7a325d20548ff97eb9920dcd29513174f0294d3", size = 13840, upload-time = "2022-06-07T20:16:57.763Z" }, ] [[package]] @@ -4487,9 +4667,9 @@ dependencies = [ { name = "olefile" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a2/4e/869f34faedbc968796d2c7e9837dede079c9cb9750917356b1f1eda926e9/python_oxmsg-0.0.2.tar.gz", hash = "sha256:a6aff4deb1b5975d44d49dab1d9384089ffeec819e19c6940bc7ffbc84775fad", size = 34713 } +sdist = { url = "https://files.pythonhosted.org/packages/a2/4e/869f34faedbc968796d2c7e9837dede079c9cb9750917356b1f1eda926e9/python_oxmsg-0.0.2.tar.gz", hash = "sha256:a6aff4deb1b5975d44d49dab1d9384089ffeec819e19c6940bc7ffbc84775fad", size = 34713, upload-time = "2025-02-03T17:13:47.415Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/53/67/f56c69a98c7eb244025845506387d0f961681657c9fcd8b2d2edd148f9d2/python_oxmsg-0.0.2-py3-none-any.whl", hash = "sha256:22be29b14c46016bcd05e34abddfd8e05ee82082f53b82753d115da3fc7d0355", size = 31455 }, + { url = "https://files.pythonhosted.org/packages/53/67/f56c69a98c7eb244025845506387d0f961681657c9fcd8b2d2edd148f9d2/python_oxmsg-0.0.2-py3-none-any.whl", hash = "sha256:22be29b14c46016bcd05e34abddfd8e05ee82082f53b82753d115da3fc7d0355", size = 31455, upload-time = "2025-02-03T17:13:46.061Z" }, ] [[package]] @@ -4502,18 +4682,18 @@ dependencies = [ { name = "typing-extensions" }, { name = "xlsxwriter" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/52/a9/0c0db8d37b2b8a645666f7fd8accea4c6224e013c42b1d5c17c93590cd06/python_pptx-1.0.2.tar.gz", hash = "sha256:479a8af0eaf0f0d76b6f00b0887732874ad2e3188230315290cd1f9dd9cc7095", size = 10109297 } +sdist = { url = "https://files.pythonhosted.org/packages/52/a9/0c0db8d37b2b8a645666f7fd8accea4c6224e013c42b1d5c17c93590cd06/python_pptx-1.0.2.tar.gz", hash = "sha256:479a8af0eaf0f0d76b6f00b0887732874ad2e3188230315290cd1f9dd9cc7095", size = 10109297, upload-time = "2024-08-07T17:33:37.772Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d9/4f/00be2196329ebbff56ce564aa94efb0fbc828d00de250b1980de1a34ab49/python_pptx-1.0.2-py3-none-any.whl", hash = "sha256:160838e0b8565a8b1f67947675886e9fea18aa5e795db7ae531606d68e785cba", size = 472788 }, + { url = "https://files.pythonhosted.org/packages/d9/4f/00be2196329ebbff56ce564aa94efb0fbc828d00de250b1980de1a34ab49/python_pptx-1.0.2-py3-none-any.whl", hash = "sha256:160838e0b8565a8b1f67947675886e9fea18aa5e795db7ae531606d68e785cba", size = 472788, upload-time = "2024-08-07T17:33:28.192Z" }, ] [[package]] name = "pytz" version = "2025.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f8/bf/abbd3cdfb8fbc7fb3d4d38d320f2441b1e7cbe29be4f23797b4a2b5d8aac/pytz-2025.2.tar.gz", hash = "sha256:360b9e3dbb49a209c21ad61809c7fb453643e048b38924c765813546746e81c3", size = 320884 } +sdist = { url = "https://files.pythonhosted.org/packages/f8/bf/abbd3cdfb8fbc7fb3d4d38d320f2441b1e7cbe29be4f23797b4a2b5d8aac/pytz-2025.2.tar.gz", hash = "sha256:360b9e3dbb49a209c21ad61809c7fb453643e048b38924c765813546746e81c3", size = 320884, upload-time = "2025-03-25T02:25:00.538Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/81/c4/34e93fe5f5429d7570ec1fa436f1986fb1f00c3e0f43a589fe2bbcd22c3f/pytz-2025.2-py2.py3-none-any.whl", hash = "sha256:5ddf76296dd8c44c26eb8f4b6f35488f3ccbf6fbbd7adee0b7262d43f0ec2f00", size = 509225 }, + { url = "https://files.pythonhosted.org/packages/81/c4/34e93fe5f5429d7570ec1fa436f1986fb1f00c3e0f43a589fe2bbcd22c3f/pytz-2025.2-py2.py3-none-any.whl", hash = "sha256:5ddf76296dd8c44c26eb8f4b6f35488f3ccbf6fbbd7adee0b7262d43f0ec2f00", size = 509225, upload-time = "2025-03-25T02:24:58.468Z" }, ] [[package]] @@ -4521,52 +4701,52 @@ name = "pywin32" version = "310" source = { registry = "https://pypi.org/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f7/b1/68aa2986129fb1011dabbe95f0136f44509afaf072b12b8f815905a39f33/pywin32-310-cp311-cp311-win32.whl", hash = "sha256:1e765f9564e83011a63321bb9d27ec456a0ed90d3732c4b2e312b855365ed8bd", size = 8784284 }, - { url = "https://files.pythonhosted.org/packages/b3/bd/d1592635992dd8db5bb8ace0551bc3a769de1ac8850200cfa517e72739fb/pywin32-310-cp311-cp311-win_amd64.whl", hash = "sha256:126298077a9d7c95c53823934f000599f66ec9296b09167810eb24875f32689c", size = 9520748 }, - { url = "https://files.pythonhosted.org/packages/90/b1/ac8b1ffce6603849eb45a91cf126c0fa5431f186c2e768bf56889c46f51c/pywin32-310-cp311-cp311-win_arm64.whl", hash = "sha256:19ec5fc9b1d51c4350be7bb00760ffce46e6c95eaf2f0b2f1150657b1a43c582", size = 8455941 }, - { url = "https://files.pythonhosted.org/packages/6b/ec/4fdbe47932f671d6e348474ea35ed94227fb5df56a7c30cbbb42cd396ed0/pywin32-310-cp312-cp312-win32.whl", hash = "sha256:8a75a5cc3893e83a108c05d82198880704c44bbaee4d06e442e471d3c9ea4f3d", size = 8796239 }, - { url = "https://files.pythonhosted.org/packages/e3/e5/b0627f8bb84e06991bea89ad8153a9e50ace40b2e1195d68e9dff6b03d0f/pywin32-310-cp312-cp312-win_amd64.whl", hash = "sha256:bf5c397c9a9a19a6f62f3fb821fbf36cac08f03770056711f765ec1503972060", size = 9503839 }, - { url = "https://files.pythonhosted.org/packages/1f/32/9ccf53748df72301a89713936645a664ec001abd35ecc8578beda593d37d/pywin32-310-cp312-cp312-win_arm64.whl", hash = "sha256:2349cc906eae872d0663d4d6290d13b90621eaf78964bb1578632ff20e152966", size = 8459470 }, + { url = "https://files.pythonhosted.org/packages/f7/b1/68aa2986129fb1011dabbe95f0136f44509afaf072b12b8f815905a39f33/pywin32-310-cp311-cp311-win32.whl", hash = "sha256:1e765f9564e83011a63321bb9d27ec456a0ed90d3732c4b2e312b855365ed8bd", size = 8784284, upload-time = "2025-03-17T00:55:53.124Z" }, + { url = "https://files.pythonhosted.org/packages/b3/bd/d1592635992dd8db5bb8ace0551bc3a769de1ac8850200cfa517e72739fb/pywin32-310-cp311-cp311-win_amd64.whl", hash = "sha256:126298077a9d7c95c53823934f000599f66ec9296b09167810eb24875f32689c", size = 9520748, upload-time = "2025-03-17T00:55:55.203Z" }, + { url = "https://files.pythonhosted.org/packages/90/b1/ac8b1ffce6603849eb45a91cf126c0fa5431f186c2e768bf56889c46f51c/pywin32-310-cp311-cp311-win_arm64.whl", hash = "sha256:19ec5fc9b1d51c4350be7bb00760ffce46e6c95eaf2f0b2f1150657b1a43c582", size = 8455941, upload-time = "2025-03-17T00:55:57.048Z" }, + { url = "https://files.pythonhosted.org/packages/6b/ec/4fdbe47932f671d6e348474ea35ed94227fb5df56a7c30cbbb42cd396ed0/pywin32-310-cp312-cp312-win32.whl", hash = "sha256:8a75a5cc3893e83a108c05d82198880704c44bbaee4d06e442e471d3c9ea4f3d", size = 8796239, upload-time = "2025-03-17T00:55:58.807Z" }, + { url = "https://files.pythonhosted.org/packages/e3/e5/b0627f8bb84e06991bea89ad8153a9e50ace40b2e1195d68e9dff6b03d0f/pywin32-310-cp312-cp312-win_amd64.whl", hash = "sha256:bf5c397c9a9a19a6f62f3fb821fbf36cac08f03770056711f765ec1503972060", size = 9503839, upload-time = "2025-03-17T00:56:00.8Z" }, + { url = "https://files.pythonhosted.org/packages/1f/32/9ccf53748df72301a89713936645a664ec001abd35ecc8578beda593d37d/pywin32-310-cp312-cp312-win_arm64.whl", hash = "sha256:2349cc906eae872d0663d4d6290d13b90621eaf78964bb1578632ff20e152966", size = 8459470, upload-time = "2025-03-17T00:56:02.601Z" }, ] [[package]] name = "pyxlsb" version = "1.0.10" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/3f/13/eebaeb7a40b062d1c6f7f91d09e73d30a69e33e4baa7cbe4b7658548b1cd/pyxlsb-1.0.10.tar.gz", hash = "sha256:8062d1ea8626d3f1980e8b1cfe91a4483747449242ecb61013bc2df85435f685", size = 22424 } +sdist = { url = "https://files.pythonhosted.org/packages/3f/13/eebaeb7a40b062d1c6f7f91d09e73d30a69e33e4baa7cbe4b7658548b1cd/pyxlsb-1.0.10.tar.gz", hash = "sha256:8062d1ea8626d3f1980e8b1cfe91a4483747449242ecb61013bc2df85435f685", size = 22424, upload-time = "2022-10-14T19:17:47.308Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/7e/92/345823838ae367c59b63e03aef9c331f485370f9df6d049256a61a28f06d/pyxlsb-1.0.10-py2.py3-none-any.whl", hash = "sha256:87c122a9a622e35ca5e741d2e541201d28af00fb46bec492cfa9586890b120b4", size = 23849 }, + { url = "https://files.pythonhosted.org/packages/7e/92/345823838ae367c59b63e03aef9c331f485370f9df6d049256a61a28f06d/pyxlsb-1.0.10-py2.py3-none-any.whl", hash = "sha256:87c122a9a622e35ca5e741d2e541201d28af00fb46bec492cfa9586890b120b4", size = 23849, upload-time = "2022-10-14T19:17:46.079Z" }, ] [[package]] name = "pyyaml" version = "6.0.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/54/ed/79a089b6be93607fa5cdaedf301d7dfb23af5f25c398d5ead2525b063e17/pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e", size = 130631 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f8/aa/7af4e81f7acba21a4c6be026da38fd2b872ca46226673c89a758ebdc4fd2/PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774", size = 184612 }, - { url = "https://files.pythonhosted.org/packages/8b/62/b9faa998fd185f65c1371643678e4d58254add437edb764a08c5a98fb986/PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee", size = 172040 }, - { url = "https://files.pythonhosted.org/packages/ad/0c/c804f5f922a9a6563bab712d8dcc70251e8af811fce4524d57c2c0fd49a4/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c", size = 736829 }, - { url = "https://files.pythonhosted.org/packages/51/16/6af8d6a6b210c8e54f1406a6b9481febf9c64a3109c541567e35a49aa2e7/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317", size = 764167 }, - { url = "https://files.pythonhosted.org/packages/75/e4/2c27590dfc9992f73aabbeb9241ae20220bd9452df27483b6e56d3975cc5/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85", size = 762952 }, - { url = "https://files.pythonhosted.org/packages/9b/97/ecc1abf4a823f5ac61941a9c00fe501b02ac3ab0e373c3857f7d4b83e2b6/PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4", size = 735301 }, - { url = "https://files.pythonhosted.org/packages/45/73/0f49dacd6e82c9430e46f4a027baa4ca205e8b0a9dce1397f44edc23559d/PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e", size = 756638 }, - { url = "https://files.pythonhosted.org/packages/22/5f/956f0f9fc65223a58fbc14459bf34b4cc48dec52e00535c79b8db361aabd/PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5", size = 143850 }, - { url = "https://files.pythonhosted.org/packages/ed/23/8da0bbe2ab9dcdd11f4f4557ccaf95c10b9811b13ecced089d43ce59c3c8/PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44", size = 161980 }, - { url = "https://files.pythonhosted.org/packages/86/0c/c581167fc46d6d6d7ddcfb8c843a4de25bdd27e4466938109ca68492292c/PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab", size = 183873 }, - { url = "https://files.pythonhosted.org/packages/a8/0c/38374f5bb272c051e2a69281d71cba6fdb983413e6758b84482905e29a5d/PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725", size = 173302 }, - { url = "https://files.pythonhosted.org/packages/c3/93/9916574aa8c00aa06bbac729972eb1071d002b8e158bd0e83a3b9a20a1f7/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5", size = 739154 }, - { url = "https://files.pythonhosted.org/packages/95/0f/b8938f1cbd09739c6da569d172531567dbcc9789e0029aa070856f123984/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425", size = 766223 }, - { url = "https://files.pythonhosted.org/packages/b9/2b/614b4752f2e127db5cc206abc23a8c19678e92b23c3db30fc86ab731d3bd/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476", size = 767542 }, - { url = "https://files.pythonhosted.org/packages/d4/00/dd137d5bcc7efea1836d6264f049359861cf548469d18da90cd8216cf05f/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48", size = 731164 }, - { url = "https://files.pythonhosted.org/packages/c9/1f/4f998c900485e5c0ef43838363ba4a9723ac0ad73a9dc42068b12aaba4e4/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b", size = 756611 }, - { url = "https://files.pythonhosted.org/packages/df/d1/f5a275fdb252768b7a11ec63585bc38d0e87c9e05668a139fea92b80634c/PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4", size = 140591 }, - { url = "https://files.pythonhosted.org/packages/0c/e8/4f648c598b17c3d06e8753d7d13d57542b30d56e6c2dedf9c331ae56312e/PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8", size = 156338 }, +sdist = { url = "https://files.pythonhosted.org/packages/54/ed/79a089b6be93607fa5cdaedf301d7dfb23af5f25c398d5ead2525b063e17/pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e", size = 130631, upload-time = "2024-08-06T20:33:50.674Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f8/aa/7af4e81f7acba21a4c6be026da38fd2b872ca46226673c89a758ebdc4fd2/PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774", size = 184612, upload-time = "2024-08-06T20:32:03.408Z" }, + { url = "https://files.pythonhosted.org/packages/8b/62/b9faa998fd185f65c1371643678e4d58254add437edb764a08c5a98fb986/PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee", size = 172040, upload-time = "2024-08-06T20:32:04.926Z" }, + { url = "https://files.pythonhosted.org/packages/ad/0c/c804f5f922a9a6563bab712d8dcc70251e8af811fce4524d57c2c0fd49a4/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c", size = 736829, upload-time = "2024-08-06T20:32:06.459Z" }, + { url = "https://files.pythonhosted.org/packages/51/16/6af8d6a6b210c8e54f1406a6b9481febf9c64a3109c541567e35a49aa2e7/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317", size = 764167, upload-time = "2024-08-06T20:32:08.338Z" }, + { url = "https://files.pythonhosted.org/packages/75/e4/2c27590dfc9992f73aabbeb9241ae20220bd9452df27483b6e56d3975cc5/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85", size = 762952, upload-time = "2024-08-06T20:32:14.124Z" }, + { url = "https://files.pythonhosted.org/packages/9b/97/ecc1abf4a823f5ac61941a9c00fe501b02ac3ab0e373c3857f7d4b83e2b6/PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4", size = 735301, upload-time = "2024-08-06T20:32:16.17Z" }, + { url = "https://files.pythonhosted.org/packages/45/73/0f49dacd6e82c9430e46f4a027baa4ca205e8b0a9dce1397f44edc23559d/PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e", size = 756638, upload-time = "2024-08-06T20:32:18.555Z" }, + { url = "https://files.pythonhosted.org/packages/22/5f/956f0f9fc65223a58fbc14459bf34b4cc48dec52e00535c79b8db361aabd/PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5", size = 143850, upload-time = "2024-08-06T20:32:19.889Z" }, + { url = "https://files.pythonhosted.org/packages/ed/23/8da0bbe2ab9dcdd11f4f4557ccaf95c10b9811b13ecced089d43ce59c3c8/PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44", size = 161980, upload-time = "2024-08-06T20:32:21.273Z" }, + { url = "https://files.pythonhosted.org/packages/86/0c/c581167fc46d6d6d7ddcfb8c843a4de25bdd27e4466938109ca68492292c/PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab", size = 183873, upload-time = "2024-08-06T20:32:25.131Z" }, + { url = "https://files.pythonhosted.org/packages/a8/0c/38374f5bb272c051e2a69281d71cba6fdb983413e6758b84482905e29a5d/PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725", size = 173302, upload-time = "2024-08-06T20:32:26.511Z" }, + { url = "https://files.pythonhosted.org/packages/c3/93/9916574aa8c00aa06bbac729972eb1071d002b8e158bd0e83a3b9a20a1f7/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5", size = 739154, upload-time = "2024-08-06T20:32:28.363Z" }, + { url = "https://files.pythonhosted.org/packages/95/0f/b8938f1cbd09739c6da569d172531567dbcc9789e0029aa070856f123984/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425", size = 766223, upload-time = "2024-08-06T20:32:30.058Z" }, + { url = "https://files.pythonhosted.org/packages/b9/2b/614b4752f2e127db5cc206abc23a8c19678e92b23c3db30fc86ab731d3bd/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476", size = 767542, upload-time = "2024-08-06T20:32:31.881Z" }, + { url = "https://files.pythonhosted.org/packages/d4/00/dd137d5bcc7efea1836d6264f049359861cf548469d18da90cd8216cf05f/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48", size = 731164, upload-time = "2024-08-06T20:32:37.083Z" }, + { url = "https://files.pythonhosted.org/packages/c9/1f/4f998c900485e5c0ef43838363ba4a9723ac0ad73a9dc42068b12aaba4e4/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b", size = 756611, upload-time = "2024-08-06T20:32:38.898Z" }, + { url = "https://files.pythonhosted.org/packages/df/d1/f5a275fdb252768b7a11ec63585bc38d0e87c9e05668a139fea92b80634c/PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4", size = 140591, upload-time = "2024-08-06T20:32:40.241Z" }, + { url = "https://files.pythonhosted.org/packages/0c/e8/4f648c598b17c3d06e8753d7d13d57542b30d56e6c2dedf9c331ae56312e/PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8", size = 156338, upload-time = "2024-08-06T20:32:41.93Z" }, ] [[package]] name = "qdrant-client" -version = "1.7.3" +version = "1.9.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "grpcio" }, @@ -4577,58 +4757,58 @@ dependencies = [ { name = "pydantic" }, { name = "urllib3" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/71/f0/76b2583fe09d134ac659a308e6cf7a5f48443200d7c79c1963af042377a2/qdrant_client-1.7.3.tar.gz", hash = "sha256:7b809be892cdc5137ae80ea3335da40c06499ad0b0072b5abc6bad79da1d29fc", size = 180484 } +sdist = { url = "https://files.pythonhosted.org/packages/86/cf/db06a74694bf8f126ed4a869c70ef576f01ee691ef20799fba3d561d3565/qdrant_client-1.9.0.tar.gz", hash = "sha256:7b1792f616651a6f0a76312f945c13d088e9451726795b82ce0350f7df3b7981", size = 199999, upload-time = "2024-04-22T13:35:49.444Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/98/c3/8b2cebb07e0258b478f770dc2e9d246be17318c3858673ace0b345d147b0/qdrant_client-1.7.3-py3-none-any.whl", hash = "sha256:b062420ba55eb847652c7d2a26404fb1986bea13aa785763024013f96a7a915c", size = 206318 }, + { url = "https://files.pythonhosted.org/packages/3a/fa/5abd82cde353f1009c068cca820195efd94e403d261b787e78ea7a9c8318/qdrant_client-1.9.0-py3-none-any.whl", hash = "sha256:ee02893eab1f642481b1ac1e38eb68ec30bab0f673bef7cc05c19fa5d2cbf43e", size = 229258, upload-time = "2024-04-22T13:35:46.81Z" }, ] [[package]] name = "rapidfuzz" version = "3.13.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ed/f6/6895abc3a3d056b9698da3199b04c0e56226d530ae44a470edabf8b664f0/rapidfuzz-3.13.0.tar.gz", hash = "sha256:d2eaf3839e52cbcc0accbe9817a67b4b0fcf70aaeb229cfddc1c28061f9ce5d8", size = 57904226 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/87/17/9be9eff5a3c7dfc831c2511262082c6786dca2ce21aa8194eef1cb71d67a/rapidfuzz-3.13.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d395a5cad0c09c7f096433e5fd4224d83b53298d53499945a9b0e5a971a84f3a", size = 1999453 }, - { url = "https://files.pythonhosted.org/packages/75/67/62e57896ecbabe363f027d24cc769d55dd49019e576533ec10e492fcd8a2/rapidfuzz-3.13.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b7b3eda607a019169f7187328a8d1648fb9a90265087f6903d7ee3a8eee01805", size = 1450881 }, - { url = "https://files.pythonhosted.org/packages/96/5c/691c5304857f3476a7b3df99e91efc32428cbe7d25d234e967cc08346c13/rapidfuzz-3.13.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:98e0bfa602e1942d542de077baf15d658bd9d5dcfe9b762aff791724c1c38b70", size = 1422990 }, - { url = "https://files.pythonhosted.org/packages/46/81/7a7e78f977496ee2d613154b86b203d373376bcaae5de7bde92f3ad5a192/rapidfuzz-3.13.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bef86df6d59667d9655905b02770a0c776d2853971c0773767d5ef8077acd624", size = 5342309 }, - { url = "https://files.pythonhosted.org/packages/51/44/12fdd12a76b190fe94bf38d252bb28ddf0ab7a366b943e792803502901a2/rapidfuzz-3.13.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fedd316c165beed6307bf754dee54d3faca2c47e1f3bcbd67595001dfa11e969", size = 1656881 }, - { url = "https://files.pythonhosted.org/packages/27/ae/0d933e660c06fcfb087a0d2492f98322f9348a28b2cc3791a5dbadf6e6fb/rapidfuzz-3.13.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5158da7f2ec02a930be13bac53bb5903527c073c90ee37804090614cab83c29e", size = 1608494 }, - { url = "https://files.pythonhosted.org/packages/3d/2c/4b2f8aafdf9400e5599b6ed2f14bc26ca75f5a923571926ccbc998d4246a/rapidfuzz-3.13.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b6f913ee4618ddb6d6f3e387b76e8ec2fc5efee313a128809fbd44e65c2bbb2", size = 3072160 }, - { url = "https://files.pythonhosted.org/packages/60/7d/030d68d9a653c301114101c3003b31ce01cf2c3224034cd26105224cd249/rapidfuzz-3.13.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d25fdbce6459ccbbbf23b4b044f56fbd1158b97ac50994eaae2a1c0baae78301", size = 2491549 }, - { url = "https://files.pythonhosted.org/packages/8e/cd/7040ba538fc6a8ddc8816a05ecf46af9988b46c148ddd7f74fb0fb73d012/rapidfuzz-3.13.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:25343ccc589a4579fbde832e6a1e27258bfdd7f2eb0f28cb836d6694ab8591fc", size = 7584142 }, - { url = "https://files.pythonhosted.org/packages/c1/96/85f7536fbceb0aa92c04a1c37a3fc4fcd4e80649e9ed0fb585382df82edc/rapidfuzz-3.13.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:a9ad1f37894e3ffb76bbab76256e8a8b789657183870be11aa64e306bb5228fd", size = 2896234 }, - { url = "https://files.pythonhosted.org/packages/55/fd/460e78438e7019f2462fe9d4ecc880577ba340df7974c8a4cfe8d8d029df/rapidfuzz-3.13.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:5dc71ef23845bb6b62d194c39a97bb30ff171389c9812d83030c1199f319098c", size = 3437420 }, - { url = "https://files.pythonhosted.org/packages/cc/df/c3c308a106a0993befd140a414c5ea78789d201cf1dfffb8fd9749718d4f/rapidfuzz-3.13.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b7f4c65facdb94f44be759bbd9b6dda1fa54d0d6169cdf1a209a5ab97d311a75", size = 4410860 }, - { url = "https://files.pythonhosted.org/packages/75/ee/9d4ece247f9b26936cdeaae600e494af587ce9bf8ddc47d88435f05cfd05/rapidfuzz-3.13.0-cp311-cp311-win32.whl", hash = "sha256:b5104b62711565e0ff6deab2a8f5dbf1fbe333c5155abe26d2cfd6f1849b6c87", size = 1843161 }, - { url = "https://files.pythonhosted.org/packages/c9/5a/d00e1f63564050a20279015acb29ecaf41646adfacc6ce2e1e450f7f2633/rapidfuzz-3.13.0-cp311-cp311-win_amd64.whl", hash = "sha256:9093cdeb926deb32a4887ebe6910f57fbcdbc9fbfa52252c10b56ef2efb0289f", size = 1629962 }, - { url = "https://files.pythonhosted.org/packages/3b/74/0a3de18bc2576b794f41ccd07720b623e840fda219ab57091897f2320fdd/rapidfuzz-3.13.0-cp311-cp311-win_arm64.whl", hash = "sha256:f70f646751b6aa9d05be1fb40372f006cc89d6aad54e9d79ae97bd1f5fce5203", size = 866631 }, - { url = "https://files.pythonhosted.org/packages/13/4b/a326f57a4efed8f5505b25102797a58e37ee11d94afd9d9422cb7c76117e/rapidfuzz-3.13.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4a1a6a906ba62f2556372282b1ef37b26bca67e3d2ea957277cfcefc6275cca7", size = 1989501 }, - { url = "https://files.pythonhosted.org/packages/b7/53/1f7eb7ee83a06c400089ec7cb841cbd581c2edd7a4b21eb2f31030b88daa/rapidfuzz-3.13.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2fd0975e015b05c79a97f38883a11236f5a24cca83aa992bd2558ceaa5652b26", size = 1445379 }, - { url = "https://files.pythonhosted.org/packages/07/09/de8069a4599cc8e6d194e5fa1782c561151dea7d5e2741767137e2a8c1f0/rapidfuzz-3.13.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d4e13593d298c50c4f94ce453f757b4b398af3fa0fd2fde693c3e51195b7f69", size = 1405986 }, - { url = "https://files.pythonhosted.org/packages/5d/77/d9a90b39c16eca20d70fec4ca377fbe9ea4c0d358c6e4736ab0e0e78aaf6/rapidfuzz-3.13.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ed6f416bda1c9133000009d84d9409823eb2358df0950231cc936e4bf784eb97", size = 5310809 }, - { url = "https://files.pythonhosted.org/packages/1e/7d/14da291b0d0f22262d19522afaf63bccf39fc027c981233fb2137a57b71f/rapidfuzz-3.13.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1dc82b6ed01acb536b94a43996a94471a218f4d89f3fdd9185ab496de4b2a981", size = 1629394 }, - { url = "https://files.pythonhosted.org/packages/b7/e4/79ed7e4fa58f37c0f8b7c0a62361f7089b221fe85738ae2dbcfb815e985a/rapidfuzz-3.13.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e9d824de871daa6e443b39ff495a884931970d567eb0dfa213d234337343835f", size = 1600544 }, - { url = "https://files.pythonhosted.org/packages/4e/20/e62b4d13ba851b0f36370060025de50a264d625f6b4c32899085ed51f980/rapidfuzz-3.13.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2d18228a2390375cf45726ce1af9d36ff3dc1f11dce9775eae1f1b13ac6ec50f", size = 3052796 }, - { url = "https://files.pythonhosted.org/packages/cd/8d/55fdf4387dec10aa177fe3df8dbb0d5022224d95f48664a21d6b62a5299d/rapidfuzz-3.13.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:9f5fe634c9482ec5d4a6692afb8c45d370ae86755e5f57aa6c50bfe4ca2bdd87", size = 2464016 }, - { url = "https://files.pythonhosted.org/packages/9b/be/0872f6a56c0f473165d3b47d4170fa75263dc5f46985755aa9bf2bbcdea1/rapidfuzz-3.13.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:694eb531889f71022b2be86f625a4209c4049e74be9ca836919b9e395d5e33b3", size = 7556725 }, - { url = "https://files.pythonhosted.org/packages/5d/f3/6c0750e484d885a14840c7a150926f425d524982aca989cdda0bb3bdfa57/rapidfuzz-3.13.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:11b47b40650e06147dee5e51a9c9ad73bb7b86968b6f7d30e503b9f8dd1292db", size = 2859052 }, - { url = "https://files.pythonhosted.org/packages/6f/98/5a3a14701b5eb330f444f7883c9840b43fb29c575e292e09c90a270a6e07/rapidfuzz-3.13.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:98b8107ff14f5af0243f27d236bcc6e1ef8e7e3b3c25df114e91e3a99572da73", size = 3390219 }, - { url = "https://files.pythonhosted.org/packages/e9/7d/f4642eaaeb474b19974332f2a58471803448be843033e5740965775760a5/rapidfuzz-3.13.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b836f486dba0aceb2551e838ff3f514a38ee72b015364f739e526d720fdb823a", size = 4377924 }, - { url = "https://files.pythonhosted.org/packages/8e/83/fa33f61796731891c3e045d0cbca4436a5c436a170e7f04d42c2423652c3/rapidfuzz-3.13.0-cp312-cp312-win32.whl", hash = "sha256:4671ee300d1818d7bdfd8fa0608580d7778ba701817216f0c17fb29e6b972514", size = 1823915 }, - { url = "https://files.pythonhosted.org/packages/03/25/5ee7ab6841ca668567d0897905eebc79c76f6297b73bf05957be887e9c74/rapidfuzz-3.13.0-cp312-cp312-win_amd64.whl", hash = "sha256:6e2065f68fb1d0bf65adc289c1bdc45ba7e464e406b319d67bb54441a1b9da9e", size = 1616985 }, - { url = "https://files.pythonhosted.org/packages/76/5e/3f0fb88db396cb692aefd631e4805854e02120a2382723b90dcae720bcc6/rapidfuzz-3.13.0-cp312-cp312-win_arm64.whl", hash = "sha256:65cc97c2fc2c2fe23586599686f3b1ceeedeca8e598cfcc1b7e56dc8ca7e2aa7", size = 860116 }, - { url = "https://files.pythonhosted.org/packages/88/df/6060c5a9c879b302bd47a73fc012d0db37abf6544c57591bcbc3459673bd/rapidfuzz-3.13.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:1ba007f4d35a45ee68656b2eb83b8715e11d0f90e5b9f02d615a8a321ff00c27", size = 1905935 }, - { url = "https://files.pythonhosted.org/packages/a2/6c/a0b819b829e20525ef1bd58fc776fb8d07a0c38d819e63ba2b7c311a2ed4/rapidfuzz-3.13.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:d7a217310429b43be95b3b8ad7f8fc41aba341109dc91e978cd7c703f928c58f", size = 1383714 }, - { url = "https://files.pythonhosted.org/packages/6a/c1/3da3466cc8a9bfb9cd345ad221fac311143b6a9664b5af4adb95b5e6ce01/rapidfuzz-3.13.0-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:558bf526bcd777de32b7885790a95a9548ffdcce68f704a81207be4a286c1095", size = 1367329 }, - { url = "https://files.pythonhosted.org/packages/da/f0/9f2a9043bfc4e66da256b15d728c5fc2d865edf0028824337f5edac36783/rapidfuzz-3.13.0-pp311-pypy311_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:202a87760f5145140d56153b193a797ae9338f7939eb16652dd7ff96f8faf64c", size = 5251057 }, - { url = "https://files.pythonhosted.org/packages/6a/ff/af2cb1d8acf9777d52487af5c6b34ce9d13381a753f991d95ecaca813407/rapidfuzz-3.13.0-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cfcccc08f671646ccb1e413c773bb92e7bba789e3a1796fd49d23c12539fe2e4", size = 2992401 }, - { url = "https://files.pythonhosted.org/packages/c1/c5/c243b05a15a27b946180db0d1e4c999bef3f4221505dff9748f1f6c917be/rapidfuzz-3.13.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:1f219f1e3c3194d7a7de222f54450ce12bc907862ff9a8962d83061c1f923c86", size = 1553782 }, +sdist = { url = "https://files.pythonhosted.org/packages/ed/f6/6895abc3a3d056b9698da3199b04c0e56226d530ae44a470edabf8b664f0/rapidfuzz-3.13.0.tar.gz", hash = "sha256:d2eaf3839e52cbcc0accbe9817a67b4b0fcf70aaeb229cfddc1c28061f9ce5d8", size = 57904226, upload-time = "2025-04-03T20:38:51.226Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/87/17/9be9eff5a3c7dfc831c2511262082c6786dca2ce21aa8194eef1cb71d67a/rapidfuzz-3.13.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d395a5cad0c09c7f096433e5fd4224d83b53298d53499945a9b0e5a971a84f3a", size = 1999453, upload-time = "2025-04-03T20:35:40.804Z" }, + { url = "https://files.pythonhosted.org/packages/75/67/62e57896ecbabe363f027d24cc769d55dd49019e576533ec10e492fcd8a2/rapidfuzz-3.13.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b7b3eda607a019169f7187328a8d1648fb9a90265087f6903d7ee3a8eee01805", size = 1450881, upload-time = "2025-04-03T20:35:42.734Z" }, + { url = "https://files.pythonhosted.org/packages/96/5c/691c5304857f3476a7b3df99e91efc32428cbe7d25d234e967cc08346c13/rapidfuzz-3.13.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:98e0bfa602e1942d542de077baf15d658bd9d5dcfe9b762aff791724c1c38b70", size = 1422990, upload-time = "2025-04-03T20:35:45.158Z" }, + { url = "https://files.pythonhosted.org/packages/46/81/7a7e78f977496ee2d613154b86b203d373376bcaae5de7bde92f3ad5a192/rapidfuzz-3.13.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bef86df6d59667d9655905b02770a0c776d2853971c0773767d5ef8077acd624", size = 5342309, upload-time = "2025-04-03T20:35:46.952Z" }, + { url = "https://files.pythonhosted.org/packages/51/44/12fdd12a76b190fe94bf38d252bb28ddf0ab7a366b943e792803502901a2/rapidfuzz-3.13.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fedd316c165beed6307bf754dee54d3faca2c47e1f3bcbd67595001dfa11e969", size = 1656881, upload-time = "2025-04-03T20:35:49.954Z" }, + { url = "https://files.pythonhosted.org/packages/27/ae/0d933e660c06fcfb087a0d2492f98322f9348a28b2cc3791a5dbadf6e6fb/rapidfuzz-3.13.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5158da7f2ec02a930be13bac53bb5903527c073c90ee37804090614cab83c29e", size = 1608494, upload-time = "2025-04-03T20:35:51.646Z" }, + { url = "https://files.pythonhosted.org/packages/3d/2c/4b2f8aafdf9400e5599b6ed2f14bc26ca75f5a923571926ccbc998d4246a/rapidfuzz-3.13.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b6f913ee4618ddb6d6f3e387b76e8ec2fc5efee313a128809fbd44e65c2bbb2", size = 3072160, upload-time = "2025-04-03T20:35:53.472Z" }, + { url = "https://files.pythonhosted.org/packages/60/7d/030d68d9a653c301114101c3003b31ce01cf2c3224034cd26105224cd249/rapidfuzz-3.13.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d25fdbce6459ccbbbf23b4b044f56fbd1158b97ac50994eaae2a1c0baae78301", size = 2491549, upload-time = "2025-04-03T20:35:55.391Z" }, + { url = "https://files.pythonhosted.org/packages/8e/cd/7040ba538fc6a8ddc8816a05ecf46af9988b46c148ddd7f74fb0fb73d012/rapidfuzz-3.13.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:25343ccc589a4579fbde832e6a1e27258bfdd7f2eb0f28cb836d6694ab8591fc", size = 7584142, upload-time = "2025-04-03T20:35:57.71Z" }, + { url = "https://files.pythonhosted.org/packages/c1/96/85f7536fbceb0aa92c04a1c37a3fc4fcd4e80649e9ed0fb585382df82edc/rapidfuzz-3.13.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:a9ad1f37894e3ffb76bbab76256e8a8b789657183870be11aa64e306bb5228fd", size = 2896234, upload-time = "2025-04-03T20:35:59.969Z" }, + { url = "https://files.pythonhosted.org/packages/55/fd/460e78438e7019f2462fe9d4ecc880577ba340df7974c8a4cfe8d8d029df/rapidfuzz-3.13.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:5dc71ef23845bb6b62d194c39a97bb30ff171389c9812d83030c1199f319098c", size = 3437420, upload-time = "2025-04-03T20:36:01.91Z" }, + { url = "https://files.pythonhosted.org/packages/cc/df/c3c308a106a0993befd140a414c5ea78789d201cf1dfffb8fd9749718d4f/rapidfuzz-3.13.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b7f4c65facdb94f44be759bbd9b6dda1fa54d0d6169cdf1a209a5ab97d311a75", size = 4410860, upload-time = "2025-04-03T20:36:04.352Z" }, + { url = "https://files.pythonhosted.org/packages/75/ee/9d4ece247f9b26936cdeaae600e494af587ce9bf8ddc47d88435f05cfd05/rapidfuzz-3.13.0-cp311-cp311-win32.whl", hash = "sha256:b5104b62711565e0ff6deab2a8f5dbf1fbe333c5155abe26d2cfd6f1849b6c87", size = 1843161, upload-time = "2025-04-03T20:36:06.802Z" }, + { url = "https://files.pythonhosted.org/packages/c9/5a/d00e1f63564050a20279015acb29ecaf41646adfacc6ce2e1e450f7f2633/rapidfuzz-3.13.0-cp311-cp311-win_amd64.whl", hash = "sha256:9093cdeb926deb32a4887ebe6910f57fbcdbc9fbfa52252c10b56ef2efb0289f", size = 1629962, upload-time = "2025-04-03T20:36:09.133Z" }, + { url = "https://files.pythonhosted.org/packages/3b/74/0a3de18bc2576b794f41ccd07720b623e840fda219ab57091897f2320fdd/rapidfuzz-3.13.0-cp311-cp311-win_arm64.whl", hash = "sha256:f70f646751b6aa9d05be1fb40372f006cc89d6aad54e9d79ae97bd1f5fce5203", size = 866631, upload-time = "2025-04-03T20:36:11.022Z" }, + { url = "https://files.pythonhosted.org/packages/13/4b/a326f57a4efed8f5505b25102797a58e37ee11d94afd9d9422cb7c76117e/rapidfuzz-3.13.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4a1a6a906ba62f2556372282b1ef37b26bca67e3d2ea957277cfcefc6275cca7", size = 1989501, upload-time = "2025-04-03T20:36:13.43Z" }, + { url = "https://files.pythonhosted.org/packages/b7/53/1f7eb7ee83a06c400089ec7cb841cbd581c2edd7a4b21eb2f31030b88daa/rapidfuzz-3.13.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2fd0975e015b05c79a97f38883a11236f5a24cca83aa992bd2558ceaa5652b26", size = 1445379, upload-time = "2025-04-03T20:36:16.439Z" }, + { url = "https://files.pythonhosted.org/packages/07/09/de8069a4599cc8e6d194e5fa1782c561151dea7d5e2741767137e2a8c1f0/rapidfuzz-3.13.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d4e13593d298c50c4f94ce453f757b4b398af3fa0fd2fde693c3e51195b7f69", size = 1405986, upload-time = "2025-04-03T20:36:18.447Z" }, + { url = "https://files.pythonhosted.org/packages/5d/77/d9a90b39c16eca20d70fec4ca377fbe9ea4c0d358c6e4736ab0e0e78aaf6/rapidfuzz-3.13.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ed6f416bda1c9133000009d84d9409823eb2358df0950231cc936e4bf784eb97", size = 5310809, upload-time = "2025-04-03T20:36:20.324Z" }, + { url = "https://files.pythonhosted.org/packages/1e/7d/14da291b0d0f22262d19522afaf63bccf39fc027c981233fb2137a57b71f/rapidfuzz-3.13.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1dc82b6ed01acb536b94a43996a94471a218f4d89f3fdd9185ab496de4b2a981", size = 1629394, upload-time = "2025-04-03T20:36:22.256Z" }, + { url = "https://files.pythonhosted.org/packages/b7/e4/79ed7e4fa58f37c0f8b7c0a62361f7089b221fe85738ae2dbcfb815e985a/rapidfuzz-3.13.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e9d824de871daa6e443b39ff495a884931970d567eb0dfa213d234337343835f", size = 1600544, upload-time = "2025-04-03T20:36:24.207Z" }, + { url = "https://files.pythonhosted.org/packages/4e/20/e62b4d13ba851b0f36370060025de50a264d625f6b4c32899085ed51f980/rapidfuzz-3.13.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2d18228a2390375cf45726ce1af9d36ff3dc1f11dce9775eae1f1b13ac6ec50f", size = 3052796, upload-time = "2025-04-03T20:36:26.279Z" }, + { url = "https://files.pythonhosted.org/packages/cd/8d/55fdf4387dec10aa177fe3df8dbb0d5022224d95f48664a21d6b62a5299d/rapidfuzz-3.13.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:9f5fe634c9482ec5d4a6692afb8c45d370ae86755e5f57aa6c50bfe4ca2bdd87", size = 2464016, upload-time = "2025-04-03T20:36:28.525Z" }, + { url = "https://files.pythonhosted.org/packages/9b/be/0872f6a56c0f473165d3b47d4170fa75263dc5f46985755aa9bf2bbcdea1/rapidfuzz-3.13.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:694eb531889f71022b2be86f625a4209c4049e74be9ca836919b9e395d5e33b3", size = 7556725, upload-time = "2025-04-03T20:36:30.629Z" }, + { url = "https://files.pythonhosted.org/packages/5d/f3/6c0750e484d885a14840c7a150926f425d524982aca989cdda0bb3bdfa57/rapidfuzz-3.13.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:11b47b40650e06147dee5e51a9c9ad73bb7b86968b6f7d30e503b9f8dd1292db", size = 2859052, upload-time = "2025-04-03T20:36:32.836Z" }, + { url = "https://files.pythonhosted.org/packages/6f/98/5a3a14701b5eb330f444f7883c9840b43fb29c575e292e09c90a270a6e07/rapidfuzz-3.13.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:98b8107ff14f5af0243f27d236bcc6e1ef8e7e3b3c25df114e91e3a99572da73", size = 3390219, upload-time = "2025-04-03T20:36:35.062Z" }, + { url = "https://files.pythonhosted.org/packages/e9/7d/f4642eaaeb474b19974332f2a58471803448be843033e5740965775760a5/rapidfuzz-3.13.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b836f486dba0aceb2551e838ff3f514a38ee72b015364f739e526d720fdb823a", size = 4377924, upload-time = "2025-04-03T20:36:37.363Z" }, + { url = "https://files.pythonhosted.org/packages/8e/83/fa33f61796731891c3e045d0cbca4436a5c436a170e7f04d42c2423652c3/rapidfuzz-3.13.0-cp312-cp312-win32.whl", hash = "sha256:4671ee300d1818d7bdfd8fa0608580d7778ba701817216f0c17fb29e6b972514", size = 1823915, upload-time = "2025-04-03T20:36:39.451Z" }, + { url = "https://files.pythonhosted.org/packages/03/25/5ee7ab6841ca668567d0897905eebc79c76f6297b73bf05957be887e9c74/rapidfuzz-3.13.0-cp312-cp312-win_amd64.whl", hash = "sha256:6e2065f68fb1d0bf65adc289c1bdc45ba7e464e406b319d67bb54441a1b9da9e", size = 1616985, upload-time = "2025-04-03T20:36:41.631Z" }, + { url = "https://files.pythonhosted.org/packages/76/5e/3f0fb88db396cb692aefd631e4805854e02120a2382723b90dcae720bcc6/rapidfuzz-3.13.0-cp312-cp312-win_arm64.whl", hash = "sha256:65cc97c2fc2c2fe23586599686f3b1ceeedeca8e598cfcc1b7e56dc8ca7e2aa7", size = 860116, upload-time = "2025-04-03T20:36:43.915Z" }, + { url = "https://files.pythonhosted.org/packages/88/df/6060c5a9c879b302bd47a73fc012d0db37abf6544c57591bcbc3459673bd/rapidfuzz-3.13.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:1ba007f4d35a45ee68656b2eb83b8715e11d0f90e5b9f02d615a8a321ff00c27", size = 1905935, upload-time = "2025-04-03T20:38:18.07Z" }, + { url = "https://files.pythonhosted.org/packages/a2/6c/a0b819b829e20525ef1bd58fc776fb8d07a0c38d819e63ba2b7c311a2ed4/rapidfuzz-3.13.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:d7a217310429b43be95b3b8ad7f8fc41aba341109dc91e978cd7c703f928c58f", size = 1383714, upload-time = "2025-04-03T20:38:20.628Z" }, + { url = "https://files.pythonhosted.org/packages/6a/c1/3da3466cc8a9bfb9cd345ad221fac311143b6a9664b5af4adb95b5e6ce01/rapidfuzz-3.13.0-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:558bf526bcd777de32b7885790a95a9548ffdcce68f704a81207be4a286c1095", size = 1367329, upload-time = "2025-04-03T20:38:23.01Z" }, + { url = "https://files.pythonhosted.org/packages/da/f0/9f2a9043bfc4e66da256b15d728c5fc2d865edf0028824337f5edac36783/rapidfuzz-3.13.0-pp311-pypy311_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:202a87760f5145140d56153b193a797ae9338f7939eb16652dd7ff96f8faf64c", size = 5251057, upload-time = "2025-04-03T20:38:25.52Z" }, + { url = "https://files.pythonhosted.org/packages/6a/ff/af2cb1d8acf9777d52487af5c6b34ce9d13381a753f991d95ecaca813407/rapidfuzz-3.13.0-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cfcccc08f671646ccb1e413c773bb92e7bba789e3a1796fd49d23c12539fe2e4", size = 2992401, upload-time = "2025-04-03T20:38:28.196Z" }, + { url = "https://files.pythonhosted.org/packages/c1/c5/c243b05a15a27b946180db0d1e4c999bef3f4221505dff9748f1f6c917be/rapidfuzz-3.13.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:1f219f1e3c3194d7a7de222f54450ce12bc907862ff9a8962d83061c1f923c86", size = 1553782, upload-time = "2025-04-03T20:38:30.778Z" }, ] [[package]] name = "readabilipy" -version = "0.2.0" +version = "0.3.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "beautifulsoup4" }, @@ -4636,14 +4816,14 @@ dependencies = [ { name = "lxml" }, { name = "regex" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/e0/ca/0c9e5afed873dd29f529f24bb3174d582f77e343acfa8c77a39745fa7073/readabilipy-0.2.0.tar.gz", hash = "sha256:098bf347b19f362042fb6c08864ad776588bf844ac2261fb230f7f9c250fdae5", size = 38948 } +sdist = { url = "https://files.pythonhosted.org/packages/b8/e4/260a202516886c2e0cc6e6ae96d1f491792d829098886d9529a2439fbe8e/readabilipy-0.3.0.tar.gz", hash = "sha256:e13313771216953935ac031db4234bdb9725413534bfb3c19dbd6caab0887ae0", size = 35491, upload-time = "2024-12-02T23:03:02.311Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/39/42/11f5795b747841912a6f7bacab32ab1eaabc911a4e9949fbf8786121f4d3/readabilipy-0.2.0-py3-none-any.whl", hash = "sha256:0050853cd6ab012ac75bb4d8f06427feb7dc32054da65060da44654d049802d0", size = 4339504 }, + { url = "https://files.pythonhosted.org/packages/dd/46/8a640c6de1a6c6af971f858b2fb178ca5e1db91f223d8ba5f40efe1491e5/readabilipy-0.3.0-py3-none-any.whl", hash = "sha256:d106da0fad11d5fdfcde21f5c5385556bfa8ff0258483037d39ea6b1d6db3943", size = 22158, upload-time = "2024-12-02T23:03:00.438Z" }, ] [[package]] name = "realtime" -version = "2.4.2" +version = "2.5.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "aiohttp" }, @@ -4651,21 +4831,21 @@ dependencies = [ { name = "typing-extensions" }, { name = "websockets" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/1e/1e/c5f47928789cd5abb96e527929dea088213968f785983a231b3dfe08cc4f/realtime-2.4.2.tar.gz", hash = "sha256:760308d5310533f65a9098e0b482a518f6ad2f3c0f2723e83cf5856865bafc5d", size = 18802 } +sdist = { url = "https://files.pythonhosted.org/packages/47/9e/d6e478ccc23869a450a5d0dd9ab0c8a4e37fee7aec43c925d89bb09fcaf5/realtime-2.5.0.tar.gz", hash = "sha256:03d744dedc823de019a7f9917c1a6509fb6e98d357adf7fd7f4015618dac7ecd", size = 18865, upload-time = "2025-05-15T12:40:14.868Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/1d/b7/1b7651f353e14543c60cdfe40e3ea4dea412cfb2e93ab6384e72be813f05/realtime-2.4.2-py3-none-any.whl", hash = "sha256:0cc1b4a097acf9c0bd3a2f1998170de47744574c606617285113ddb3021e54ca", size = 22025 }, + { url = "https://files.pythonhosted.org/packages/52/09/a9ede9afa4a536ac84ad365bfa26116ab17463c8353f471b2396dc0e44d0/realtime-2.5.0-py3-none-any.whl", hash = "sha256:a54274a6cdc03c3eda61fbfec1d277e4a28e3aa9526d24b5c187385bb8a7e85a", size = 22086, upload-time = "2025-05-15T12:40:13.092Z" }, ] [[package]] name = "redis" -version = "5.0.8" +version = "6.1.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "async-timeout", marker = "python_full_version < '3.11.3'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/48/10/defc227d65ea9c2ff5244645870859865cba34da7373477c8376629746ec/redis-5.0.8.tar.gz", hash = "sha256:0c5b10d387568dfe0698c6fad6615750c24170e548ca2deac10c649d463e9870", size = 4595651 } +sdist = { url = "https://files.pythonhosted.org/packages/07/8b/14ef373ffe71c0d2fde93c204eab78472ea13c021d9aee63b0e11bd65896/redis-6.1.1.tar.gz", hash = "sha256:88c689325b5b41cedcbdbdfd4d937ea86cf6dab2222a83e86d8a466e4b3d2600", size = 4629515, upload-time = "2025-06-02T11:44:04.137Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c5/d1/19a9c76811757684a0f74adc25765c8a901d67f9f6472ac9d57c844a23c8/redis-5.0.8-py3-none-any.whl", hash = "sha256:56134ee08ea909106090934adc36f65c9bcbbaecea5b21ba704ba6fb561f8eb4", size = 255608 }, + { url = "https://files.pythonhosted.org/packages/c2/cd/29503c609186104c363ef1f38d6e752e7d91ef387fc90aa165e96d69f446/redis-6.1.1-py3-none-any.whl", hash = "sha256:ed44d53d065bbe04ac6d76864e331cfe5c5353f86f6deccc095f8794fd15bb2e", size = 273930, upload-time = "2025-06-02T11:44:02.705Z" }, ] [package.optional-dependencies] @@ -4682,52 +4862,52 @@ dependencies = [ { name = "rpds-py" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/2f/db/98b5c277be99dd18bfd91dd04e1b759cad18d1a338188c936e92f921c7e2/referencing-0.36.2.tar.gz", hash = "sha256:df2e89862cd09deabbdba16944cc3f10feb6b3e6f18e902f7cc25609a34775aa", size = 74744 } +sdist = { url = "https://files.pythonhosted.org/packages/2f/db/98b5c277be99dd18bfd91dd04e1b759cad18d1a338188c936e92f921c7e2/referencing-0.36.2.tar.gz", hash = "sha256:df2e89862cd09deabbdba16944cc3f10feb6b3e6f18e902f7cc25609a34775aa", size = 74744, upload-time = "2025-01-25T08:48:16.138Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c1/b1/3baf80dc6d2b7bc27a95a67752d0208e410351e3feb4eb78de5f77454d8d/referencing-0.36.2-py3-none-any.whl", hash = "sha256:e8699adbbf8b5c7de96d8ffa0eb5c158b3beafce084968e2ea8bb08c6794dcd0", size = 26775 }, + { url = "https://files.pythonhosted.org/packages/c1/b1/3baf80dc6d2b7bc27a95a67752d0208e410351e3feb4eb78de5f77454d8d/referencing-0.36.2-py3-none-any.whl", hash = "sha256:e8699adbbf8b5c7de96d8ffa0eb5c158b3beafce084968e2ea8bb08c6794dcd0", size = 26775, upload-time = "2025-01-25T08:48:14.241Z" }, ] [[package]] name = "regex" version = "2024.11.6" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/8e/5f/bd69653fbfb76cf8604468d3b4ec4c403197144c7bfe0e6a5fc9e02a07cb/regex-2024.11.6.tar.gz", hash = "sha256:7ab159b063c52a0333c884e4679f8d7a85112ee3078fe3d9004b2dd875585519", size = 399494 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/58/58/7e4d9493a66c88a7da6d205768119f51af0f684fe7be7bac8328e217a52c/regex-2024.11.6-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:5478c6962ad548b54a591778e93cd7c456a7a29f8eca9c49e4f9a806dcc5d638", size = 482669 }, - { url = "https://files.pythonhosted.org/packages/34/4c/8f8e631fcdc2ff978609eaeef1d6994bf2f028b59d9ac67640ed051f1218/regex-2024.11.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2c89a8cc122b25ce6945f0423dc1352cb9593c68abd19223eebbd4e56612c5b7", size = 287684 }, - { url = "https://files.pythonhosted.org/packages/c5/1b/f0e4d13e6adf866ce9b069e191f303a30ab1277e037037a365c3aad5cc9c/regex-2024.11.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:94d87b689cdd831934fa3ce16cc15cd65748e6d689f5d2b8f4f4df2065c9fa20", size = 284589 }, - { url = "https://files.pythonhosted.org/packages/25/4d/ab21047f446693887f25510887e6820b93f791992994f6498b0318904d4a/regex-2024.11.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1062b39a0a2b75a9c694f7a08e7183a80c63c0d62b301418ffd9c35f55aaa114", size = 792121 }, - { url = "https://files.pythonhosted.org/packages/45/ee/c867e15cd894985cb32b731d89576c41a4642a57850c162490ea34b78c3b/regex-2024.11.6-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:167ed4852351d8a750da48712c3930b031f6efdaa0f22fa1933716bfcd6bf4a3", size = 831275 }, - { url = "https://files.pythonhosted.org/packages/b3/12/b0f480726cf1c60f6536fa5e1c95275a77624f3ac8fdccf79e6727499e28/regex-2024.11.6-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2d548dafee61f06ebdb584080621f3e0c23fff312f0de1afc776e2a2ba99a74f", size = 818257 }, - { url = "https://files.pythonhosted.org/packages/bf/ce/0d0e61429f603bac433910d99ef1a02ce45a8967ffbe3cbee48599e62d88/regex-2024.11.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2a19f302cd1ce5dd01a9099aaa19cae6173306d1302a43b627f62e21cf18ac0", size = 792727 }, - { url = "https://files.pythonhosted.org/packages/e4/c1/243c83c53d4a419c1556f43777ccb552bccdf79d08fda3980e4e77dd9137/regex-2024.11.6-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bec9931dfb61ddd8ef2ebc05646293812cb6b16b60cf7c9511a832b6f1854b55", size = 780667 }, - { url = "https://files.pythonhosted.org/packages/c5/f4/75eb0dd4ce4b37f04928987f1d22547ddaf6c4bae697623c1b05da67a8aa/regex-2024.11.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:9714398225f299aa85267fd222f7142fcb5c769e73d7733344efc46f2ef5cf89", size = 776963 }, - { url = "https://files.pythonhosted.org/packages/16/5d/95c568574e630e141a69ff8a254c2f188b4398e813c40d49228c9bbd9875/regex-2024.11.6-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:202eb32e89f60fc147a41e55cb086db2a3f8cb82f9a9a88440dcfc5d37faae8d", size = 784700 }, - { url = "https://files.pythonhosted.org/packages/8e/b5/f8495c7917f15cc6fee1e7f395e324ec3e00ab3c665a7dc9d27562fd5290/regex-2024.11.6-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:4181b814e56078e9b00427ca358ec44333765f5ca1b45597ec7446d3a1ef6e34", size = 848592 }, - { url = "https://files.pythonhosted.org/packages/1c/80/6dd7118e8cb212c3c60b191b932dc57db93fb2e36fb9e0e92f72a5909af9/regex-2024.11.6-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:068376da5a7e4da51968ce4c122a7cd31afaaec4fccc7856c92f63876e57b51d", size = 852929 }, - { url = "https://files.pythonhosted.org/packages/11/9b/5a05d2040297d2d254baf95eeeb6df83554e5e1df03bc1a6687fc4ba1f66/regex-2024.11.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ac10f2c4184420d881a3475fb2c6f4d95d53a8d50209a2500723d831036f7c45", size = 781213 }, - { url = "https://files.pythonhosted.org/packages/26/b7/b14e2440156ab39e0177506c08c18accaf2b8932e39fb092074de733d868/regex-2024.11.6-cp311-cp311-win32.whl", hash = "sha256:c36f9b6f5f8649bb251a5f3f66564438977b7ef8386a52460ae77e6070d309d9", size = 261734 }, - { url = "https://files.pythonhosted.org/packages/80/32/763a6cc01d21fb3819227a1cc3f60fd251c13c37c27a73b8ff4315433a8e/regex-2024.11.6-cp311-cp311-win_amd64.whl", hash = "sha256:02e28184be537f0e75c1f9b2f8847dc51e08e6e171c6bde130b2687e0c33cf60", size = 274052 }, - { url = "https://files.pythonhosted.org/packages/ba/30/9a87ce8336b172cc232a0db89a3af97929d06c11ceaa19d97d84fa90a8f8/regex-2024.11.6-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:52fb28f528778f184f870b7cf8f225f5eef0a8f6e3778529bdd40c7b3920796a", size = 483781 }, - { url = "https://files.pythonhosted.org/packages/01/e8/00008ad4ff4be8b1844786ba6636035f7ef926db5686e4c0f98093612add/regex-2024.11.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:fdd6028445d2460f33136c55eeb1f601ab06d74cb3347132e1c24250187500d9", size = 288455 }, - { url = "https://files.pythonhosted.org/packages/60/85/cebcc0aff603ea0a201667b203f13ba75d9fc8668fab917ac5b2de3967bc/regex-2024.11.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:805e6b60c54bf766b251e94526ebad60b7de0c70f70a4e6210ee2891acb70bf2", size = 284759 }, - { url = "https://files.pythonhosted.org/packages/94/2b/701a4b0585cb05472a4da28ee28fdfe155f3638f5e1ec92306d924e5faf0/regex-2024.11.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b85c2530be953a890eaffde05485238f07029600e8f098cdf1848d414a8b45e4", size = 794976 }, - { url = "https://files.pythonhosted.org/packages/4b/bf/fa87e563bf5fee75db8915f7352e1887b1249126a1be4813837f5dbec965/regex-2024.11.6-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bb26437975da7dc36b7efad18aa9dd4ea569d2357ae6b783bf1118dabd9ea577", size = 833077 }, - { url = "https://files.pythonhosted.org/packages/a1/56/7295e6bad94b047f4d0834e4779491b81216583c00c288252ef625c01d23/regex-2024.11.6-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:abfa5080c374a76a251ba60683242bc17eeb2c9818d0d30117b4486be10c59d3", size = 823160 }, - { url = "https://files.pythonhosted.org/packages/fb/13/e3b075031a738c9598c51cfbc4c7879e26729c53aa9cca59211c44235314/regex-2024.11.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b7fa6606c2881c1db9479b0eaa11ed5dfa11c8d60a474ff0e095099f39d98e", size = 796896 }, - { url = "https://files.pythonhosted.org/packages/24/56/0b3f1b66d592be6efec23a795b37732682520b47c53da5a32c33ed7d84e3/regex-2024.11.6-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0c32f75920cf99fe6b6c539c399a4a128452eaf1af27f39bce8909c9a3fd8cbe", size = 783997 }, - { url = "https://files.pythonhosted.org/packages/f9/a1/eb378dada8b91c0e4c5f08ffb56f25fcae47bf52ad18f9b2f33b83e6d498/regex-2024.11.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:982e6d21414e78e1f51cf595d7f321dcd14de1f2881c5dc6a6e23bbbbd68435e", size = 781725 }, - { url = "https://files.pythonhosted.org/packages/83/f2/033e7dec0cfd6dda93390089864732a3409246ffe8b042e9554afa9bff4e/regex-2024.11.6-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a7c2155f790e2fb448faed6dd241386719802296ec588a8b9051c1f5c481bc29", size = 789481 }, - { url = "https://files.pythonhosted.org/packages/83/23/15d4552ea28990a74e7696780c438aadd73a20318c47e527b47a4a5a596d/regex-2024.11.6-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:149f5008d286636e48cd0b1dd65018548944e495b0265b45e1bffecce1ef7f39", size = 852896 }, - { url = "https://files.pythonhosted.org/packages/e3/39/ed4416bc90deedbfdada2568b2cb0bc1fdb98efe11f5378d9892b2a88f8f/regex-2024.11.6-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:e5364a4502efca094731680e80009632ad6624084aff9a23ce8c8c6820de3e51", size = 860138 }, - { url = "https://files.pythonhosted.org/packages/93/2d/dd56bb76bd8e95bbce684326302f287455b56242a4f9c61f1bc76e28360e/regex-2024.11.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0a86e7eeca091c09e021db8eb72d54751e527fa47b8d5787caf96d9831bd02ad", size = 787692 }, - { url = "https://files.pythonhosted.org/packages/0b/55/31877a249ab7a5156758246b9c59539abbeba22461b7d8adc9e8475ff73e/regex-2024.11.6-cp312-cp312-win32.whl", hash = "sha256:32f9a4c643baad4efa81d549c2aadefaeba12249b2adc5af541759237eee1c54", size = 262135 }, - { url = "https://files.pythonhosted.org/packages/38/ec/ad2d7de49a600cdb8dd78434a1aeffe28b9d6fc42eb36afab4a27ad23384/regex-2024.11.6-cp312-cp312-win_amd64.whl", hash = "sha256:a93c194e2df18f7d264092dc8539b8ffb86b45b899ab976aa15d48214138e81b", size = 273567 }, +sdist = { url = "https://files.pythonhosted.org/packages/8e/5f/bd69653fbfb76cf8604468d3b4ec4c403197144c7bfe0e6a5fc9e02a07cb/regex-2024.11.6.tar.gz", hash = "sha256:7ab159b063c52a0333c884e4679f8d7a85112ee3078fe3d9004b2dd875585519", size = 399494, upload-time = "2024-11-06T20:12:31.635Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/58/58/7e4d9493a66c88a7da6d205768119f51af0f684fe7be7bac8328e217a52c/regex-2024.11.6-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:5478c6962ad548b54a591778e93cd7c456a7a29f8eca9c49e4f9a806dcc5d638", size = 482669, upload-time = "2024-11-06T20:09:31.064Z" }, + { url = "https://files.pythonhosted.org/packages/34/4c/8f8e631fcdc2ff978609eaeef1d6994bf2f028b59d9ac67640ed051f1218/regex-2024.11.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2c89a8cc122b25ce6945f0423dc1352cb9593c68abd19223eebbd4e56612c5b7", size = 287684, upload-time = "2024-11-06T20:09:32.915Z" }, + { url = "https://files.pythonhosted.org/packages/c5/1b/f0e4d13e6adf866ce9b069e191f303a30ab1277e037037a365c3aad5cc9c/regex-2024.11.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:94d87b689cdd831934fa3ce16cc15cd65748e6d689f5d2b8f4f4df2065c9fa20", size = 284589, upload-time = "2024-11-06T20:09:35.504Z" }, + { url = "https://files.pythonhosted.org/packages/25/4d/ab21047f446693887f25510887e6820b93f791992994f6498b0318904d4a/regex-2024.11.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1062b39a0a2b75a9c694f7a08e7183a80c63c0d62b301418ffd9c35f55aaa114", size = 792121, upload-time = "2024-11-06T20:09:37.701Z" }, + { url = "https://files.pythonhosted.org/packages/45/ee/c867e15cd894985cb32b731d89576c41a4642a57850c162490ea34b78c3b/regex-2024.11.6-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:167ed4852351d8a750da48712c3930b031f6efdaa0f22fa1933716bfcd6bf4a3", size = 831275, upload-time = "2024-11-06T20:09:40.371Z" }, + { url = "https://files.pythonhosted.org/packages/b3/12/b0f480726cf1c60f6536fa5e1c95275a77624f3ac8fdccf79e6727499e28/regex-2024.11.6-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2d548dafee61f06ebdb584080621f3e0c23fff312f0de1afc776e2a2ba99a74f", size = 818257, upload-time = "2024-11-06T20:09:43.059Z" }, + { url = "https://files.pythonhosted.org/packages/bf/ce/0d0e61429f603bac433910d99ef1a02ce45a8967ffbe3cbee48599e62d88/regex-2024.11.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2a19f302cd1ce5dd01a9099aaa19cae6173306d1302a43b627f62e21cf18ac0", size = 792727, upload-time = "2024-11-06T20:09:48.19Z" }, + { url = "https://files.pythonhosted.org/packages/e4/c1/243c83c53d4a419c1556f43777ccb552bccdf79d08fda3980e4e77dd9137/regex-2024.11.6-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bec9931dfb61ddd8ef2ebc05646293812cb6b16b60cf7c9511a832b6f1854b55", size = 780667, upload-time = "2024-11-06T20:09:49.828Z" }, + { url = "https://files.pythonhosted.org/packages/c5/f4/75eb0dd4ce4b37f04928987f1d22547ddaf6c4bae697623c1b05da67a8aa/regex-2024.11.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:9714398225f299aa85267fd222f7142fcb5c769e73d7733344efc46f2ef5cf89", size = 776963, upload-time = "2024-11-06T20:09:51.819Z" }, + { url = "https://files.pythonhosted.org/packages/16/5d/95c568574e630e141a69ff8a254c2f188b4398e813c40d49228c9bbd9875/regex-2024.11.6-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:202eb32e89f60fc147a41e55cb086db2a3f8cb82f9a9a88440dcfc5d37faae8d", size = 784700, upload-time = "2024-11-06T20:09:53.982Z" }, + { url = "https://files.pythonhosted.org/packages/8e/b5/f8495c7917f15cc6fee1e7f395e324ec3e00ab3c665a7dc9d27562fd5290/regex-2024.11.6-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:4181b814e56078e9b00427ca358ec44333765f5ca1b45597ec7446d3a1ef6e34", size = 848592, upload-time = "2024-11-06T20:09:56.222Z" }, + { url = "https://files.pythonhosted.org/packages/1c/80/6dd7118e8cb212c3c60b191b932dc57db93fb2e36fb9e0e92f72a5909af9/regex-2024.11.6-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:068376da5a7e4da51968ce4c122a7cd31afaaec4fccc7856c92f63876e57b51d", size = 852929, upload-time = "2024-11-06T20:09:58.642Z" }, + { url = "https://files.pythonhosted.org/packages/11/9b/5a05d2040297d2d254baf95eeeb6df83554e5e1df03bc1a6687fc4ba1f66/regex-2024.11.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ac10f2c4184420d881a3475fb2c6f4d95d53a8d50209a2500723d831036f7c45", size = 781213, upload-time = "2024-11-06T20:10:00.867Z" }, + { url = "https://files.pythonhosted.org/packages/26/b7/b14e2440156ab39e0177506c08c18accaf2b8932e39fb092074de733d868/regex-2024.11.6-cp311-cp311-win32.whl", hash = "sha256:c36f9b6f5f8649bb251a5f3f66564438977b7ef8386a52460ae77e6070d309d9", size = 261734, upload-time = "2024-11-06T20:10:03.361Z" }, + { url = "https://files.pythonhosted.org/packages/80/32/763a6cc01d21fb3819227a1cc3f60fd251c13c37c27a73b8ff4315433a8e/regex-2024.11.6-cp311-cp311-win_amd64.whl", hash = "sha256:02e28184be537f0e75c1f9b2f8847dc51e08e6e171c6bde130b2687e0c33cf60", size = 274052, upload-time = "2024-11-06T20:10:05.179Z" }, + { url = "https://files.pythonhosted.org/packages/ba/30/9a87ce8336b172cc232a0db89a3af97929d06c11ceaa19d97d84fa90a8f8/regex-2024.11.6-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:52fb28f528778f184f870b7cf8f225f5eef0a8f6e3778529bdd40c7b3920796a", size = 483781, upload-time = "2024-11-06T20:10:07.07Z" }, + { url = "https://files.pythonhosted.org/packages/01/e8/00008ad4ff4be8b1844786ba6636035f7ef926db5686e4c0f98093612add/regex-2024.11.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:fdd6028445d2460f33136c55eeb1f601ab06d74cb3347132e1c24250187500d9", size = 288455, upload-time = "2024-11-06T20:10:09.117Z" }, + { url = "https://files.pythonhosted.org/packages/60/85/cebcc0aff603ea0a201667b203f13ba75d9fc8668fab917ac5b2de3967bc/regex-2024.11.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:805e6b60c54bf766b251e94526ebad60b7de0c70f70a4e6210ee2891acb70bf2", size = 284759, upload-time = "2024-11-06T20:10:11.155Z" }, + { url = "https://files.pythonhosted.org/packages/94/2b/701a4b0585cb05472a4da28ee28fdfe155f3638f5e1ec92306d924e5faf0/regex-2024.11.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b85c2530be953a890eaffde05485238f07029600e8f098cdf1848d414a8b45e4", size = 794976, upload-time = "2024-11-06T20:10:13.24Z" }, + { url = "https://files.pythonhosted.org/packages/4b/bf/fa87e563bf5fee75db8915f7352e1887b1249126a1be4813837f5dbec965/regex-2024.11.6-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bb26437975da7dc36b7efad18aa9dd4ea569d2357ae6b783bf1118dabd9ea577", size = 833077, upload-time = "2024-11-06T20:10:15.37Z" }, + { url = "https://files.pythonhosted.org/packages/a1/56/7295e6bad94b047f4d0834e4779491b81216583c00c288252ef625c01d23/regex-2024.11.6-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:abfa5080c374a76a251ba60683242bc17eeb2c9818d0d30117b4486be10c59d3", size = 823160, upload-time = "2024-11-06T20:10:19.027Z" }, + { url = "https://files.pythonhosted.org/packages/fb/13/e3b075031a738c9598c51cfbc4c7879e26729c53aa9cca59211c44235314/regex-2024.11.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b7fa6606c2881c1db9479b0eaa11ed5dfa11c8d60a474ff0e095099f39d98e", size = 796896, upload-time = "2024-11-06T20:10:21.85Z" }, + { url = "https://files.pythonhosted.org/packages/24/56/0b3f1b66d592be6efec23a795b37732682520b47c53da5a32c33ed7d84e3/regex-2024.11.6-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0c32f75920cf99fe6b6c539c399a4a128452eaf1af27f39bce8909c9a3fd8cbe", size = 783997, upload-time = "2024-11-06T20:10:24.329Z" }, + { url = "https://files.pythonhosted.org/packages/f9/a1/eb378dada8b91c0e4c5f08ffb56f25fcae47bf52ad18f9b2f33b83e6d498/regex-2024.11.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:982e6d21414e78e1f51cf595d7f321dcd14de1f2881c5dc6a6e23bbbbd68435e", size = 781725, upload-time = "2024-11-06T20:10:28.067Z" }, + { url = "https://files.pythonhosted.org/packages/83/f2/033e7dec0cfd6dda93390089864732a3409246ffe8b042e9554afa9bff4e/regex-2024.11.6-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a7c2155f790e2fb448faed6dd241386719802296ec588a8b9051c1f5c481bc29", size = 789481, upload-time = "2024-11-06T20:10:31.612Z" }, + { url = "https://files.pythonhosted.org/packages/83/23/15d4552ea28990a74e7696780c438aadd73a20318c47e527b47a4a5a596d/regex-2024.11.6-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:149f5008d286636e48cd0b1dd65018548944e495b0265b45e1bffecce1ef7f39", size = 852896, upload-time = "2024-11-06T20:10:34.054Z" }, + { url = "https://files.pythonhosted.org/packages/e3/39/ed4416bc90deedbfdada2568b2cb0bc1fdb98efe11f5378d9892b2a88f8f/regex-2024.11.6-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:e5364a4502efca094731680e80009632ad6624084aff9a23ce8c8c6820de3e51", size = 860138, upload-time = "2024-11-06T20:10:36.142Z" }, + { url = "https://files.pythonhosted.org/packages/93/2d/dd56bb76bd8e95bbce684326302f287455b56242a4f9c61f1bc76e28360e/regex-2024.11.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0a86e7eeca091c09e021db8eb72d54751e527fa47b8d5787caf96d9831bd02ad", size = 787692, upload-time = "2024-11-06T20:10:38.394Z" }, + { url = "https://files.pythonhosted.org/packages/0b/55/31877a249ab7a5156758246b9c59539abbeba22461b7d8adc9e8475ff73e/regex-2024.11.6-cp312-cp312-win32.whl", hash = "sha256:32f9a4c643baad4efa81d549c2aadefaeba12249b2adc5af541759237eee1c54", size = 262135, upload-time = "2024-11-06T20:10:40.367Z" }, + { url = "https://files.pythonhosted.org/packages/38/ec/ad2d7de49a600cdb8dd78434a1aeffe28b9d6fc42eb36afab4a27ad23384/regex-2024.11.6-cp312-cp312-win_amd64.whl", hash = "sha256:a93c194e2df18f7d264092dc8539b8ffb86b45b899ab976aa15d48214138e81b", size = 273567, upload-time = "2024-11-06T20:10:43.467Z" }, ] [[package]] name = "requests" -version = "2.31.0" +version = "2.32.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "certifi" }, @@ -4735,9 +4915,9 @@ dependencies = [ { name = "idna" }, { name = "urllib3" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/9d/be/10918a2eac4ae9f02f6cfe6414b7a155ccd8f7f9d4380d62fd5b955065c3/requests-2.31.0.tar.gz", hash = "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1", size = 110794 } +sdist = { url = "https://files.pythonhosted.org/packages/63/70/2bf7780ad2d390a8d301ad0b550f1581eadbd9a20f896afe06353c2a2913/requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760", size = 131218, upload-time = "2024-05-29T15:37:49.536Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/70/8e/0e2d847013cb52cd35b38c009bb167a1a26b2ce6cd6965bf26b47bc0bf44/requests-2.31.0-py3-none-any.whl", hash = "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f", size = 62574 }, + { url = "https://files.pythonhosted.org/packages/f9/9b/335f9764261e915ed497fcdeb11df5dfd6f7bf257d4a6a2a686d80da4d54/requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6", size = 64928, upload-time = "2024-05-29T15:37:47.027Z" }, ] [[package]] @@ -4748,9 +4928,9 @@ dependencies = [ { name = "oauthlib" }, { name = "requests" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/42/f2/05f29bc3913aea15eb670be136045bf5c5bbf4b99ecb839da9b422bb2c85/requests-oauthlib-2.0.0.tar.gz", hash = "sha256:b3dffaebd884d8cd778494369603a9e7b58d29111bf6b41bdc2dcd87203af4e9", size = 55650 } +sdist = { url = "https://files.pythonhosted.org/packages/42/f2/05f29bc3913aea15eb670be136045bf5c5bbf4b99ecb839da9b422bb2c85/requests-oauthlib-2.0.0.tar.gz", hash = "sha256:b3dffaebd884d8cd778494369603a9e7b58d29111bf6b41bdc2dcd87203af4e9", size = 55650, upload-time = "2024-03-22T20:32:29.939Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/3b/5d/63d4ae3b9daea098d5d6f5da83984853c1bbacd5dc826764b249fe119d24/requests_oauthlib-2.0.0-py2.py3-none-any.whl", hash = "sha256:7dd8a5c40426b779b0868c404bdef9768deccf22749cde15852df527e6269b36", size = 24179 }, + { url = "https://files.pythonhosted.org/packages/3b/5d/63d4ae3b9daea098d5d6f5da83984853c1bbacd5dc826764b249fe119d24/requests_oauthlib-2.0.0-py2.py3-none-any.whl", hash = "sha256:7dd8a5c40426b779b0868c404bdef9768deccf22749cde15852df527e6269b36", size = 24179, upload-time = "2024-03-22T20:32:28.055Z" }, ] [[package]] @@ -4760,21 +4940,22 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "requests" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/f3/61/d7545dafb7ac2230c70d38d31cbfe4cc64f7144dc41f6e4e4b78ecd9f5bb/requests-toolbelt-1.0.0.tar.gz", hash = "sha256:7681a0a3d047012b5bdc0ee37d7f8f07ebe76ab08caeccfc3921ce23c88d5bc6", size = 206888 } +sdist = { url = "https://files.pythonhosted.org/packages/f3/61/d7545dafb7ac2230c70d38d31cbfe4cc64f7144dc41f6e4e4b78ecd9f5bb/requests-toolbelt-1.0.0.tar.gz", hash = "sha256:7681a0a3d047012b5bdc0ee37d7f8f07ebe76ab08caeccfc3921ce23c88d5bc6", size = 206888, upload-time = "2023-05-01T04:11:33.229Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/3f/51/d4db610ef29373b879047326cbf6fa98b6c1969d6f6dc423279de2b1be2c/requests_toolbelt-1.0.0-py2.py3-none-any.whl", hash = "sha256:cccfdd665f0a24fcf4726e690f65639d272bb0637b9b92dfd91a5568ccf6bd06", size = 54481 }, + { url = "https://files.pythonhosted.org/packages/3f/51/d4db610ef29373b879047326cbf6fa98b6c1969d6f6dc423279de2b1be2c/requests_toolbelt-1.0.0-py2.py3-none-any.whl", hash = "sha256:cccfdd665f0a24fcf4726e690f65639d272bb0637b9b92dfd91a5568ccf6bd06", size = 54481, upload-time = "2023-05-01T04:11:28.427Z" }, ] [[package]] name = "resend" -version = "0.7.2" +version = "2.9.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "requests" }, + { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/69/db/781735424454ed359c1597a28ffb25218cf21a9beaa05fea21cec1f966af/resend-0.7.2.tar.gz", hash = "sha256:bb10522a5ef1235b6cc2d74902df39c4863ac12b89dc48b46dd5c6f980574622", size = 7092 } +sdist = { url = "https://files.pythonhosted.org/packages/1f/2a/535a794e5b64f6ef4abc1342ef1a43465af2111c5185e98b4cca2a6b6b7a/resend-2.9.0.tar.gz", hash = "sha256:e8d4c909a7fe7701119789f848a6befb0a4a668e2182d7bbfe764742f1952bd3", size = 13600, upload-time = "2025-05-06T00:35:20.363Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/60/58/3365bd6d38548fd77ec8ddca7adb66a4f75fb354ada0e355073bcaee5413/resend-0.7.2-py2.py3-none-any.whl", hash = "sha256:4f16711e11b007da7f8826283af6cdc34c99bd77c1dfad92afe9466a90d06c61", size = 8736 }, + { url = "https://files.pythonhosted.org/packages/96/81/ba1feb9959bafbcde6466b78d4628405d69cd14613f6eba12b928a77b86a/resend-2.9.0-py2.py3-none-any.whl", hash = "sha256:6607f75e3a9257a219c0640f935b8d1211338190d553eb043c25732affb92949", size = 20173, upload-time = "2025-05-06T00:35:18.963Z" }, ] [[package]] @@ -4785,9 +4966,9 @@ dependencies = [ { name = "decorator" }, { name = "py" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/9d/72/75d0b85443fbc8d9f38d08d2b1b67cc184ce35280e4a3813cda2f445f3a4/retry-0.9.2.tar.gz", hash = "sha256:f8bfa8b99b69c4506d6f5bd3b0aabf77f98cdb17f3c9fc3f5ca820033336fba4", size = 6448 } +sdist = { url = "https://files.pythonhosted.org/packages/9d/72/75d0b85443fbc8d9f38d08d2b1b67cc184ce35280e4a3813cda2f445f3a4/retry-0.9.2.tar.gz", hash = "sha256:f8bfa8b99b69c4506d6f5bd3b0aabf77f98cdb17f3c9fc3f5ca820033336fba4", size = 6448, upload-time = "2016-05-11T13:58:51.541Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/4b/0d/53aea75710af4528a25ed6837d71d117602b01946b307a3912cb3cfcbcba/retry-0.9.2-py2.py3-none-any.whl", hash = "sha256:ccddf89761fa2c726ab29391837d4327f819ea14d244c232a1d24c67a2f98606", size = 7986 }, + { url = "https://files.pythonhosted.org/packages/4b/0d/53aea75710af4528a25ed6837d71d117602b01946b307a3912cb3cfcbcba/retry-0.9.2-py2.py3-none-any.whl", hash = "sha256:ccddf89761fa2c726ab29391837d4327f819ea14d244c232a1d24c67a2f98606", size = 7986, upload-time = "2016-05-11T13:58:39.925Z" }, ] [[package]] @@ -4798,91 +4979,93 @@ dependencies = [ { name = "markdown-it-py" }, { name = "pygments" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a1/53/830aa4c3066a8ab0ae9a9955976fb770fe9c6102117c8ec4ab3ea62d89e8/rich-14.0.0.tar.gz", hash = "sha256:82f1bc23a6a21ebca4ae0c45af9bdbc492ed20231dcb63f297d6d1021a9d5725", size = 224078 } +sdist = { url = "https://files.pythonhosted.org/packages/a1/53/830aa4c3066a8ab0ae9a9955976fb770fe9c6102117c8ec4ab3ea62d89e8/rich-14.0.0.tar.gz", hash = "sha256:82f1bc23a6a21ebca4ae0c45af9bdbc492ed20231dcb63f297d6d1021a9d5725", size = 224078, upload-time = "2025-03-30T14:15:14.23Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/0d/9b/63f4c7ebc259242c89b3acafdb37b41d1185c07ff0011164674e9076b491/rich-14.0.0-py3-none-any.whl", hash = "sha256:1c9491e1951aac09caffd42f448ee3d04e58923ffe14993f6e83068dc395d7e0", size = 243229 }, + { url = "https://files.pythonhosted.org/packages/0d/9b/63f4c7ebc259242c89b3acafdb37b41d1185c07ff0011164674e9076b491/rich-14.0.0-py3-none-any.whl", hash = "sha256:1c9491e1951aac09caffd42f448ee3d04e58923ffe14993f6e83068dc395d7e0", size = 243229, upload-time = "2025-03-30T14:15:12.283Z" }, ] [[package]] name = "rpds-py" -version = "0.24.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/0b/b3/52b213298a0ba7097c7ea96bee95e1947aa84cc816d48cebb539770cdf41/rpds_py-0.24.0.tar.gz", hash = "sha256:772cc1b2cd963e7e17e6cc55fe0371fb9c704d63e44cacec7b9b7f523b78919e", size = 26863 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/80/e6/c1458bbfb257448fdb2528071f1f4e19e26798ed5ef6d47d7aab0cb69661/rpds_py-0.24.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:2d3ee4615df36ab8eb16c2507b11e764dcc11fd350bbf4da16d09cda11fcedef", size = 377679 }, - { url = "https://files.pythonhosted.org/packages/dd/26/ea4181ef78f58b2c167548c6a833d7dc22408e5b3b181bda9dda440bb92d/rpds_py-0.24.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e13ae74a8a3a0c2f22f450f773e35f893484fcfacb00bb4344a7e0f4f48e1f97", size = 362571 }, - { url = "https://files.pythonhosted.org/packages/56/fa/1ec54dd492c64c280a2249a047fc3369e2789dc474eac20445ebfc72934b/rpds_py-0.24.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cf86f72d705fc2ef776bb7dd9e5fbba79d7e1f3e258bf9377f8204ad0fc1c51e", size = 388012 }, - { url = "https://files.pythonhosted.org/packages/3a/be/bad8b0e0f7e58ef4973bb75e91c472a7d51da1977ed43b09989264bf065c/rpds_py-0.24.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c43583ea8517ed2e780a345dd9960896afc1327e8cf3ac8239c167530397440d", size = 394730 }, - { url = "https://files.pythonhosted.org/packages/35/56/ab417fc90c21826df048fc16e55316ac40876e4b790104ececcbce813d8f/rpds_py-0.24.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4cd031e63bc5f05bdcda120646a0d32f6d729486d0067f09d79c8db5368f4586", size = 448264 }, - { url = "https://files.pythonhosted.org/packages/b6/75/4c63862d5c05408589196c8440a35a14ea4ae337fa70ded1f03638373f06/rpds_py-0.24.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:34d90ad8c045df9a4259c47d2e16a3f21fdb396665c94520dbfe8766e62187a4", size = 446813 }, - { url = "https://files.pythonhosted.org/packages/e7/0c/91cf17dffa9a38835869797a9f041056091ebba6a53963d3641207e3d467/rpds_py-0.24.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e838bf2bb0b91ee67bf2b889a1a841e5ecac06dd7a2b1ef4e6151e2ce155c7ae", size = 389438 }, - { url = "https://files.pythonhosted.org/packages/1b/b0/60e6c72727c978276e02851819f3986bc40668f115be72c1bc4d922c950f/rpds_py-0.24.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:04ecf5c1ff4d589987b4d9882872f80ba13da7d42427234fce8f22efb43133bc", size = 420416 }, - { url = "https://files.pythonhosted.org/packages/a1/d7/f46f85b9f863fb59fd3c534b5c874c48bee86b19e93423b9da8784605415/rpds_py-0.24.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:630d3d8ea77eabd6cbcd2ea712e1c5cecb5b558d39547ac988351195db433f6c", size = 565236 }, - { url = "https://files.pythonhosted.org/packages/2a/d1/1467620ded6dd70afc45ec822cdf8dfe7139537780d1f3905de143deb6fd/rpds_py-0.24.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:ebcb786b9ff30b994d5969213a8430cbb984cdd7ea9fd6df06663194bd3c450c", size = 592016 }, - { url = "https://files.pythonhosted.org/packages/5d/13/fb1ded2e6adfaa0c0833106c42feb290973f665300f4facd5bf5d7891d9c/rpds_py-0.24.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:174e46569968ddbbeb8a806d9922f17cd2b524aa753b468f35b97ff9c19cb718", size = 560123 }, - { url = "https://files.pythonhosted.org/packages/1e/df/09fc1857ac7cc2eb16465a7199c314cbce7edde53c8ef21d615410d7335b/rpds_py-0.24.0-cp311-cp311-win32.whl", hash = "sha256:5ef877fa3bbfb40b388a5ae1cb00636a624690dcb9a29a65267054c9ea86d88a", size = 222256 }, - { url = "https://files.pythonhosted.org/packages/ff/25/939b40bc4d54bf910e5ee60fb5af99262c92458f4948239e8c06b0b750e7/rpds_py-0.24.0-cp311-cp311-win_amd64.whl", hash = "sha256:e274f62cbd274359eff63e5c7e7274c913e8e09620f6a57aae66744b3df046d6", size = 234718 }, - { url = "https://files.pythonhosted.org/packages/1a/e0/1c55f4a3be5f1ca1a4fd1f3ff1504a1478c1ed48d84de24574c4fa87e921/rpds_py-0.24.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:d8551e733626afec514b5d15befabea0dd70a343a9f23322860c4f16a9430205", size = 366945 }, - { url = "https://files.pythonhosted.org/packages/39/1b/a3501574fbf29118164314dbc800d568b8c1c7b3258b505360e8abb3902c/rpds_py-0.24.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0e374c0ce0ca82e5b67cd61fb964077d40ec177dd2c4eda67dba130de09085c7", size = 351935 }, - { url = "https://files.pythonhosted.org/packages/dc/47/77d3d71c55f6a374edde29f1aca0b2e547325ed00a9da820cabbc9497d2b/rpds_py-0.24.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d69d003296df4840bd445a5d15fa5b6ff6ac40496f956a221c4d1f6f7b4bc4d9", size = 390817 }, - { url = "https://files.pythonhosted.org/packages/4e/ec/1e336ee27484379e19c7f9cc170f4217c608aee406d3ae3a2e45336bff36/rpds_py-0.24.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8212ff58ac6dfde49946bea57474a386cca3f7706fc72c25b772b9ca4af6b79e", size = 401983 }, - { url = "https://files.pythonhosted.org/packages/07/f8/39b65cbc272c635eaea6d393c2ad1ccc81c39eca2db6723a0ca4b2108fce/rpds_py-0.24.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:528927e63a70b4d5f3f5ccc1fa988a35456eb5d15f804d276709c33fc2f19bda", size = 451719 }, - { url = "https://files.pythonhosted.org/packages/32/05/05c2b27dd9c30432f31738afed0300659cb9415db0ff7429b05dfb09bbde/rpds_py-0.24.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a824d2c7a703ba6daaca848f9c3d5cb93af0505be505de70e7e66829affd676e", size = 442546 }, - { url = "https://files.pythonhosted.org/packages/7d/e0/19383c8b5d509bd741532a47821c3e96acf4543d0832beba41b4434bcc49/rpds_py-0.24.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:44d51febb7a114293ffd56c6cf4736cb31cd68c0fddd6aa303ed09ea5a48e029", size = 393695 }, - { url = "https://files.pythonhosted.org/packages/9d/15/39f14e96d94981d0275715ae8ea564772237f3fa89bc3c21e24de934f2c7/rpds_py-0.24.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:3fab5f4a2c64a8fb64fc13b3d139848817a64d467dd6ed60dcdd6b479e7febc9", size = 427218 }, - { url = "https://files.pythonhosted.org/packages/22/b9/12da7124905a680f690da7a9de6f11de770b5e359f5649972f7181c8bf51/rpds_py-0.24.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:9be4f99bee42ac107870c61dfdb294d912bf81c3c6d45538aad7aecab468b6b7", size = 568062 }, - { url = "https://files.pythonhosted.org/packages/88/17/75229017a2143d915f6f803721a6d721eca24f2659c5718a538afa276b4f/rpds_py-0.24.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:564c96b6076a98215af52f55efa90d8419cc2ef45d99e314fddefe816bc24f91", size = 596262 }, - { url = "https://files.pythonhosted.org/packages/aa/64/8e8a1d8bd1b6b638d6acb6d41ab2cec7f2067a5b8b4c9175703875159a7c/rpds_py-0.24.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:75a810b7664c17f24bf2ffd7f92416c00ec84b49bb68e6a0d93e542406336b56", size = 564306 }, - { url = "https://files.pythonhosted.org/packages/68/1c/a7eac8d8ed8cb234a9b1064647824c387753343c3fab6ed7c83481ed0be7/rpds_py-0.24.0-cp312-cp312-win32.whl", hash = "sha256:f6016bd950be4dcd047b7475fdf55fb1e1f59fc7403f387be0e8123e4a576d30", size = 224281 }, - { url = "https://files.pythonhosted.org/packages/bb/46/b8b5424d1d21f2f2f3f2d468660085318d4f74a8df8289e3dd6ad224d488/rpds_py-0.24.0-cp312-cp312-win_amd64.whl", hash = "sha256:998c01b8e71cf051c28f5d6f1187abbdf5cf45fc0efce5da6c06447cba997034", size = 239719 }, - { url = "https://files.pythonhosted.org/packages/65/53/40bcc246a8354530d51a26d2b5b9afd1deacfb0d79e67295cc74df362f52/rpds_py-0.24.0-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:f9e0057a509e096e47c87f753136c9b10d7a91842d8042c2ee6866899a717c0d", size = 378386 }, - { url = "https://files.pythonhosted.org/packages/80/b0/5ea97dd2f53e3618560aa1f9674e896e63dff95a9b796879a201bc4c1f00/rpds_py-0.24.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:d6e109a454412ab82979c5b1b3aee0604eca4bbf9a02693bb9df027af2bfa91a", size = 363440 }, - { url = "https://files.pythonhosted.org/packages/57/9d/259b6eada6f747cdd60c9a5eb3efab15f6704c182547149926c38e5bd0d5/rpds_py-0.24.0-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fc1c892b1ec1f8cbd5da8de287577b455e388d9c328ad592eabbdcb6fc93bee5", size = 388816 }, - { url = "https://files.pythonhosted.org/packages/94/c1/faafc7183712f89f4b7620c3c15979ada13df137d35ef3011ae83e93b005/rpds_py-0.24.0-pp311-pypy311_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9c39438c55983d48f4bb3487734d040e22dad200dab22c41e331cee145e7a50d", size = 395058 }, - { url = "https://files.pythonhosted.org/packages/6c/96/d7fa9d2a7b7604a61da201cc0306a355006254942093779d7121c64700ce/rpds_py-0.24.0-pp311-pypy311_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9d7e8ce990ae17dda686f7e82fd41a055c668e13ddcf058e7fb5e9da20b57793", size = 448692 }, - { url = "https://files.pythonhosted.org/packages/96/37/a3146c6eebc65d6d8c96cc5ffdcdb6af2987412c789004213227fbe52467/rpds_py-0.24.0-pp311-pypy311_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9ea7f4174d2e4194289cb0c4e172d83e79a6404297ff95f2875cf9ac9bced8ba", size = 446462 }, - { url = "https://files.pythonhosted.org/packages/1f/13/6481dfd9ac7de43acdaaa416e3a7da40bc4bb8f5c6ca85e794100aa54596/rpds_py-0.24.0-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bb2954155bb8f63bb19d56d80e5e5320b61d71084617ed89efedb861a684baea", size = 390460 }, - { url = "https://files.pythonhosted.org/packages/61/e1/37e36bce65e109543cc4ff8d23206908649023549604fa2e7fbeba5342f7/rpds_py-0.24.0-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:04f2b712a2206e13800a8136b07aaedc23af3facab84918e7aa89e4be0260032", size = 421609 }, - { url = "https://files.pythonhosted.org/packages/20/dd/1f1a923d6cd798b8582176aca8a0784676f1a0449fb6f07fce6ac1cdbfb6/rpds_py-0.24.0-pp311-pypy311_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:eda5c1e2a715a4cbbca2d6d304988460942551e4e5e3b7457b50943cd741626d", size = 565818 }, - { url = "https://files.pythonhosted.org/packages/56/ec/d8da6df6a1eb3a418944a17b1cb38dd430b9e5a2e972eafd2b06f10c7c46/rpds_py-0.24.0-pp311-pypy311_pp73-musllinux_1_2_i686.whl", hash = "sha256:9abc80fe8c1f87218db116016de575a7998ab1629078c90840e8d11ab423ee25", size = 592627 }, - { url = "https://files.pythonhosted.org/packages/b3/14/c492b9c7d5dd133e13f211ddea6bb9870f99e4f73932f11aa00bc09a9be9/rpds_py-0.24.0-pp311-pypy311_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:6a727fd083009bc83eb83d6950f0c32b3c94c8b80a9b667c87f4bd1274ca30ba", size = 560885 }, +version = "0.25.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/8c/a6/60184b7fc00dd3ca80ac635dd5b8577d444c57e8e8742cecabfacb829921/rpds_py-0.25.1.tar.gz", hash = "sha256:8960b6dac09b62dac26e75d7e2c4a22efb835d827a7278c34f72b2b84fa160e3", size = 27304, upload-time = "2025-05-21T12:46:12.502Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/95/e1/df13fe3ddbbea43567e07437f097863b20c99318ae1f58a0fe389f763738/rpds_py-0.25.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:5f048bbf18b1f9120685c6d6bb70cc1a52c8cc11bdd04e643d28d3be0baf666d", size = 373341, upload-time = "2025-05-21T12:43:02.978Z" }, + { url = "https://files.pythonhosted.org/packages/7a/58/deef4d30fcbcbfef3b6d82d17c64490d5c94585a2310544ce8e2d3024f83/rpds_py-0.25.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4fbb0dbba559959fcb5d0735a0f87cdbca9e95dac87982e9b95c0f8f7ad10255", size = 359111, upload-time = "2025-05-21T12:43:05.128Z" }, + { url = "https://files.pythonhosted.org/packages/bb/7e/39f1f4431b03e96ebaf159e29a0f82a77259d8f38b2dd474721eb3a8ac9b/rpds_py-0.25.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d4ca54b9cf9d80b4016a67a0193ebe0bcf29f6b0a96f09db942087e294d3d4c2", size = 386112, upload-time = "2025-05-21T12:43:07.13Z" }, + { url = "https://files.pythonhosted.org/packages/db/e7/847068a48d63aec2ae695a1646089620b3b03f8ccf9f02c122ebaf778f3c/rpds_py-0.25.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1ee3e26eb83d39b886d2cb6e06ea701bba82ef30a0de044d34626ede51ec98b0", size = 400362, upload-time = "2025-05-21T12:43:08.693Z" }, + { url = "https://files.pythonhosted.org/packages/3b/3d/9441d5db4343d0cee759a7ab4d67420a476cebb032081763de934719727b/rpds_py-0.25.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:89706d0683c73a26f76a5315d893c051324d771196ae8b13e6ffa1ffaf5e574f", size = 522214, upload-time = "2025-05-21T12:43:10.694Z" }, + { url = "https://files.pythonhosted.org/packages/a2/ec/2cc5b30d95f9f1a432c79c7a2f65d85e52812a8f6cbf8768724571710786/rpds_py-0.25.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c2013ee878c76269c7b557a9a9c042335d732e89d482606990b70a839635feb7", size = 411491, upload-time = "2025-05-21T12:43:12.739Z" }, + { url = "https://files.pythonhosted.org/packages/dc/6c/44695c1f035077a017dd472b6a3253553780837af2fac9b6ac25f6a5cb4d/rpds_py-0.25.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:45e484db65e5380804afbec784522de84fa95e6bb92ef1bd3325d33d13efaebd", size = 386978, upload-time = "2025-05-21T12:43:14.25Z" }, + { url = "https://files.pythonhosted.org/packages/b1/74/b4357090bb1096db5392157b4e7ed8bb2417dc7799200fcbaee633a032c9/rpds_py-0.25.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:48d64155d02127c249695abb87d39f0faf410733428d499867606be138161d65", size = 420662, upload-time = "2025-05-21T12:43:15.8Z" }, + { url = "https://files.pythonhosted.org/packages/26/dd/8cadbebf47b96e59dfe8b35868e5c38a42272699324e95ed522da09d3a40/rpds_py-0.25.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:048893e902132fd6548a2e661fb38bf4896a89eea95ac5816cf443524a85556f", size = 563385, upload-time = "2025-05-21T12:43:17.78Z" }, + { url = "https://files.pythonhosted.org/packages/c3/ea/92960bb7f0e7a57a5ab233662f12152085c7dc0d5468534c65991a3d48c9/rpds_py-0.25.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:0317177b1e8691ab5879f4f33f4b6dc55ad3b344399e23df2e499de7b10a548d", size = 592047, upload-time = "2025-05-21T12:43:19.457Z" }, + { url = "https://files.pythonhosted.org/packages/61/ad/71aabc93df0d05dabcb4b0c749277881f8e74548582d96aa1bf24379493a/rpds_py-0.25.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:bffcf57826d77a4151962bf1701374e0fc87f536e56ec46f1abdd6a903354042", size = 557863, upload-time = "2025-05-21T12:43:21.69Z" }, + { url = "https://files.pythonhosted.org/packages/93/0f/89df0067c41f122b90b76f3660028a466eb287cbe38efec3ea70e637ca78/rpds_py-0.25.1-cp311-cp311-win32.whl", hash = "sha256:cda776f1967cb304816173b30994faaf2fd5bcb37e73118a47964a02c348e1bc", size = 219627, upload-time = "2025-05-21T12:43:23.311Z" }, + { url = "https://files.pythonhosted.org/packages/7c/8d/93b1a4c1baa903d0229374d9e7aa3466d751f1d65e268c52e6039c6e338e/rpds_py-0.25.1-cp311-cp311-win_amd64.whl", hash = "sha256:dc3c1ff0abc91444cd20ec643d0f805df9a3661fcacf9c95000329f3ddf268a4", size = 231603, upload-time = "2025-05-21T12:43:25.145Z" }, + { url = "https://files.pythonhosted.org/packages/cb/11/392605e5247bead2f23e6888e77229fbd714ac241ebbebb39a1e822c8815/rpds_py-0.25.1-cp311-cp311-win_arm64.whl", hash = "sha256:5a3ddb74b0985c4387719fc536faced33cadf2172769540c62e2a94b7b9be1c4", size = 223967, upload-time = "2025-05-21T12:43:26.566Z" }, + { url = "https://files.pythonhosted.org/packages/7f/81/28ab0408391b1dc57393653b6a0cf2014cc282cc2909e4615e63e58262be/rpds_py-0.25.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:b5ffe453cde61f73fea9430223c81d29e2fbf412a6073951102146c84e19e34c", size = 364647, upload-time = "2025-05-21T12:43:28.559Z" }, + { url = "https://files.pythonhosted.org/packages/2c/9a/7797f04cad0d5e56310e1238434f71fc6939d0bc517192a18bb99a72a95f/rpds_py-0.25.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:115874ae5e2fdcfc16b2aedc95b5eef4aebe91b28e7e21951eda8a5dc0d3461b", size = 350454, upload-time = "2025-05-21T12:43:30.615Z" }, + { url = "https://files.pythonhosted.org/packages/69/3c/93d2ef941b04898011e5d6eaa56a1acf46a3b4c9f4b3ad1bbcbafa0bee1f/rpds_py-0.25.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a714bf6e5e81b0e570d01f56e0c89c6375101b8463999ead3a93a5d2a4af91fa", size = 389665, upload-time = "2025-05-21T12:43:32.629Z" }, + { url = "https://files.pythonhosted.org/packages/c1/57/ad0e31e928751dde8903a11102559628d24173428a0f85e25e187defb2c1/rpds_py-0.25.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:35634369325906bcd01577da4c19e3b9541a15e99f31e91a02d010816b49bfda", size = 403873, upload-time = "2025-05-21T12:43:34.576Z" }, + { url = "https://files.pythonhosted.org/packages/16/ad/c0c652fa9bba778b4f54980a02962748479dc09632e1fd34e5282cf2556c/rpds_py-0.25.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d4cb2b3ddc16710548801c6fcc0cfcdeeff9dafbc983f77265877793f2660309", size = 525866, upload-time = "2025-05-21T12:43:36.123Z" }, + { url = "https://files.pythonhosted.org/packages/2a/39/3e1839bc527e6fcf48d5fec4770070f872cdee6c6fbc9b259932f4e88a38/rpds_py-0.25.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9ceca1cf097ed77e1a51f1dbc8d174d10cb5931c188a4505ff9f3e119dfe519b", size = 416886, upload-time = "2025-05-21T12:43:38.034Z" }, + { url = "https://files.pythonhosted.org/packages/7a/95/dd6b91cd4560da41df9d7030a038298a67d24f8ca38e150562644c829c48/rpds_py-0.25.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2c2cd1a4b0c2b8c5e31ffff50d09f39906fe351389ba143c195566056c13a7ea", size = 390666, upload-time = "2025-05-21T12:43:40.065Z" }, + { url = "https://files.pythonhosted.org/packages/64/48/1be88a820e7494ce0a15c2d390ccb7c52212370badabf128e6a7bb4cb802/rpds_py-0.25.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1de336a4b164c9188cb23f3703adb74a7623ab32d20090d0e9bf499a2203ad65", size = 425109, upload-time = "2025-05-21T12:43:42.263Z" }, + { url = "https://files.pythonhosted.org/packages/cf/07/3e2a17927ef6d7720b9949ec1b37d1e963b829ad0387f7af18d923d5cfa5/rpds_py-0.25.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:9fca84a15333e925dd59ce01da0ffe2ffe0d6e5d29a9eeba2148916d1824948c", size = 567244, upload-time = "2025-05-21T12:43:43.846Z" }, + { url = "https://files.pythonhosted.org/packages/d2/e5/76cf010998deccc4f95305d827847e2eae9c568099c06b405cf96384762b/rpds_py-0.25.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:88ec04afe0c59fa64e2f6ea0dd9657e04fc83e38de90f6de201954b4d4eb59bd", size = 596023, upload-time = "2025-05-21T12:43:45.932Z" }, + { url = "https://files.pythonhosted.org/packages/52/9a/df55efd84403736ba37a5a6377b70aad0fd1cb469a9109ee8a1e21299a1c/rpds_py-0.25.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a8bd2f19e312ce3e1d2c635618e8a8d8132892bb746a7cf74780a489f0f6cdcb", size = 561634, upload-time = "2025-05-21T12:43:48.263Z" }, + { url = "https://files.pythonhosted.org/packages/ab/aa/dc3620dd8db84454aaf9374bd318f1aa02578bba5e567f5bf6b79492aca4/rpds_py-0.25.1-cp312-cp312-win32.whl", hash = "sha256:e5e2f7280d8d0d3ef06f3ec1b4fd598d386cc6f0721e54f09109a8132182fbfe", size = 222713, upload-time = "2025-05-21T12:43:49.897Z" }, + { url = "https://files.pythonhosted.org/packages/a3/7f/7cef485269a50ed5b4e9bae145f512d2a111ca638ae70cc101f661b4defd/rpds_py-0.25.1-cp312-cp312-win_amd64.whl", hash = "sha256:db58483f71c5db67d643857404da360dce3573031586034b7d59f245144cc192", size = 235280, upload-time = "2025-05-21T12:43:51.893Z" }, + { url = "https://files.pythonhosted.org/packages/99/f2/c2d64f6564f32af913bf5f3f7ae41c7c263c5ae4c4e8f1a17af8af66cd46/rpds_py-0.25.1-cp312-cp312-win_arm64.whl", hash = "sha256:6d50841c425d16faf3206ddbba44c21aa3310a0cebc3c1cdfc3e3f4f9f6f5728", size = 225399, upload-time = "2025-05-21T12:43:53.351Z" }, + { url = "https://files.pythonhosted.org/packages/49/74/48f3df0715a585cbf5d34919c9c757a4c92c1a9eba059f2d334e72471f70/rpds_py-0.25.1-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:ee86d81551ec68a5c25373c5643d343150cc54672b5e9a0cafc93c1870a53954", size = 374208, upload-time = "2025-05-21T12:45:26.306Z" }, + { url = "https://files.pythonhosted.org/packages/55/b0/9b01bb11ce01ec03d05e627249cc2c06039d6aa24ea5a22a39c312167c10/rpds_py-0.25.1-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:89c24300cd4a8e4a51e55c31a8ff3918e6651b241ee8876a42cc2b2a078533ba", size = 359262, upload-time = "2025-05-21T12:45:28.322Z" }, + { url = "https://files.pythonhosted.org/packages/a9/eb/5395621618f723ebd5116c53282052943a726dba111b49cd2071f785b665/rpds_py-0.25.1-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:771c16060ff4e79584dc48902a91ba79fd93eade3aa3a12d6d2a4aadaf7d542b", size = 387366, upload-time = "2025-05-21T12:45:30.42Z" }, + { url = "https://files.pythonhosted.org/packages/68/73/3d51442bdb246db619d75039a50ea1cf8b5b4ee250c3e5cd5c3af5981cd4/rpds_py-0.25.1-pp311-pypy311_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:785ffacd0ee61c3e60bdfde93baa6d7c10d86f15655bd706c89da08068dc5038", size = 400759, upload-time = "2025-05-21T12:45:32.516Z" }, + { url = "https://files.pythonhosted.org/packages/b7/4c/3a32d5955d7e6cb117314597bc0f2224efc798428318b13073efe306512a/rpds_py-0.25.1-pp311-pypy311_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2a40046a529cc15cef88ac5ab589f83f739e2d332cb4d7399072242400ed68c9", size = 523128, upload-time = "2025-05-21T12:45:34.396Z" }, + { url = "https://files.pythonhosted.org/packages/be/95/1ffccd3b0bb901ae60b1dd4b1be2ab98bb4eb834cd9b15199888f5702f7b/rpds_py-0.25.1-pp311-pypy311_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:85fc223d9c76cabe5d0bff82214459189720dc135db45f9f66aa7cffbf9ff6c1", size = 411597, upload-time = "2025-05-21T12:45:36.164Z" }, + { url = "https://files.pythonhosted.org/packages/ef/6d/6e6cd310180689db8b0d2de7f7d1eabf3fb013f239e156ae0d5a1a85c27f/rpds_py-0.25.1-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b0be9965f93c222fb9b4cc254235b3b2b215796c03ef5ee64f995b1b69af0762", size = 388053, upload-time = "2025-05-21T12:45:38.45Z" }, + { url = "https://files.pythonhosted.org/packages/4a/87/ec4186b1fe6365ced6fa470960e68fc7804bafbe7c0cf5a36237aa240efa/rpds_py-0.25.1-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8378fa4a940f3fb509c081e06cb7f7f2adae8cf46ef258b0e0ed7519facd573e", size = 421821, upload-time = "2025-05-21T12:45:40.732Z" }, + { url = "https://files.pythonhosted.org/packages/7a/60/84f821f6bf4e0e710acc5039d91f8f594fae0d93fc368704920d8971680d/rpds_py-0.25.1-pp311-pypy311_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:33358883a4490287e67a2c391dfaea4d9359860281db3292b6886bf0be3d8692", size = 564534, upload-time = "2025-05-21T12:45:42.672Z" }, + { url = "https://files.pythonhosted.org/packages/41/3a/bc654eb15d3b38f9330fe0f545016ba154d89cdabc6177b0295910cd0ebe/rpds_py-0.25.1-pp311-pypy311_pp73-musllinux_1_2_i686.whl", hash = "sha256:1d1fadd539298e70cac2f2cb36f5b8a65f742b9b9f1014dd4ea1f7785e2470bf", size = 592674, upload-time = "2025-05-21T12:45:44.533Z" }, + { url = "https://files.pythonhosted.org/packages/2e/ba/31239736f29e4dfc7a58a45955c5db852864c306131fd6320aea214d5437/rpds_py-0.25.1-pp311-pypy311_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:9a46c2fb2545e21181445515960006e85d22025bd2fe6db23e76daec6eb689fe", size = 558781, upload-time = "2025-05-21T12:45:46.281Z" }, ] [[package]] name = "rsa" -version = "4.9" +version = "4.9.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyasn1" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/aa/65/7d973b89c4d2351d7fb232c2e452547ddfa243e93131e7cfa766da627b52/rsa-4.9.tar.gz", hash = "sha256:e38464a49c6c85d7f1351b0126661487a7e0a14a50f1675ec50eb34d4f20ef21", size = 29711 } +sdist = { url = "https://files.pythonhosted.org/packages/da/8a/22b7beea3ee0d44b1916c0c1cb0ee3af23b700b6da9f04991899d0c555d4/rsa-4.9.1.tar.gz", hash = "sha256:e7bdbfdb5497da4c07dfd35530e1a902659db6ff241e39d9953cad06ebd0ae75", size = 29034, upload-time = "2025-04-16T09:51:18.218Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/49/97/fa78e3d2f65c02c8e1268b9aba606569fe97f6c8f7c2d74394553347c145/rsa-4.9-py3-none-any.whl", hash = "sha256:90260d9058e514786967344d0ef75fa8727eed8a7d2e43ce9f4bcf1b536174f7", size = 34315 }, + { url = "https://files.pythonhosted.org/packages/64/8d/0133e4eb4beed9e425d9a98ed6e081a55d195481b7632472be1af08d2f6b/rsa-4.9.1-py3-none-any.whl", hash = "sha256:68635866661c6836b8d39430f97a996acbd61bfa49406748ea243539fe239762", size = 34696, upload-time = "2025-04-16T09:51:17.142Z" }, ] [[package]] name = "ruff" -version = "0.11.5" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/45/71/5759b2a6b2279bb77fe15b1435b89473631c2cd6374d45ccdb6b785810be/ruff-0.11.5.tar.gz", hash = "sha256:cae2e2439cb88853e421901ec040a758960b576126dab520fa08e9de431d1bef", size = 3976488 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/23/db/6efda6381778eec7f35875b5cbefd194904832a1153d68d36d6b269d81a8/ruff-0.11.5-py3-none-linux_armv6l.whl", hash = "sha256:2561294e108eb648e50f210671cc56aee590fb6167b594144401532138c66c7b", size = 10103150 }, - { url = "https://files.pythonhosted.org/packages/44/f2/06cd9006077a8db61956768bc200a8e52515bf33a8f9b671ee527bb10d77/ruff-0.11.5-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:ac12884b9e005c12d0bd121f56ccf8033e1614f736f766c118ad60780882a077", size = 10898637 }, - { url = "https://files.pythonhosted.org/packages/18/f5/af390a013c56022fe6f72b95c86eb7b2585c89cc25d63882d3bfe411ecf1/ruff-0.11.5-py3-none-macosx_11_0_arm64.whl", hash = "sha256:4bfd80a6ec559a5eeb96c33f832418bf0fb96752de0539905cf7b0cc1d31d779", size = 10236012 }, - { url = "https://files.pythonhosted.org/packages/b8/ca/b9bf954cfed165e1a0c24b86305d5c8ea75def256707f2448439ac5e0d8b/ruff-0.11.5-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0947c0a1afa75dcb5db4b34b070ec2bccee869d40e6cc8ab25aca11a7d527794", size = 10415338 }, - { url = "https://files.pythonhosted.org/packages/d9/4d/2522dde4e790f1b59885283f8786ab0046958dfd39959c81acc75d347467/ruff-0.11.5-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ad871ff74b5ec9caa66cb725b85d4ef89b53f8170f47c3406e32ef040400b038", size = 9965277 }, - { url = "https://files.pythonhosted.org/packages/e5/7a/749f56f150eef71ce2f626a2f6988446c620af2f9ba2a7804295ca450397/ruff-0.11.5-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e6cf918390cfe46d240732d4d72fa6e18e528ca1f60e318a10835cf2fa3dc19f", size = 11541614 }, - { url = "https://files.pythonhosted.org/packages/89/b2/7d9b8435222485b6aac627d9c29793ba89be40b5de11584ca604b829e960/ruff-0.11.5-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:56145ee1478582f61c08f21076dc59153310d606ad663acc00ea3ab5b2125f82", size = 12198873 }, - { url = "https://files.pythonhosted.org/packages/00/e0/a1a69ef5ffb5c5f9c31554b27e030a9c468fc6f57055886d27d316dfbabd/ruff-0.11.5-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e5f66f8f1e8c9fc594cbd66fbc5f246a8d91f916cb9667e80208663ec3728304", size = 11670190 }, - { url = "https://files.pythonhosted.org/packages/05/61/c1c16df6e92975072c07f8b20dad35cd858e8462b8865bc856fe5d6ccb63/ruff-0.11.5-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:80b4df4d335a80315ab9afc81ed1cff62be112bd165e162b5eed8ac55bfc8470", size = 13902301 }, - { url = "https://files.pythonhosted.org/packages/79/89/0af10c8af4363304fd8cb833bd407a2850c760b71edf742c18d5a87bb3ad/ruff-0.11.5-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3068befab73620b8a0cc2431bd46b3cd619bc17d6f7695a3e1bb166b652c382a", size = 11350132 }, - { url = "https://files.pythonhosted.org/packages/b9/e1/ecb4c687cbf15164dd00e38cf62cbab238cad05dd8b6b0fc68b0c2785e15/ruff-0.11.5-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:f5da2e710a9641828e09aa98b92c9ebbc60518fdf3921241326ca3e8f8e55b8b", size = 10312937 }, - { url = "https://files.pythonhosted.org/packages/cf/4f/0e53fe5e500b65934500949361e3cd290c5ba60f0324ed59d15f46479c06/ruff-0.11.5-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:ef39f19cb8ec98cbc762344921e216f3857a06c47412030374fffd413fb8fd3a", size = 9936683 }, - { url = "https://files.pythonhosted.org/packages/04/a8/8183c4da6d35794ae7f76f96261ef5960853cd3f899c2671961f97a27d8e/ruff-0.11.5-py3-none-musllinux_1_2_i686.whl", hash = "sha256:b2a7cedf47244f431fd11aa5a7e2806dda2e0c365873bda7834e8f7d785ae159", size = 10950217 }, - { url = "https://files.pythonhosted.org/packages/26/88/9b85a5a8af21e46a0639b107fcf9bfc31da4f1d263f2fc7fbe7199b47f0a/ruff-0.11.5-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:81be52e7519f3d1a0beadcf8e974715b2dfc808ae8ec729ecfc79bddf8dbb783", size = 11404521 }, - { url = "https://files.pythonhosted.org/packages/fc/52/047f35d3b20fd1ae9ccfe28791ef0f3ca0ef0b3e6c1a58badd97d450131b/ruff-0.11.5-py3-none-win32.whl", hash = "sha256:e268da7b40f56e3eca571508a7e567e794f9bfcc0f412c4b607931d3af9c4afe", size = 10320697 }, - { url = "https://files.pythonhosted.org/packages/b9/fe/00c78010e3332a6e92762424cf4c1919065707e962232797d0b57fd8267e/ruff-0.11.5-py3-none-win_amd64.whl", hash = "sha256:6c6dc38af3cfe2863213ea25b6dc616d679205732dc0fb673356c2d69608f800", size = 11378665 }, - { url = "https://files.pythonhosted.org/packages/43/7c/c83fe5cbb70ff017612ff36654edfebec4b1ef79b558b8e5fd933bab836b/ruff-0.11.5-py3-none-win_arm64.whl", hash = "sha256:67e241b4314f4eacf14a601d586026a962f4002a475aa702c69980a38087aa4e", size = 10460287 }, +version = "0.11.12" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/15/0a/92416b159ec00cdf11e5882a9d80d29bf84bba3dbebc51c4898bfbca1da6/ruff-0.11.12.tar.gz", hash = "sha256:43cf7f69c7d7c7d7513b9d59c5d8cafd704e05944f978614aa9faff6ac202603", size = 4202289, upload-time = "2025-05-29T13:31:40.037Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/60/cc/53eb79f012d15e136d40a8e8fc519ba8f55a057f60b29c2df34efd47c6e3/ruff-0.11.12-py3-none-linux_armv6l.whl", hash = "sha256:c7680aa2f0d4c4f43353d1e72123955c7a2159b8646cd43402de6d4a3a25d7cc", size = 10285597, upload-time = "2025-05-29T13:30:57.539Z" }, + { url = "https://files.pythonhosted.org/packages/e7/d7/73386e9fb0232b015a23f62fea7503f96e29c29e6c45461d4a73bac74df9/ruff-0.11.12-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:2cad64843da9f134565c20bcc430642de897b8ea02e2e79e6e02a76b8dcad7c3", size = 11053154, upload-time = "2025-05-29T13:31:00.865Z" }, + { url = "https://files.pythonhosted.org/packages/4e/eb/3eae144c5114e92deb65a0cb2c72326c8469e14991e9bc3ec0349da1331c/ruff-0.11.12-py3-none-macosx_11_0_arm64.whl", hash = "sha256:9b6886b524a1c659cee1758140138455d3c029783d1b9e643f3624a5ee0cb0aa", size = 10403048, upload-time = "2025-05-29T13:31:03.413Z" }, + { url = "https://files.pythonhosted.org/packages/29/64/20c54b20e58b1058db6689e94731f2a22e9f7abab74e1a758dfba058b6ca/ruff-0.11.12-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3cc3a3690aad6e86c1958d3ec3c38c4594b6ecec75c1f531e84160bd827b2012", size = 10597062, upload-time = "2025-05-29T13:31:05.539Z" }, + { url = "https://files.pythonhosted.org/packages/29/3a/79fa6a9a39422a400564ca7233a689a151f1039110f0bbbabcb38106883a/ruff-0.11.12-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f97fdbc2549f456c65b3b0048560d44ddd540db1f27c778a938371424b49fe4a", size = 10155152, upload-time = "2025-05-29T13:31:07.986Z" }, + { url = "https://files.pythonhosted.org/packages/e5/a4/22c2c97b2340aa968af3a39bc38045e78d36abd4ed3fa2bde91c31e712e3/ruff-0.11.12-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:74adf84960236961090e2d1348c1a67d940fd12e811a33fb3d107df61eef8fc7", size = 11723067, upload-time = "2025-05-29T13:31:10.57Z" }, + { url = "https://files.pythonhosted.org/packages/bc/cf/3e452fbd9597bcd8058856ecd42b22751749d07935793a1856d988154151/ruff-0.11.12-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:b56697e5b8bcf1d61293ccfe63873aba08fdbcbbba839fc046ec5926bdb25a3a", size = 12460807, upload-time = "2025-05-29T13:31:12.88Z" }, + { url = "https://files.pythonhosted.org/packages/2f/ec/8f170381a15e1eb7d93cb4feef8d17334d5a1eb33fee273aee5d1f8241a3/ruff-0.11.12-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4d47afa45e7b0eaf5e5969c6b39cbd108be83910b5c74626247e366fd7a36a13", size = 12063261, upload-time = "2025-05-29T13:31:15.236Z" }, + { url = "https://files.pythonhosted.org/packages/0d/bf/57208f8c0a8153a14652a85f4116c0002148e83770d7a41f2e90b52d2b4e/ruff-0.11.12-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:692bf9603fe1bf949de8b09a2da896f05c01ed7a187f4a386cdba6760e7f61be", size = 11329601, upload-time = "2025-05-29T13:31:18.68Z" }, + { url = "https://files.pythonhosted.org/packages/c3/56/edf942f7fdac5888094d9ffa303f12096f1a93eb46570bcf5f14c0c70880/ruff-0.11.12-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:08033320e979df3b20dba567c62f69c45e01df708b0f9c83912d7abd3e0801cd", size = 11522186, upload-time = "2025-05-29T13:31:21.216Z" }, + { url = "https://files.pythonhosted.org/packages/ed/63/79ffef65246911ed7e2290aeece48739d9603b3a35f9529fec0fc6c26400/ruff-0.11.12-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:929b7706584f5bfd61d67d5070f399057d07c70585fa8c4491d78ada452d3bef", size = 10449032, upload-time = "2025-05-29T13:31:23.417Z" }, + { url = "https://files.pythonhosted.org/packages/88/19/8c9d4d8a1c2a3f5a1ea45a64b42593d50e28b8e038f1aafd65d6b43647f3/ruff-0.11.12-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:7de4a73205dc5756b8e09ee3ed67c38312dce1aa28972b93150f5751199981b5", size = 10129370, upload-time = "2025-05-29T13:31:25.777Z" }, + { url = "https://files.pythonhosted.org/packages/bc/0f/2d15533eaa18f460530a857e1778900cd867ded67f16c85723569d54e410/ruff-0.11.12-py3-none-musllinux_1_2_i686.whl", hash = "sha256:2635c2a90ac1b8ca9e93b70af59dfd1dd2026a40e2d6eebaa3efb0465dd9cf02", size = 11123529, upload-time = "2025-05-29T13:31:28.396Z" }, + { url = "https://files.pythonhosted.org/packages/4f/e2/4c2ac669534bdded835356813f48ea33cfb3a947dc47f270038364587088/ruff-0.11.12-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:d05d6a78a89166f03f03a198ecc9d18779076ad0eec476819467acb401028c0c", size = 11577642, upload-time = "2025-05-29T13:31:30.647Z" }, + { url = "https://files.pythonhosted.org/packages/a7/9b/c9ddf7f924d5617a1c94a93ba595f4b24cb5bc50e98b94433ab3f7ad27e5/ruff-0.11.12-py3-none-win32.whl", hash = "sha256:f5a07f49767c4be4772d161bfc049c1f242db0cfe1bd976e0f0886732a4765d6", size = 10475511, upload-time = "2025-05-29T13:31:32.917Z" }, + { url = "https://files.pythonhosted.org/packages/fd/d6/74fb6d3470c1aada019ffff33c0f9210af746cca0a4de19a1f10ce54968a/ruff-0.11.12-py3-none-win_amd64.whl", hash = "sha256:5a4d9f8030d8c3a45df201d7fb3ed38d0219bccd7955268e863ee4a115fa0832", size = 11523573, upload-time = "2025-05-29T13:31:35.782Z" }, + { url = "https://files.pythonhosted.org/packages/44/42/d58086ec20f52d2b0140752ae54b355ea2be2ed46f914231136dd1effcc7/ruff-0.11.12-py3-none-win_arm64.whl", hash = "sha256:65194e37853158d368e333ba282217941029a28ea90913c67e558c611d04daa5", size = 10697770, upload-time = "2025-05-29T13:31:38.009Z" }, ] [[package]] @@ -4892,44 +5075,56 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "botocore" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c0/0a/1cdbabf9edd0ea7747efdf6c9ab4e7061b085aa7f9bfc36bb1601563b069/s3transfer-0.10.4.tar.gz", hash = "sha256:29edc09801743c21eb5ecbc617a152df41d3c287f67b615f73e5f750583666a7", size = 145287 } +sdist = { url = "https://files.pythonhosted.org/packages/c0/0a/1cdbabf9edd0ea7747efdf6c9ab4e7061b085aa7f9bfc36bb1601563b069/s3transfer-0.10.4.tar.gz", hash = "sha256:29edc09801743c21eb5ecbc617a152df41d3c287f67b615f73e5f750583666a7", size = 145287, upload-time = "2024-11-20T21:06:05.981Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/66/05/7957af15543b8c9799209506df4660cba7afc4cf94bfb60513827e96bed6/s3transfer-0.10.4-py3-none-any.whl", hash = "sha256:244a76a24355363a68164241438de1b72f8781664920260c48465896b712a41e", size = 83175 }, + { url = "https://files.pythonhosted.org/packages/66/05/7957af15543b8c9799209506df4660cba7afc4cf94bfb60513827e96bed6/s3transfer-0.10.4-py3-none-any.whl", hash = "sha256:244a76a24355363a68164241438de1b72f8781664920260c48465896b712a41e", size = 83175, upload-time = "2024-11-20T21:06:03.961Z" }, ] [[package]] name = "safetensors" version = "0.5.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/71/7e/2d5d6ee7b40c0682315367ec7475693d110f512922d582fef1bd4a63adc3/safetensors-0.5.3.tar.gz", hash = "sha256:b6b0d6ecacec39a4fdd99cc19f4576f5219ce858e6fd8dbe7609df0b8dc56965", size = 67210 } +sdist = { url = "https://files.pythonhosted.org/packages/71/7e/2d5d6ee7b40c0682315367ec7475693d110f512922d582fef1bd4a63adc3/safetensors-0.5.3.tar.gz", hash = "sha256:b6b0d6ecacec39a4fdd99cc19f4576f5219ce858e6fd8dbe7609df0b8dc56965", size = 67210, upload-time = "2025-02-26T09:15:13.155Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/18/ae/88f6c49dbd0cc4da0e08610019a3c78a7d390879a919411a410a1876d03a/safetensors-0.5.3-cp38-abi3-macosx_10_12_x86_64.whl", hash = "sha256:bd20eb133db8ed15b40110b7c00c6df51655a2998132193de2f75f72d99c7073", size = 436917, upload-time = "2025-02-26T09:15:03.702Z" }, + { url = "https://files.pythonhosted.org/packages/b8/3b/11f1b4a2f5d2ab7da34ecc062b0bc301f2be024d110a6466726bec8c055c/safetensors-0.5.3-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:21d01c14ff6c415c485616b8b0bf961c46b3b343ca59110d38d744e577f9cce7", size = 418419, upload-time = "2025-02-26T09:15:01.765Z" }, + { url = "https://files.pythonhosted.org/packages/5d/9a/add3e6fef267658075c5a41573c26d42d80c935cdc992384dfae435feaef/safetensors-0.5.3-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:11bce6164887cd491ca75c2326a113ba934be596e22b28b1742ce27b1d076467", size = 459493, upload-time = "2025-02-26T09:14:51.812Z" }, + { url = "https://files.pythonhosted.org/packages/df/5c/bf2cae92222513cc23b3ff85c4a1bb2811a2c3583ac0f8e8d502751de934/safetensors-0.5.3-cp38-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4a243be3590bc3301c821da7a18d87224ef35cbd3e5f5727e4e0728b8172411e", size = 472400, upload-time = "2025-02-26T09:14:53.549Z" }, + { url = "https://files.pythonhosted.org/packages/58/11/7456afb740bd45782d0f4c8e8e1bb9e572f1bf82899fb6ace58af47b4282/safetensors-0.5.3-cp38-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8bd84b12b1670a6f8e50f01e28156422a2bc07fb16fc4e98bded13039d688a0d", size = 522891, upload-time = "2025-02-26T09:14:55.717Z" }, + { url = "https://files.pythonhosted.org/packages/57/3d/fe73a9d2ace487e7285f6e157afee2383bd1ddb911b7cb44a55cf812eae3/safetensors-0.5.3-cp38-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:391ac8cab7c829452175f871fcaf414aa1e292b5448bd02620f675a7f3e7abb9", size = 537694, upload-time = "2025-02-26T09:14:57.036Z" }, + { url = "https://files.pythonhosted.org/packages/a6/f8/dae3421624fcc87a89d42e1898a798bc7ff72c61f38973a65d60df8f124c/safetensors-0.5.3-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cead1fa41fc54b1e61089fa57452e8834f798cb1dc7a09ba3524f1eb08e0317a", size = 471642, upload-time = "2025-02-26T09:15:00.544Z" }, + { url = "https://files.pythonhosted.org/packages/ce/20/1fbe16f9b815f6c5a672f5b760951e20e17e43f67f231428f871909a37f6/safetensors-0.5.3-cp38-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1077f3e94182d72618357b04b5ced540ceb71c8a813d3319f1aba448e68a770d", size = 502241, upload-time = "2025-02-26T09:14:58.303Z" }, + { url = "https://files.pythonhosted.org/packages/5f/18/8e108846b506487aa4629fe4116b27db65c3dde922de2c8e0cc1133f3f29/safetensors-0.5.3-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:799021e78287bac619c7b3f3606730a22da4cda27759ddf55d37c8db7511c74b", size = 638001, upload-time = "2025-02-26T09:15:05.79Z" }, + { url = "https://files.pythonhosted.org/packages/82/5a/c116111d8291af6c8c8a8b40628fe833b9db97d8141c2a82359d14d9e078/safetensors-0.5.3-cp38-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:df26da01aaac504334644e1b7642fa000bfec820e7cef83aeac4e355e03195ff", size = 734013, upload-time = "2025-02-26T09:15:07.892Z" }, + { url = "https://files.pythonhosted.org/packages/7d/ff/41fcc4d3b7de837963622e8610d998710705bbde9a8a17221d85e5d0baad/safetensors-0.5.3-cp38-abi3-musllinux_1_2_i686.whl", hash = "sha256:32c3ef2d7af8b9f52ff685ed0bc43913cdcde135089ae322ee576de93eae5135", size = 670687, upload-time = "2025-02-26T09:15:09.979Z" }, + { url = "https://files.pythonhosted.org/packages/40/ad/2b113098e69c985a3d8fbda4b902778eae4a35b7d5188859b4a63d30c161/safetensors-0.5.3-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:37f1521be045e56fc2b54c606d4455573e717b2d887c579ee1dbba5f868ece04", size = 643147, upload-time = "2025-02-26T09:15:11.185Z" }, + { url = "https://files.pythonhosted.org/packages/0a/0c/95aeb51d4246bd9a3242d3d8349c1112b4ee7611a4b40f0c5c93b05f001d/safetensors-0.5.3-cp38-abi3-win32.whl", hash = "sha256:cfc0ec0846dcf6763b0ed3d1846ff36008c6e7290683b61616c4b040f6a54ace", size = 296677, upload-time = "2025-02-26T09:15:16.554Z" }, + { url = "https://files.pythonhosted.org/packages/69/e2/b011c38e5394c4c18fb5500778a55ec43ad6106126e74723ffaee246f56e/safetensors-0.5.3-cp38-abi3-win_amd64.whl", hash = "sha256:836cbbc320b47e80acd40e44c8682db0e8ad7123209f69b093def21ec7cafd11", size = 308878, upload-time = "2025-02-26T09:15:14.99Z" }, +] + +[[package]] +name = "scipy-stubs" +version = "1.15.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "optype" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0b/5f/35c43bd7d412add4adcd68475702571b2489b50c40b6564f808b2355e452/scipy_stubs-1.15.3.0.tar.gz", hash = "sha256:e8f76c9887461cf9424c1e2ad78ea5dac71dd4cbb383dc85f91adfe8f74d1e17", size = 275699, upload-time = "2025-05-08T16:58:35.139Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/18/ae/88f6c49dbd0cc4da0e08610019a3c78a7d390879a919411a410a1876d03a/safetensors-0.5.3-cp38-abi3-macosx_10_12_x86_64.whl", hash = "sha256:bd20eb133db8ed15b40110b7c00c6df51655a2998132193de2f75f72d99c7073", size = 436917 }, - { url = "https://files.pythonhosted.org/packages/b8/3b/11f1b4a2f5d2ab7da34ecc062b0bc301f2be024d110a6466726bec8c055c/safetensors-0.5.3-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:21d01c14ff6c415c485616b8b0bf961c46b3b343ca59110d38d744e577f9cce7", size = 418419 }, - { url = "https://files.pythonhosted.org/packages/5d/9a/add3e6fef267658075c5a41573c26d42d80c935cdc992384dfae435feaef/safetensors-0.5.3-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:11bce6164887cd491ca75c2326a113ba934be596e22b28b1742ce27b1d076467", size = 459493 }, - { url = "https://files.pythonhosted.org/packages/df/5c/bf2cae92222513cc23b3ff85c4a1bb2811a2c3583ac0f8e8d502751de934/safetensors-0.5.3-cp38-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4a243be3590bc3301c821da7a18d87224ef35cbd3e5f5727e4e0728b8172411e", size = 472400 }, - { url = "https://files.pythonhosted.org/packages/58/11/7456afb740bd45782d0f4c8e8e1bb9e572f1bf82899fb6ace58af47b4282/safetensors-0.5.3-cp38-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8bd84b12b1670a6f8e50f01e28156422a2bc07fb16fc4e98bded13039d688a0d", size = 522891 }, - { url = "https://files.pythonhosted.org/packages/57/3d/fe73a9d2ace487e7285f6e157afee2383bd1ddb911b7cb44a55cf812eae3/safetensors-0.5.3-cp38-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:391ac8cab7c829452175f871fcaf414aa1e292b5448bd02620f675a7f3e7abb9", size = 537694 }, - { url = "https://files.pythonhosted.org/packages/a6/f8/dae3421624fcc87a89d42e1898a798bc7ff72c61f38973a65d60df8f124c/safetensors-0.5.3-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cead1fa41fc54b1e61089fa57452e8834f798cb1dc7a09ba3524f1eb08e0317a", size = 471642 }, - { url = "https://files.pythonhosted.org/packages/ce/20/1fbe16f9b815f6c5a672f5b760951e20e17e43f67f231428f871909a37f6/safetensors-0.5.3-cp38-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1077f3e94182d72618357b04b5ced540ceb71c8a813d3319f1aba448e68a770d", size = 502241 }, - { url = "https://files.pythonhosted.org/packages/5f/18/8e108846b506487aa4629fe4116b27db65c3dde922de2c8e0cc1133f3f29/safetensors-0.5.3-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:799021e78287bac619c7b3f3606730a22da4cda27759ddf55d37c8db7511c74b", size = 638001 }, - { url = "https://files.pythonhosted.org/packages/82/5a/c116111d8291af6c8c8a8b40628fe833b9db97d8141c2a82359d14d9e078/safetensors-0.5.3-cp38-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:df26da01aaac504334644e1b7642fa000bfec820e7cef83aeac4e355e03195ff", size = 734013 }, - { url = "https://files.pythonhosted.org/packages/7d/ff/41fcc4d3b7de837963622e8610d998710705bbde9a8a17221d85e5d0baad/safetensors-0.5.3-cp38-abi3-musllinux_1_2_i686.whl", hash = "sha256:32c3ef2d7af8b9f52ff685ed0bc43913cdcde135089ae322ee576de93eae5135", size = 670687 }, - { url = "https://files.pythonhosted.org/packages/40/ad/2b113098e69c985a3d8fbda4b902778eae4a35b7d5188859b4a63d30c161/safetensors-0.5.3-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:37f1521be045e56fc2b54c606d4455573e717b2d887c579ee1dbba5f868ece04", size = 643147 }, - { url = "https://files.pythonhosted.org/packages/0a/0c/95aeb51d4246bd9a3242d3d8349c1112b4ee7611a4b40f0c5c93b05f001d/safetensors-0.5.3-cp38-abi3-win32.whl", hash = "sha256:cfc0ec0846dcf6763b0ed3d1846ff36008c6e7290683b61616c4b040f6a54ace", size = 296677 }, - { url = "https://files.pythonhosted.org/packages/69/e2/b011c38e5394c4c18fb5500778a55ec43ad6106126e74723ffaee246f56e/safetensors-0.5.3-cp38-abi3-win_amd64.whl", hash = "sha256:836cbbc320b47e80acd40e44c8682db0e8ad7123209f69b093def21ec7cafd11", size = 308878 }, + { url = "https://files.pythonhosted.org/packages/6c/42/cd8dc81f8060de1f14960885ad5b2d2651f41de8b93d09f3f919d6567a5a/scipy_stubs-1.15.3.0-py3-none-any.whl", hash = "sha256:a251254cf4fd6e7fb87c55c1feee92d32ddbc1f542ecdf6a0159cdb81c2fb62d", size = 459062, upload-time = "2025-05-08T16:58:33.356Z" }, ] [[package]] name = "sentry-sdk" -version = "1.44.1" +version = "2.28.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "certifi" }, { name = "urllib3" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/fd/72/85a8bc961d9160ac8c9f0a6d39dbdad21795d55c7b02a433bd0ffb75c037/sentry-sdk-1.44.1.tar.gz", hash = "sha256:24e6a53eeabffd2f95d952aa35ca52f0f4201d17f820ac9d3ff7244c665aaf68", size = 243446 } +sdist = { url = "https://files.pythonhosted.org/packages/5e/bb/6a41b2e0e9121bed4d2ec68d50568ab95c49f4744156a9bbb789c866c66d/sentry_sdk-2.28.0.tar.gz", hash = "sha256:14d2b73bc93afaf2a9412490329099e6217761cbab13b6ee8bc0e82927e1504e", size = 325052, upload-time = "2025-05-12T07:53:12.785Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b1/f8/2038661bc32579d0c11191fc1093e49db590bfb6e63d501d7995fb798d62/sentry_sdk-1.44.1-py2.py3-none-any.whl", hash = "sha256:5f75eb91d8ab6037c754a87b8501cc581b2827e923682f593bed3539ce5b3999", size = 266105 }, + { url = "https://files.pythonhosted.org/packages/9b/4e/b1575833094c088dfdef63fbca794518860fcbc8002aadf51ebe8b6a387f/sentry_sdk-2.28.0-py2.py3-none-any.whl", hash = "sha256:51496e6cb3cb625b99c8e08907c67a9112360259b0ef08470e532c3ab184a232", size = 341693, upload-time = "2025-05-12T07:53:10.882Z" }, ] [package.optional-dependencies] @@ -4939,85 +5134,126 @@ flask = [ { name = "markupsafe" }, ] +[[package]] +name = "setproctitle" +version = "1.3.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9e/af/56efe21c53ac81ac87e000b15e60b3d8104224b4313b6eacac3597bd183d/setproctitle-1.3.6.tar.gz", hash = "sha256:c9f32b96c700bb384f33f7cf07954bb609d35dd82752cef57fb2ee0968409169", size = 26889, upload-time = "2025-04-29T13:35:00.184Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/27/3b/8288d0cd969a63500dd62fc2c99ce6980f9909ccef0770ab1f86c361e0bf/setproctitle-1.3.6-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a1d856b0f4e4a33e31cdab5f50d0a14998f3a2d726a3fd5cb7c4d45a57b28d1b", size = 17412, upload-time = "2025-04-29T13:32:58.135Z" }, + { url = "https://files.pythonhosted.org/packages/39/37/43a5a3e25ca1048dbbf4db0d88d346226f5f1acd131bb8e660f4bfe2799f/setproctitle-1.3.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:50706b9c0eda55f7de18695bfeead5f28b58aa42fd5219b3b1692d554ecbc9ec", size = 11963, upload-time = "2025-04-29T13:32:59.17Z" }, + { url = "https://files.pythonhosted.org/packages/5b/47/f103c40e133154783c91a10ab08ac9fc410ed835aa85bcf7107cb882f505/setproctitle-1.3.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:af188f3305f0a65c3217c30c6d4c06891e79144076a91e8b454f14256acc7279", size = 31718, upload-time = "2025-04-29T13:33:00.36Z" }, + { url = "https://files.pythonhosted.org/packages/1f/13/7325dd1c008dd6c0ebd370ddb7505977054a87e406f142318e395031a792/setproctitle-1.3.6-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cce0ed8b3f64c71c140f0ec244e5fdf8ecf78ddf8d2e591d4a8b6aa1c1214235", size = 33027, upload-time = "2025-04-29T13:33:01.499Z" }, + { url = "https://files.pythonhosted.org/packages/0c/0a/6075bfea05a71379d77af98a9ac61163e8b6e5ef1ae58cd2b05871b2079c/setproctitle-1.3.6-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:70100e2087fe05359f249a0b5f393127b3a1819bf34dec3a3e0d4941138650c9", size = 30223, upload-time = "2025-04-29T13:33:03.259Z" }, + { url = "https://files.pythonhosted.org/packages/cc/41/fbf57ec52f4f0776193bd94334a841f0bc9d17e745f89c7790f336420c65/setproctitle-1.3.6-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1065ed36bd03a3fd4186d6c6de5f19846650b015789f72e2dea2d77be99bdca1", size = 31204, upload-time = "2025-04-29T13:33:04.455Z" }, + { url = "https://files.pythonhosted.org/packages/97/b5/f799fb7a00de29fb0ac1dfd015528dea425b9e31a8f1068a0b3df52d317f/setproctitle-1.3.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:4adf6a0013fe4e0844e3ba7583ec203ca518b9394c6cc0d3354df2bf31d1c034", size = 31181, upload-time = "2025-04-29T13:33:05.697Z" }, + { url = "https://files.pythonhosted.org/packages/b5/b7/81f101b612014ec61723436022c31146178813d6ca6b947f7b9c84e9daf4/setproctitle-1.3.6-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:eb7452849f6615871eabed6560ffedfe56bc8af31a823b6be4ce1e6ff0ab72c5", size = 30101, upload-time = "2025-04-29T13:33:07.223Z" }, + { url = "https://files.pythonhosted.org/packages/67/23/681232eed7640eab96719daa8647cc99b639e3daff5c287bd270ef179a73/setproctitle-1.3.6-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:a094b7ce455ca341b59a0f6ce6be2e11411ba6e2860b9aa3dbb37468f23338f4", size = 32438, upload-time = "2025-04-29T13:33:08.538Z" }, + { url = "https://files.pythonhosted.org/packages/19/f8/4d075a7bdc3609ac71535b849775812455e4c40aedfbf0778a6f123b1774/setproctitle-1.3.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ad1c2c2baaba62823a7f348f469a967ece0062140ca39e7a48e4bbb1f20d54c4", size = 30625, upload-time = "2025-04-29T13:33:09.707Z" }, + { url = "https://files.pythonhosted.org/packages/5f/73/a2a8259ebee166aee1ca53eead75de0e190b3ddca4f716e5c7470ebb7ef6/setproctitle-1.3.6-cp311-cp311-win32.whl", hash = "sha256:8050c01331135f77ec99d99307bfbc6519ea24d2f92964b06f3222a804a3ff1f", size = 11488, upload-time = "2025-04-29T13:33:10.953Z" }, + { url = "https://files.pythonhosted.org/packages/c9/15/52cf5e1ff0727d53704cfdde2858eaf237ce523b0b04db65faa84ff83e13/setproctitle-1.3.6-cp311-cp311-win_amd64.whl", hash = "sha256:9b73cf0fe28009a04a35bb2522e4c5b5176cc148919431dcb73fdbdfaab15781", size = 12201, upload-time = "2025-04-29T13:33:12.389Z" }, + { url = "https://files.pythonhosted.org/packages/8f/fb/99456fd94d4207c5f6c40746a048a33a52b4239cd7d9c8d4889e2210ec82/setproctitle-1.3.6-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:af44bb7a1af163806bbb679eb8432fa7b4fb6d83a5d403b541b675dcd3798638", size = 17399, upload-time = "2025-04-29T13:33:13.406Z" }, + { url = "https://files.pythonhosted.org/packages/d5/48/9699191fe6062827683c43bfa9caac33a2c89f8781dd8c7253fa3dba85fd/setproctitle-1.3.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3cca16fd055316a48f0debfcbfb6af7cea715429fc31515ab3fcac05abd527d8", size = 11966, upload-time = "2025-04-29T13:33:14.976Z" }, + { url = "https://files.pythonhosted.org/packages/33/03/b085d192b9ecb9c7ce6ad6ef30ecf4110b7f39430b58a56245569827fcf4/setproctitle-1.3.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ea002088d5554fd75e619742cefc78b84a212ba21632e59931b3501f0cfc8f67", size = 32017, upload-time = "2025-04-29T13:33:16.163Z" }, + { url = "https://files.pythonhosted.org/packages/ae/68/c53162e645816f97212002111420d1b2f75bf6d02632e37e961dc2cd6d8b/setproctitle-1.3.6-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bb465dd5825356c1191a038a86ee1b8166e3562d6e8add95eec04ab484cfb8a2", size = 33419, upload-time = "2025-04-29T13:33:18.239Z" }, + { url = "https://files.pythonhosted.org/packages/ac/0d/119a45d15a816a6cf5ccc61b19729f82620095b27a47e0a6838216a95fae/setproctitle-1.3.6-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d2c8e20487b3b73c1fa72c56f5c89430617296cd380373e7af3a538a82d4cd6d", size = 30711, upload-time = "2025-04-29T13:33:19.571Z" }, + { url = "https://files.pythonhosted.org/packages/e3/fb/5e9b5068df9e9f31a722a775a5e8322a29a638eaaa3eac5ea7f0b35e6314/setproctitle-1.3.6-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a0d6252098e98129a1decb59b46920d4eca17b0395f3d71b0d327d086fefe77d", size = 31742, upload-time = "2025-04-29T13:33:21.172Z" }, + { url = "https://files.pythonhosted.org/packages/35/88/54de1e73e8fce87d587889c7eedb48fc4ee2bbe4e4ca6331690d03024f86/setproctitle-1.3.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:cf355fbf0d4275d86f9f57be705d8e5eaa7f8ddb12b24ced2ea6cbd68fdb14dc", size = 31925, upload-time = "2025-04-29T13:33:22.427Z" }, + { url = "https://files.pythonhosted.org/packages/f3/01/65948d7badd66e63e3db247b923143da142790fa293830fdecf832712c2d/setproctitle-1.3.6-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:e288f8a162d663916060beb5e8165a8551312b08efee9cf68302687471a6545d", size = 30981, upload-time = "2025-04-29T13:33:23.739Z" }, + { url = "https://files.pythonhosted.org/packages/22/20/c495e61786f1d38d5dc340b9d9077fee9be3dfc7e89f515afe12e1526dbc/setproctitle-1.3.6-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:b2e54f4a2dc6edf0f5ea5b1d0a608d2af3dcb5aa8c8eeab9c8841b23e1b054fe", size = 33209, upload-time = "2025-04-29T13:33:24.915Z" }, + { url = "https://files.pythonhosted.org/packages/98/3f/a457b8550fbd34d5b482fe20b8376b529e76bf1fbf9a474a6d9a641ab4ad/setproctitle-1.3.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b6f4abde9a2946f57e8daaf1160b2351bcf64274ef539e6675c1d945dbd75e2a", size = 31587, upload-time = "2025-04-29T13:33:26.123Z" }, + { url = "https://files.pythonhosted.org/packages/44/fe/743517340e5a635e3f1c4310baea20c16c66202f96a6f4cead222ffd6d84/setproctitle-1.3.6-cp312-cp312-win32.whl", hash = "sha256:db608db98ccc21248370d30044a60843b3f0f3d34781ceeea67067c508cd5a28", size = 11487, upload-time = "2025-04-29T13:33:27.403Z" }, + { url = "https://files.pythonhosted.org/packages/60/9a/d88f1c1f0f4efff1bd29d9233583ee341114dda7d9613941453984849674/setproctitle-1.3.6-cp312-cp312-win_amd64.whl", hash = "sha256:082413db8a96b1f021088e8ec23f0a61fec352e649aba20881895815388b66d3", size = 12208, upload-time = "2025-04-29T13:33:28.852Z" }, +] + [[package]] name = "setuptools" -version = "78.1.0" +version = "80.9.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a9/5a/0db4da3bc908df06e5efae42b44e75c81dd52716e10192ff36d0c1c8e379/setuptools-78.1.0.tar.gz", hash = "sha256:18fd474d4a82a5f83dac888df697af65afa82dec7323d09c3e37d1f14288da54", size = 1367827 } +sdist = { url = "https://files.pythonhosted.org/packages/18/5d/3bf57dcd21979b887f014ea83c24ae194cfcd12b9e0fda66b957c69d1fca/setuptools-80.9.0.tar.gz", hash = "sha256:f36b47402ecde768dbfafc46e8e4207b4360c654f1f3bb84475f0a28628fb19c", size = 1319958, upload-time = "2025-05-27T00:56:51.443Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/54/21/f43f0a1fa8b06b32812e0975981f4677d28e0f3271601dc88ac5a5b83220/setuptools-78.1.0-py3-none-any.whl", hash = "sha256:3e386e96793c8702ae83d17b853fb93d3e09ef82ec62722e61da5cd22376dcd8", size = 1256108 }, + { url = "https://files.pythonhosted.org/packages/a3/dc/17031897dae0efacfea57dfd3a82fdd2a2aeb58e0ff71b77b87e44edc772/setuptools-80.9.0-py3-none-any.whl", hash = "sha256:062d34222ad13e0cc312a4c02d73f059e86a4acbfbdea8f8f76b28c99f306922", size = 1201486, upload-time = "2025-05-27T00:56:49.664Z" }, ] [[package]] name = "shapely" -version = "2.1.0" +version = "2.1.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "numpy" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/fb/fe/3b0d2f828ffaceadcdcb51b75b9c62d98e62dd95ce575278de35f24a1c20/shapely-2.1.0.tar.gz", hash = "sha256:2cbe90e86fa8fc3ca8af6ffb00a77b246b918c7cf28677b7c21489b678f6b02e", size = 313617 } +sdist = { url = "https://files.pythonhosted.org/packages/ca/3c/2da625233f4e605155926566c0e7ea8dda361877f48e8b1655e53456f252/shapely-2.1.1.tar.gz", hash = "sha256:500621967f2ffe9642454808009044c21e5b35db89ce69f8a2042c2ffd0e2772", size = 315422, upload-time = "2025-05-19T11:04:41.265Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/1c/37/ae448f06f363ff3dfe4bae890abd842c4e3e9edaf01245dbc9b97008c9e6/shapely-2.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c8323031ef7c1bdda7a92d5ddbc7b6b62702e73ba37e9a8ccc8da99ec2c0b87c", size = 1820974 }, - { url = "https://files.pythonhosted.org/packages/78/da/ea2a898e93c6953c5eef353a0e1781a0013a1352f2b90aa9ab0b800e0c75/shapely-2.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4da7c6cd748d86ec6aace99ad17129d30954ccf5e73e9911cdb5f0fa9658b4f8", size = 1624137 }, - { url = "https://files.pythonhosted.org/packages/64/4a/f903f82f0fabcd3f43ea2e8132cabda079119247330a9fe58018c39c4e22/shapely-2.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f0cdf85ff80831137067e7a237085a3ee72c225dba1b30beef87f7d396cf02b", size = 2957161 }, - { url = "https://files.pythonhosted.org/packages/92/07/3e2738c542d73182066196b8ce99388cb537d19e300e428d50b1537e3b21/shapely-2.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:41f2be5d79aac39886f23000727cf02001aef3af8810176c29ee12cdc3ef3a50", size = 3078530 }, - { url = "https://files.pythonhosted.org/packages/82/08/32210e63d8f8af9142d37c2433ece4846862cdac91a0fe66f040780a71bd/shapely-2.1.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:21a4515009f56d7a159cf5c2554264e82f56405b4721f9a422cb397237c5dca8", size = 3902208 }, - { url = "https://files.pythonhosted.org/packages/19/0e/0abb5225f8a32fbdb615476637038a7d2db40c0af46d1bb3a08b869bee39/shapely-2.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:15cebc323cec2cb6b2eaa310fdfc621f6dbbfaf6bde336d13838fcea76c885a9", size = 4082863 }, - { url = "https://files.pythonhosted.org/packages/f8/1b/7cd816fd388108c872ab7e2930180b02d0c34891213f361e4a66e5e032f2/shapely-2.1.0-cp311-cp311-win32.whl", hash = "sha256:cad51b7a5c8f82f5640472944a74f0f239123dde9a63042b3c5ea311739b7d20", size = 1527488 }, - { url = "https://files.pythonhosted.org/packages/fd/28/7bb5b1944d4002d4b2f967762018500381c3b532f98e456bbda40c3ded68/shapely-2.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:d4005309dde8658e287ad9c435c81877f6a95a9419b932fa7a1f34b120f270ae", size = 1708311 }, - { url = "https://files.pythonhosted.org/packages/4e/d1/6a9371ec39d3ef08e13225594e6c55b045209629afd9e6d403204507c2a8/shapely-2.1.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:53e7ee8bd8609cf12ee6dce01ea5affe676976cf7049315751d53d8db6d2b4b2", size = 1830732 }, - { url = "https://files.pythonhosted.org/packages/32/87/799e3e48be7ce848c08509b94d2180f4ddb02e846e3c62d0af33da4d78d3/shapely-2.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3cab20b665d26dbec0b380e15749bea720885a481fa7b1eedc88195d4a98cfa4", size = 1638404 }, - { url = "https://files.pythonhosted.org/packages/85/00/6665d77f9dd09478ab0993b8bc31668aec4fd3e5f1ddd1b28dd5830e47be/shapely-2.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f4a38b39a09340273c3c92b3b9a374272a12cc7e468aeeea22c1c46217a03e5c", size = 2945316 }, - { url = "https://files.pythonhosted.org/packages/34/49/738e07d10bbc67cae0dcfe5a484c6e518a517f4f90550dda2adf3a78b9f2/shapely-2.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:edaec656bdd9b71278b98e6f77c464b1c3b2daa9eace78012ff0f0b4b5b15b04", size = 3063099 }, - { url = "https://files.pythonhosted.org/packages/88/b8/138098674559362ab29f152bff3b6630de423378fbb0324812742433a4ef/shapely-2.1.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:c8a732ddd9b25e7a54aa748e7df8fd704e23e5d5d35b7d376d80bffbfc376d04", size = 3887873 }, - { url = "https://files.pythonhosted.org/packages/67/a8/fdae7c2db009244991d86f4d2ca09d2f5ccc9d41c312c3b1ee1404dc55da/shapely-2.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:9c93693ad8adfdc9138a5a2d42da02da94f728dd2e82d2f0f442f10e25027f5f", size = 4067004 }, - { url = "https://files.pythonhosted.org/packages/ed/78/17e17d91b489019379df3ee1afc4bd39787b232aaa1d540f7d376f0280b7/shapely-2.1.0-cp312-cp312-win32.whl", hash = "sha256:d8ac6604eefe807e71a908524de23a37920133a1729fe3a4dfe0ed82c044cbf4", size = 1527366 }, - { url = "https://files.pythonhosted.org/packages/b8/bd/9249bd6dda948441e25e4fb14cbbb5205146b0fff12c66b19331f1ff2141/shapely-2.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:f4f47e631aa4f9ec5576eac546eb3f38802e2f82aeb0552f9612cb9a14ece1db", size = 1708265 }, + { url = "https://files.pythonhosted.org/packages/19/97/2df985b1e03f90c503796ad5ecd3d9ed305123b64d4ccb54616b30295b29/shapely-2.1.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:587a1aa72bc858fab9b8c20427b5f6027b7cbc92743b8e2c73b9de55aa71c7a7", size = 1819368, upload-time = "2025-05-19T11:03:55.937Z" }, + { url = "https://files.pythonhosted.org/packages/56/17/504518860370f0a28908b18864f43d72f03581e2b6680540ca668f07aa42/shapely-2.1.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9fa5c53b0791a4b998f9ad84aad456c988600757a96b0a05e14bba10cebaaaea", size = 1625362, upload-time = "2025-05-19T11:03:57.06Z" }, + { url = "https://files.pythonhosted.org/packages/36/a1/9677337d729b79fce1ef3296aac6b8ef4743419086f669e8a8070eff8f40/shapely-2.1.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aabecd038841ab5310d23495253f01c2a82a3aedae5ab9ca489be214aa458aa7", size = 2999005, upload-time = "2025-05-19T11:03:58.692Z" }, + { url = "https://files.pythonhosted.org/packages/a2/17/e09357274699c6e012bbb5a8ea14765a4d5860bb658df1931c9f90d53bd3/shapely-2.1.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:586f6aee1edec04e16227517a866df3e9a2e43c1f635efc32978bb3dc9c63753", size = 3108489, upload-time = "2025-05-19T11:04:00.059Z" }, + { url = "https://files.pythonhosted.org/packages/17/5d/93a6c37c4b4e9955ad40834f42b17260ca74ecf36df2e81bb14d12221b90/shapely-2.1.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:b9878b9e37ad26c72aada8de0c9cfe418d9e2ff36992a1693b7f65a075b28647", size = 3945727, upload-time = "2025-05-19T11:04:01.786Z" }, + { url = "https://files.pythonhosted.org/packages/a3/1a/ad696648f16fd82dd6bfcca0b3b8fbafa7aacc13431c7fc4c9b49e481681/shapely-2.1.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d9a531c48f289ba355e37b134e98e28c557ff13965d4653a5228d0f42a09aed0", size = 4109311, upload-time = "2025-05-19T11:04:03.134Z" }, + { url = "https://files.pythonhosted.org/packages/d4/38/150dd245beab179ec0d4472bf6799bf18f21b1efbef59ac87de3377dbf1c/shapely-2.1.1-cp311-cp311-win32.whl", hash = "sha256:4866de2673a971820c75c0167b1f1cd8fb76f2d641101c23d3ca021ad0449bab", size = 1522982, upload-time = "2025-05-19T11:04:05.217Z" }, + { url = "https://files.pythonhosted.org/packages/93/5b/842022c00fbb051083c1c85430f3bb55565b7fd2d775f4f398c0ba8052ce/shapely-2.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:20a9d79958b3d6c70d8a886b250047ea32ff40489d7abb47d01498c704557a93", size = 1703872, upload-time = "2025-05-19T11:04:06.791Z" }, + { url = "https://files.pythonhosted.org/packages/fb/64/9544dc07dfe80a2d489060791300827c941c451e2910f7364b19607ea352/shapely-2.1.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:2827365b58bf98efb60affc94a8e01c56dd1995a80aabe4b701465d86dcbba43", size = 1833021, upload-time = "2025-05-19T11:04:08.022Z" }, + { url = "https://files.pythonhosted.org/packages/07/aa/fb5f545e72e89b6a0f04a0effda144f5be956c9c312c7d4e00dfddbddbcf/shapely-2.1.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a9c551f7fa7f1e917af2347fe983f21f212863f1d04f08eece01e9c275903fad", size = 1643018, upload-time = "2025-05-19T11:04:09.343Z" }, + { url = "https://files.pythonhosted.org/packages/03/46/61e03edba81de729f09d880ce7ae5c1af873a0814206bbfb4402ab5c3388/shapely-2.1.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:78dec4d4fbe7b1db8dc36de3031767e7ece5911fb7782bc9e95c5cdec58fb1e9", size = 2986417, upload-time = "2025-05-19T11:04:10.56Z" }, + { url = "https://files.pythonhosted.org/packages/1f/1e/83ec268ab8254a446b4178b45616ab5822d7b9d2b7eb6e27cf0b82f45601/shapely-2.1.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:872d3c0a7b8b37da0e23d80496ec5973c4692920b90de9f502b5beb994bbaaef", size = 3098224, upload-time = "2025-05-19T11:04:11.903Z" }, + { url = "https://files.pythonhosted.org/packages/f1/44/0c21e7717c243e067c9ef8fa9126de24239f8345a5bba9280f7bb9935959/shapely-2.1.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2e2b9125ebfbc28ecf5353511de62f75a8515ae9470521c9a693e4bb9fbe0cf1", size = 3925982, upload-time = "2025-05-19T11:04:13.224Z" }, + { url = "https://files.pythonhosted.org/packages/15/50/d3b4e15fefc103a0eb13d83bad5f65cd6e07a5d8b2ae920e767932a247d1/shapely-2.1.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:4b96cea171b3d7f6786976a0520f178c42792897653ecca0c5422fb1e6946e6d", size = 4089122, upload-time = "2025-05-19T11:04:14.477Z" }, + { url = "https://files.pythonhosted.org/packages/bd/05/9a68f27fc6110baeedeeebc14fd86e73fa38738c5b741302408fb6355577/shapely-2.1.1-cp312-cp312-win32.whl", hash = "sha256:39dca52201e02996df02e447f729da97cfb6ff41a03cb50f5547f19d02905af8", size = 1522437, upload-time = "2025-05-19T11:04:16.203Z" }, + { url = "https://files.pythonhosted.org/packages/bc/e9/a4560e12b9338842a1f82c9016d2543eaa084fce30a1ca11991143086b57/shapely-2.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:13d643256f81d55a50013eff6321142781cf777eb6a9e207c2c9e6315ba6044a", size = 1703479, upload-time = "2025-05-19T11:04:18.497Z" }, ] [[package]] name = "shellingham" version = "1.5.4" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/58/15/8b3609fd3830ef7b27b655beb4b4e9c62313a4e8da8c676e142cc210d58e/shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de", size = 10310 } +sdist = { url = "https://files.pythonhosted.org/packages/58/15/8b3609fd3830ef7b27b655beb4b4e9c62313a4e8da8c676e142cc210d58e/shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de", size = 10310, upload-time = "2023-10-24T04:13:40.426Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686", size = 9755 }, + { url = "https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686", size = 9755, upload-time = "2023-10-24T04:13:38.866Z" }, ] [[package]] name = "six" version = "1.17.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031 } +sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031, upload-time = "2024-12-04T17:35:28.174Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050 }, + { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" }, +] + +[[package]] +name = "smmap" +version = "5.0.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/44/cd/a040c4b3119bbe532e5b0732286f805445375489fceaec1f48306068ee3b/smmap-5.0.2.tar.gz", hash = "sha256:26ea65a03958fa0c8a1c7e8c7a58fdc77221b8910f6be2131affade476898ad5", size = 22329, upload-time = "2025-01-02T07:14:40.909Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/be/d09147ad1ec7934636ad912901c5fd7667e1c858e19d355237db0d0cd5e4/smmap-5.0.2-py3-none-any.whl", hash = "sha256:b30115f0def7d7531d22a0fb6502488d879e75b260a9db4d0819cfb25403af5e", size = 24303, upload-time = "2025-01-02T07:14:38.724Z" }, ] [[package]] name = "sniffio" version = "1.3.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372 } +sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372, upload-time = "2024-02-25T23:20:04.057Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235 }, + { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload-time = "2024-02-25T23:20:01.196Z" }, ] [[package]] name = "socksio" version = "1.0.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f8/5c/48a7d9495be3d1c651198fd99dbb6ce190e2274d0f28b9051307bdec6b85/socksio-1.0.0.tar.gz", hash = "sha256:f88beb3da5b5c38b9890469de67d0cb0f9d494b78b106ca1845f96c10b91c4ac", size = 19055 } +sdist = { url = "https://files.pythonhosted.org/packages/f8/5c/48a7d9495be3d1c651198fd99dbb6ce190e2274d0f28b9051307bdec6b85/socksio-1.0.0.tar.gz", hash = "sha256:f88beb3da5b5c38b9890469de67d0cb0f9d494b78b106ca1845f96c10b91c4ac", size = 19055, upload-time = "2020-04-17T15:50:34.664Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/37/c3/6eeb6034408dac0fa653d126c9204ade96b819c936e136c5e8a6897eee9c/socksio-1.0.0-py3-none-any.whl", hash = "sha256:95dc1f15f9b34e8d7b16f06d74b8ccf48f609af32ab33c608d08761c5dcbb1f3", size = 12763 }, + { url = "https://files.pythonhosted.org/packages/37/c3/6eeb6034408dac0fa653d126c9204ade96b819c936e136c5e8a6897eee9c/socksio-1.0.0-py3-none-any.whl", hash = "sha256:95dc1f15f9b34e8d7b16f06d74b8ccf48f609af32ab33c608d08761c5dcbb1f3", size = 12763, upload-time = "2020-04-17T15:50:31.878Z" }, ] [[package]] name = "soupsieve" -version = "2.6" +version = "2.7" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d7/ce/fbaeed4f9fb8b2daa961f90591662df6a86c1abf25c548329a86920aedfb/soupsieve-2.6.tar.gz", hash = "sha256:e2e68417777af359ec65daac1057404a3c8a5455bb8abc36f1a9866ab1a51abb", size = 101569 } +sdist = { url = "https://files.pythonhosted.org/packages/3f/f4/4a80cd6ef364b2e8b65b15816a843c0980f7a5a2b4dc701fc574952aa19f/soupsieve-2.7.tar.gz", hash = "sha256:ad282f9b6926286d2ead4750552c8a6142bc4c783fd66b0293547c8fe6ae126a", size = 103418, upload-time = "2025-04-20T18:50:08.518Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d1/c2/fe97d779f3ef3b15f05c94a2f1e3d21732574ed441687474db9d342a7315/soupsieve-2.6-py3-none-any.whl", hash = "sha256:e72c4ff06e4fb6e4b5a9f0f55fe6e81514581fca1515028625d0f299c602ccc9", size = 36186 }, + { url = "https://files.pythonhosted.org/packages/e7/9c/0e6afc12c269578be5c0c1c9f4b49a8d32770a080260c333ac04cc1c832d/soupsieve-2.7-py3-none-any.whl", hash = "sha256:6e60cc5c1ffaf1cebcc12e8188320b72071e922c2e897f737cadce79ad5d30c4", size = 36677, upload-time = "2025-04-20T18:50:07.196Z" }, ] [[package]] @@ -5028,25 +5264,34 @@ dependencies = [ { name = "greenlet", marker = "platform_machine == 'AMD64' or platform_machine == 'WIN32' or platform_machine == 'aarch64' or platform_machine == 'amd64' or platform_machine == 'ppc64le' or platform_machine == 'win32' or platform_machine == 'x86_64'" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/36/48/4f190a83525f5cefefa44f6adc9e6386c4de5218d686c27eda92eb1f5424/sqlalchemy-2.0.35.tar.gz", hash = "sha256:e11d7ea4d24f0a262bccf9a7cd6284c976c5369dac21db237cff59586045ab9f", size = 9562798 } +sdist = { url = "https://files.pythonhosted.org/packages/36/48/4f190a83525f5cefefa44f6adc9e6386c4de5218d686c27eda92eb1f5424/sqlalchemy-2.0.35.tar.gz", hash = "sha256:e11d7ea4d24f0a262bccf9a7cd6284c976c5369dac21db237cff59586045ab9f", size = 9562798, upload-time = "2024-09-16T20:30:05.964Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c3/46/9215a35bf98c3a2528e987791e6180eb51624d2c7d5cb8e2d96a6450b657/SQLAlchemy-2.0.35-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e21f66748ab725ade40fa7af8ec8b5019c68ab00b929f6643e1b1af461eddb60", size = 2091274, upload-time = "2024-09-16T21:07:13.344Z" }, + { url = "https://files.pythonhosted.org/packages/1e/69/919673c5101a0c633658d58b11b454b251ca82300941fba801201434755d/SQLAlchemy-2.0.35-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8a6219108a15fc6d24de499d0d515c7235c617b2540d97116b663dade1a54d62", size = 2081672, upload-time = "2024-09-16T21:07:14.807Z" }, + { url = "https://files.pythonhosted.org/packages/67/ea/a6b0597cbda12796be2302153369dbbe90573fdab3bc4885f8efac499247/SQLAlchemy-2.0.35-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:042622a5306c23b972192283f4e22372da3b8ddf5f7aac1cc5d9c9b222ab3ff6", size = 3200083, upload-time = "2024-09-16T22:45:15.766Z" }, + { url = "https://files.pythonhosted.org/packages/8c/d6/97bdc8d714fb21762f2092511f380f18cdb2d985d516071fa925bb433a90/SQLAlchemy-2.0.35-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:627dee0c280eea91aed87b20a1f849e9ae2fe719d52cbf847c0e0ea34464b3f7", size = 3200080, upload-time = "2024-09-16T21:18:19.033Z" }, + { url = "https://files.pythonhosted.org/packages/87/d2/8c2adaf2ade4f6f1b725acd0b0be9210bb6a2df41024729a8eec6a86fe5a/SQLAlchemy-2.0.35-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:4fdcd72a789c1c31ed242fd8c1bcd9ea186a98ee8e5408a50e610edfef980d71", size = 3137108, upload-time = "2024-09-16T22:45:19.167Z" }, + { url = "https://files.pythonhosted.org/packages/7e/ae/ea05d0bfa8f2b25ae34591895147152854fc950f491c4ce362ae06035db8/SQLAlchemy-2.0.35-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:89b64cd8898a3a6f642db4eb7b26d1b28a497d4022eccd7717ca066823e9fb01", size = 3157437, upload-time = "2024-09-16T21:18:21.988Z" }, + { url = "https://files.pythonhosted.org/packages/fe/5d/8ad6df01398388a766163d27960b3365f1bbd8bb7b05b5cad321a8b69b25/SQLAlchemy-2.0.35-cp311-cp311-win32.whl", hash = "sha256:6a93c5a0dfe8d34951e8a6f499a9479ffb9258123551fa007fc708ae2ac2bc5e", size = 2061935, upload-time = "2024-09-16T20:54:10.564Z" }, + { url = "https://files.pythonhosted.org/packages/ff/68/8557efc0c32c8e2c147cb6512237448b8ed594a57cd015fda67f8e56bb3f/SQLAlchemy-2.0.35-cp311-cp311-win_amd64.whl", hash = "sha256:c68fe3fcde03920c46697585620135b4ecfdfc1ed23e75cc2c2ae9f8502c10b8", size = 2087281, upload-time = "2024-09-16T20:54:13.429Z" }, + { url = "https://files.pythonhosted.org/packages/2f/2b/fff87e6db0da31212c98bbc445f83fb608ea92b96bda3f3f10e373bac76c/SQLAlchemy-2.0.35-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:eb60b026d8ad0c97917cb81d3662d0b39b8ff1335e3fabb24984c6acd0c900a2", size = 2089790, upload-time = "2024-09-16T21:07:16.161Z" }, + { url = "https://files.pythonhosted.org/packages/68/92/4bb761bd82764d5827bf6b6095168c40fb5dbbd23670203aef2f96ba6bc6/SQLAlchemy-2.0.35-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6921ee01caf375363be5e9ae70d08ce7ca9d7e0e8983183080211a062d299468", size = 2080266, upload-time = "2024-09-16T21:07:18.277Z" }, + { url = "https://files.pythonhosted.org/packages/22/46/068a65db6dc253c6f25a7598d99e0a1d60b14f661f9d09ef6c73c718fa4e/SQLAlchemy-2.0.35-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8cdf1a0dbe5ced887a9b127da4ffd7354e9c1a3b9bb330dce84df6b70ccb3a8d", size = 3229760, upload-time = "2024-09-16T22:45:20.863Z" }, + { url = "https://files.pythonhosted.org/packages/6e/36/59830dafe40dda592304debd4cd86e583f63472f3a62c9e2695a5795e786/SQLAlchemy-2.0.35-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:93a71c8601e823236ac0e5d087e4f397874a421017b3318fd92c0b14acf2b6db", size = 3240649, upload-time = "2024-09-16T21:18:23.996Z" }, + { url = "https://files.pythonhosted.org/packages/00/50/844c50c6996f9c7f000c959dd1a7436a6c94e449ee113046a1d19e470089/SQLAlchemy-2.0.35-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e04b622bb8a88f10e439084486f2f6349bf4d50605ac3e445869c7ea5cf0fa8c", size = 3176138, upload-time = "2024-09-16T22:45:22.518Z" }, + { url = "https://files.pythonhosted.org/packages/df/d2/336b18cac68eecb67de474fc15c85f13be4e615c6f5bae87ea38c6734ce0/SQLAlchemy-2.0.35-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1b56961e2d31389aaadf4906d453859f35302b4eb818d34a26fab72596076bb8", size = 3202753, upload-time = "2024-09-16T21:18:25.966Z" }, + { url = "https://files.pythonhosted.org/packages/f0/f3/ee1e62fabdc10910b5ef720ae08e59bc785f26652876af3a50b89b97b412/SQLAlchemy-2.0.35-cp312-cp312-win32.whl", hash = "sha256:0f9f3f9a3763b9c4deb8c5d09c4cc52ffe49f9876af41cc1b2ad0138878453cf", size = 2060113, upload-time = "2024-09-16T20:54:15.16Z" }, + { url = "https://files.pythonhosted.org/packages/60/63/a3cef44a52979169d884f3583d0640e64b3c28122c096474a1d7cfcaf1f3/SQLAlchemy-2.0.35-cp312-cp312-win_amd64.whl", hash = "sha256:25b0f63e7fcc2a6290cb5f7f5b4fc4047843504983a28856ce9b35d8f7de03cc", size = 2085839, upload-time = "2024-09-16T20:54:17.11Z" }, + { url = "https://files.pythonhosted.org/packages/0e/c6/33c706449cdd92b1b6d756b247761e27d32230fd6b2de5f44c4c3e5632b2/SQLAlchemy-2.0.35-py3-none-any.whl", hash = "sha256:2ab3f0336c0387662ce6221ad30ab3a5e6499aab01b9790879b6578fd9b8faa1", size = 1881276, upload-time = "2024-09-16T23:14:28.324Z" }, +] + +[[package]] +name = "sqlglot" +version = "26.24.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/4d/f7/0fa9f9f2477c4e3d8e28b0f5e066f0e72343c29c8a302ee6a77579e8986b/sqlglot-26.24.0.tar.gz", hash = "sha256:e778ca9cb685b4fc34b59d50432c20f463c63ec90d0448fa91afa7f320a88518", size = 5371208, upload-time = "2025-05-30T08:44:06.516Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c3/46/9215a35bf98c3a2528e987791e6180eb51624d2c7d5cb8e2d96a6450b657/SQLAlchemy-2.0.35-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e21f66748ab725ade40fa7af8ec8b5019c68ab00b929f6643e1b1af461eddb60", size = 2091274 }, - { url = "https://files.pythonhosted.org/packages/1e/69/919673c5101a0c633658d58b11b454b251ca82300941fba801201434755d/SQLAlchemy-2.0.35-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8a6219108a15fc6d24de499d0d515c7235c617b2540d97116b663dade1a54d62", size = 2081672 }, - { url = "https://files.pythonhosted.org/packages/67/ea/a6b0597cbda12796be2302153369dbbe90573fdab3bc4885f8efac499247/SQLAlchemy-2.0.35-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:042622a5306c23b972192283f4e22372da3b8ddf5f7aac1cc5d9c9b222ab3ff6", size = 3200083 }, - { url = "https://files.pythonhosted.org/packages/8c/d6/97bdc8d714fb21762f2092511f380f18cdb2d985d516071fa925bb433a90/SQLAlchemy-2.0.35-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:627dee0c280eea91aed87b20a1f849e9ae2fe719d52cbf847c0e0ea34464b3f7", size = 3200080 }, - { url = "https://files.pythonhosted.org/packages/87/d2/8c2adaf2ade4f6f1b725acd0b0be9210bb6a2df41024729a8eec6a86fe5a/SQLAlchemy-2.0.35-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:4fdcd72a789c1c31ed242fd8c1bcd9ea186a98ee8e5408a50e610edfef980d71", size = 3137108 }, - { url = "https://files.pythonhosted.org/packages/7e/ae/ea05d0bfa8f2b25ae34591895147152854fc950f491c4ce362ae06035db8/SQLAlchemy-2.0.35-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:89b64cd8898a3a6f642db4eb7b26d1b28a497d4022eccd7717ca066823e9fb01", size = 3157437 }, - { url = "https://files.pythonhosted.org/packages/fe/5d/8ad6df01398388a766163d27960b3365f1bbd8bb7b05b5cad321a8b69b25/SQLAlchemy-2.0.35-cp311-cp311-win32.whl", hash = "sha256:6a93c5a0dfe8d34951e8a6f499a9479ffb9258123551fa007fc708ae2ac2bc5e", size = 2061935 }, - { url = "https://files.pythonhosted.org/packages/ff/68/8557efc0c32c8e2c147cb6512237448b8ed594a57cd015fda67f8e56bb3f/SQLAlchemy-2.0.35-cp311-cp311-win_amd64.whl", hash = "sha256:c68fe3fcde03920c46697585620135b4ecfdfc1ed23e75cc2c2ae9f8502c10b8", size = 2087281 }, - { url = "https://files.pythonhosted.org/packages/2f/2b/fff87e6db0da31212c98bbc445f83fb608ea92b96bda3f3f10e373bac76c/SQLAlchemy-2.0.35-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:eb60b026d8ad0c97917cb81d3662d0b39b8ff1335e3fabb24984c6acd0c900a2", size = 2089790 }, - { url = "https://files.pythonhosted.org/packages/68/92/4bb761bd82764d5827bf6b6095168c40fb5dbbd23670203aef2f96ba6bc6/SQLAlchemy-2.0.35-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6921ee01caf375363be5e9ae70d08ce7ca9d7e0e8983183080211a062d299468", size = 2080266 }, - { url = "https://files.pythonhosted.org/packages/22/46/068a65db6dc253c6f25a7598d99e0a1d60b14f661f9d09ef6c73c718fa4e/SQLAlchemy-2.0.35-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8cdf1a0dbe5ced887a9b127da4ffd7354e9c1a3b9bb330dce84df6b70ccb3a8d", size = 3229760 }, - { url = "https://files.pythonhosted.org/packages/6e/36/59830dafe40dda592304debd4cd86e583f63472f3a62c9e2695a5795e786/SQLAlchemy-2.0.35-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:93a71c8601e823236ac0e5d087e4f397874a421017b3318fd92c0b14acf2b6db", size = 3240649 }, - { url = "https://files.pythonhosted.org/packages/00/50/844c50c6996f9c7f000c959dd1a7436a6c94e449ee113046a1d19e470089/SQLAlchemy-2.0.35-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e04b622bb8a88f10e439084486f2f6349bf4d50605ac3e445869c7ea5cf0fa8c", size = 3176138 }, - { url = "https://files.pythonhosted.org/packages/df/d2/336b18cac68eecb67de474fc15c85f13be4e615c6f5bae87ea38c6734ce0/SQLAlchemy-2.0.35-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1b56961e2d31389aaadf4906d453859f35302b4eb818d34a26fab72596076bb8", size = 3202753 }, - { url = "https://files.pythonhosted.org/packages/f0/f3/ee1e62fabdc10910b5ef720ae08e59bc785f26652876af3a50b89b97b412/SQLAlchemy-2.0.35-cp312-cp312-win32.whl", hash = "sha256:0f9f3f9a3763b9c4deb8c5d09c4cc52ffe49f9876af41cc1b2ad0138878453cf", size = 2060113 }, - { url = "https://files.pythonhosted.org/packages/60/63/a3cef44a52979169d884f3583d0640e64b3c28122c096474a1d7cfcaf1f3/SQLAlchemy-2.0.35-cp312-cp312-win_amd64.whl", hash = "sha256:25b0f63e7fcc2a6290cb5f7f5b4fc4047843504983a28856ce9b35d8f7de03cc", size = 2085839 }, - { url = "https://files.pythonhosted.org/packages/0e/c6/33c706449cdd92b1b6d756b247761e27d32230fd6b2de5f44c4c3e5632b2/SQLAlchemy-2.0.35-py3-none-any.whl", hash = "sha256:2ab3f0336c0387662ce6221ad30ab3a5e6499aab01b9790879b6578fd9b8faa1", size = 1881276 }, + { url = "https://files.pythonhosted.org/packages/b2/11/6995759d913d714ff443478f5865b2616dbcd32b12764d02df9550d7a61e/sqlglot-26.24.0-py3-none-any.whl", hash = "sha256:81f7e47bb1b4b396c564359f47c7c1aee476575a0cadf84dc35f7189cab87f82", size = 464043, upload-time = "2025-05-30T08:44:00.801Z" }, ] [[package]] @@ -5056,9 +5301,9 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/78/53/c3a36690a923706e7ac841f649c64f5108889ab1ec44218dac45771f252a/starlette-0.41.0.tar.gz", hash = "sha256:39cbd8768b107d68bfe1ff1672b38a2c38b49777de46d2a592841d58e3bf7c2a", size = 2573755 } +sdist = { url = "https://files.pythonhosted.org/packages/78/53/c3a36690a923706e7ac841f649c64f5108889ab1ec44218dac45771f252a/starlette-0.41.0.tar.gz", hash = "sha256:39cbd8768b107d68bfe1ff1672b38a2c38b49777de46d2a592841d58e3bf7c2a", size = 2573755, upload-time = "2024-10-15T17:32:04.224Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/35/c6/a4443bfabf5629129512ca0e07866c4c3c094079ba4e9b2551006927253c/starlette-0.41.0-py3-none-any.whl", hash = "sha256:a0193a3c413ebc9c78bff1c3546a45bb8c8bcb4a84cae8747d650a65bd37210a", size = 73216 }, + { url = "https://files.pythonhosted.org/packages/35/c6/a4443bfabf5629129512ca0e07866c4c3c094079ba4e9b2551006927253c/starlette-0.41.0-py3-none-any.whl", hash = "sha256:a0193a3c413ebc9c78bff1c3546a45bb8c8bcb4a84cae8747d650a65bd37210a", size = 73216, upload-time = "2024-10-15T17:32:02.931Z" }, ] [[package]] @@ -5070,9 +5315,9 @@ dependencies = [ { name = "python-dateutil" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a8/af/94cd4925c8a80b4c06bdef60226c04566973f6e2982957d2eabeecb2d5ca/storage3-0.8.2.tar.gz", hash = "sha256:db05d3fe8fb73bd30c814c4c4749664f37a5dfc78b629e8c058ef558c2b89f5a", size = 9041 } +sdist = { url = "https://files.pythonhosted.org/packages/a8/af/94cd4925c8a80b4c06bdef60226c04566973f6e2982957d2eabeecb2d5ca/storage3-0.8.2.tar.gz", hash = "sha256:db05d3fe8fb73bd30c814c4c4749664f37a5dfc78b629e8c058ef558c2b89f5a", size = 9041, upload-time = "2024-10-18T07:05:40.219Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c8/67/7d281ba69b3ba3359f528bb0a1cac9d87896938d80119451123e829b3820/storage3-0.8.2-py3-none-any.whl", hash = "sha256:f2e995b18c77a2a9265d1a33047d43e4d6abb11eb3ca5067959f68281c305de3", size = 16230 }, + { url = "https://files.pythonhosted.org/packages/c8/67/7d281ba69b3ba3359f528bb0a1cac9d87896938d80119451123e829b3820/storage3-0.8.2-py3-none-any.whl", hash = "sha256:f2e995b18c77a2a9265d1a33047d43e4d6abb11eb3ca5067959f68281c305de3", size = 16230, upload-time = "2024-10-18T07:05:38.408Z" }, ] [[package]] @@ -5088,9 +5333,9 @@ dependencies = [ { name = "supafunc" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/80/46/0846eae977d7e067e73960d880a3457e2a87b1ec7467ff3bc5365b318df7/supabase-2.8.1.tar.gz", hash = "sha256:711c70e6acd9e2ff48ca0dc0b1bb70c01c25378cc5189ec9f5ed9655b30bc41d", size = 13955 } +sdist = { url = "https://files.pythonhosted.org/packages/80/46/0846eae977d7e067e73960d880a3457e2a87b1ec7467ff3bc5365b318df7/supabase-2.8.1.tar.gz", hash = "sha256:711c70e6acd9e2ff48ca0dc0b1bb70c01c25378cc5189ec9f5ed9655b30bc41d", size = 13955, upload-time = "2024-09-30T16:03:53.548Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/15/ca/7f1dfcd9dfff2cb56ce063b3c8e4c29ae43e50102f039d5196cbed8d51b8/supabase-2.8.1-py3-none-any.whl", hash = "sha256:dfa8bef89b54129093521d5bba2136ff765baf67cd76d8ad0aa4984d61a7815c", size = 16589 }, + { url = "https://files.pythonhosted.org/packages/15/ca/7f1dfcd9dfff2cb56ce063b3c8e4c29ae43e50102f039d5196cbed8d51b8/supabase-2.8.1-py3-none-any.whl", hash = "sha256:dfa8bef89b54129093521d5bba2136ff765baf67cd76d8ad0aa4984d61a7815c", size = 16589, upload-time = "2024-09-30T16:03:51.737Z" }, ] [[package]] @@ -5100,21 +5345,21 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "httpx", extra = ["http2"] }, ] -sdist = { url = "https://files.pythonhosted.org/packages/85/28/c808bfd80c996cbf0ba5de6714edf2e2f68637f50058f6b9373f49b82a70/supafunc-0.6.2.tar.gz", hash = "sha256:c7dfa20db7182f7fe4ae436e94e05c06cd7ed98d697fed75d68c7b9792822adc", size = 3902 } +sdist = { url = "https://files.pythonhosted.org/packages/85/28/c808bfd80c996cbf0ba5de6714edf2e2f68637f50058f6b9373f49b82a70/supafunc-0.6.2.tar.gz", hash = "sha256:c7dfa20db7182f7fe4ae436e94e05c06cd7ed98d697fed75d68c7b9792822adc", size = 3902, upload-time = "2024-10-18T07:06:39.038Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/18/91/cb7a31cf250ee66dfd40cca2c7c36eede7e1d8e3183f99865d14438c66a7/supafunc-0.6.2-py3-none-any.whl", hash = "sha256:101b30616b0a1ce8cf938eca1df362fa4cf1deacb0271f53ebbd674190fb0da5", size = 6622 }, + { url = "https://files.pythonhosted.org/packages/18/91/cb7a31cf250ee66dfd40cca2c7c36eede7e1d8e3183f99865d14438c66a7/supafunc-0.6.2-py3-none-any.whl", hash = "sha256:101b30616b0a1ce8cf938eca1df362fa4cf1deacb0271f53ebbd674190fb0da5", size = 6622, upload-time = "2024-10-18T07:06:37.782Z" }, ] [[package]] name = "sympy" -version = "1.13.3" +version = "1.14.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "mpmath" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/11/8a/5a7fd6284fa8caac23a26c9ddf9c30485a48169344b4bd3b0f02fef1890f/sympy-1.13.3.tar.gz", hash = "sha256:b27fd2c6530e0ab39e275fc9b683895367e51d5da91baa8d3d64db2565fec4d9", size = 7533196 } +sdist = { url = "https://files.pythonhosted.org/packages/83/d3/803453b36afefb7c2bb238361cd4ae6125a569b4db67cd9e79846ba2d68c/sympy-1.14.0.tar.gz", hash = "sha256:d3d3fe8df1e5a0b42f0e7bdf50541697dbe7d23746e894990c030e2b05e72517", size = 7793921, upload-time = "2025-04-27T18:05:01.611Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/99/ff/c87e0622b1dadea79d2fb0b25ade9ed98954c9033722eb707053d310d4f3/sympy-1.13.3-py3-none-any.whl", hash = "sha256:54612cf55a62755ee71824ce692986f23c88ffa77207b30c1368eda4a7060f73", size = 6189483 }, + { url = "https://files.pythonhosted.org/packages/a2/09/77d55d46fd61b4a135c444fc97158ef34a095e5681d0a6c10b75bf356191/sympy-1.14.0-py3-none-any.whl", hash = "sha256:e091cc3e99d2141a0ba2847328f5479b05d94a6635cb96148ccb3f34671bd8f5", size = 6299353, upload-time = "2025-04-27T18:04:59.103Z" }, ] [[package]] @@ -5132,15 +5377,15 @@ dependencies = [ { name = "six" }, { name = "urllib3" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/0f/ed/5bdd906ec9d2dbae3909525dbb7602558c377e0cbcdddb6405d2d0d3f1af/tablestore-6.1.0.tar.gz", hash = "sha256:bfe6a3e0fe88a230729723c357f4a46b8869a06a4b936db20692ed587a721c1c", size = 135690 } +sdist = { url = "https://files.pythonhosted.org/packages/0f/ed/5bdd906ec9d2dbae3909525dbb7602558c377e0cbcdddb6405d2d0d3f1af/tablestore-6.1.0.tar.gz", hash = "sha256:bfe6a3e0fe88a230729723c357f4a46b8869a06a4b936db20692ed587a721c1c", size = 135690, upload-time = "2024-12-20T07:38:37.428Z" } [[package]] name = "tabulate" version = "0.9.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ec/fe/802052aecb21e3797b8f7902564ab6ea0d60ff8ca23952079064155d1ae1/tabulate-0.9.0.tar.gz", hash = "sha256:0095b12bf5966de529c0feb1fa08671671b3368eec77d7ef7ab114be2c068b3c", size = 81090 } +sdist = { url = "https://files.pythonhosted.org/packages/ec/fe/802052aecb21e3797b8f7902564ab6ea0d60ff8ca23952079064155d1ae1/tabulate-0.9.0.tar.gz", hash = "sha256:0095b12bf5966de529c0feb1fa08671671b3368eec77d7ef7ab114be2c068b3c", size = 81090, upload-time = "2022-10-06T17:21:48.54Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/40/44/4a5f08c96eb108af5cb50b41f76142f0afa346dfa99d5296fe7202a11854/tabulate-0.9.0-py3-none-any.whl", hash = "sha256:024ca478df22e9340661486f85298cff5f6dcdba14f3813e8830015b9ed1948f", size = 35252 }, + { url = "https://files.pythonhosted.org/packages/40/44/4a5f08c96eb108af5cb50b41f76142f0afa346dfa99d5296fe7202a11854/tabulate-0.9.0-py3-none-any.whl", hash = "sha256:024ca478df22e9340661486f85298cff5f6dcdba14f3813e8830015b9ed1948f", size = 35252, upload-time = "2022-10-06T17:21:44.262Z" }, ] [[package]] @@ -5153,9 +5398,9 @@ dependencies = [ { name = "numpy" }, { name = "tqdm" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/b6/3f/9487f703edb5b8be51ada52b675b4b2fcd507399946aeab8c10028f75265/tcvdb_text-1.1.1.tar.gz", hash = "sha256:db36b5d7b640b194ae72c0c429718c9613b8ef9de5fffb9d510aba5be75ff1cb", size = 57859792 } +sdist = { url = "https://files.pythonhosted.org/packages/b6/3f/9487f703edb5b8be51ada52b675b4b2fcd507399946aeab8c10028f75265/tcvdb_text-1.1.1.tar.gz", hash = "sha256:db36b5d7b640b194ae72c0c429718c9613b8ef9de5fffb9d510aba5be75ff1cb", size = 57859792, upload-time = "2025-02-07T11:08:17.586Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/76/d3/8c8799802676bc6c4696bed7ca7b01a3a5b6ab080ed959e5a4925640e01b/tcvdb_text-1.1.1-py3-none-any.whl", hash = "sha256:981eb2323c0668129942c066de05e8f0d2165be36f567877906646dea07d17a9", size = 59535083 }, + { url = "https://files.pythonhosted.org/packages/76/d3/8c8799802676bc6c4696bed7ca7b01a3a5b6ab080ed959e5a4925640e01b/tcvdb_text-1.1.1-py3-none-any.whl", hash = "sha256:981eb2323c0668129942c066de05e8f0d2165be36f567877906646dea07d17a9", size = 59535083, upload-time = "2025-02-07T11:07:59.66Z" }, ] [[package]] @@ -5173,18 +5418,18 @@ dependencies = [ { name = "ujson" }, { name = "urllib3" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/19/ec/c80579aff1539257aafcf8dc3f3c13630171f299d65b33b68440e166f27c/tcvectordb-1.6.4.tar.gz", hash = "sha256:6fb18e15ccc6744d5147e9bbd781f84df3d66112de7d9cc615878b3f72d3a29a", size = 75188 } +sdist = { url = "https://files.pythonhosted.org/packages/19/ec/c80579aff1539257aafcf8dc3f3c13630171f299d65b33b68440e166f27c/tcvectordb-1.6.4.tar.gz", hash = "sha256:6fb18e15ccc6744d5147e9bbd781f84df3d66112de7d9cc615878b3f72d3a29a", size = 75188, upload-time = "2025-03-05T09:14:19.925Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/68/bf/f38d9f629324ecffca8fe934e8df47e1233a9021b0739447e59e9fb248f9/tcvectordb-1.6.4-py3-none-any.whl", hash = "sha256:06ef13e7edb4575b04615065fc90e1a28374e318ada305f3786629aec5c9318a", size = 88917 }, + { url = "https://files.pythonhosted.org/packages/68/bf/f38d9f629324ecffca8fe934e8df47e1233a9021b0739447e59e9fb248f9/tcvectordb-1.6.4-py3-none-any.whl", hash = "sha256:06ef13e7edb4575b04615065fc90e1a28374e318ada305f3786629aec5c9318a", size = 88917, upload-time = "2025-03-05T09:14:17.494Z" }, ] [[package]] name = "tenacity" version = "9.1.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/0a/d4/2b0cd0fe285e14b36db076e78c93766ff1d529d70408bd1d2a5a84f1d929/tenacity-9.1.2.tar.gz", hash = "sha256:1169d376c297e7de388d18b4481760d478b0e99a777cad3a9c86e556f4b697cb", size = 48036 } +sdist = { url = "https://files.pythonhosted.org/packages/0a/d4/2b0cd0fe285e14b36db076e78c93766ff1d529d70408bd1d2a5a84f1d929/tenacity-9.1.2.tar.gz", hash = "sha256:1169d376c297e7de388d18b4481760d478b0e99a777cad3a9c86e556f4b697cb", size = 48036, upload-time = "2025-04-02T08:25:09.966Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e5/30/643397144bfbfec6f6ef821f36f33e57d35946c44a2352d3c9f0ae847619/tenacity-9.1.2-py3-none-any.whl", hash = "sha256:f77bf36710d8b73a50b2dd155c97b870017ad21afe6ab300326b0371b3b05138", size = 28248 }, + { url = "https://files.pythonhosted.org/packages/e5/30/643397144bfbfec6f6ef821f36f33e57d35946c44a2352d3c9f0ae847619/tenacity-9.1.2-py3-none-any.whl", hash = "sha256:f77bf36710d8b73a50b2dd155c97b870017ad21afe6ab300326b0371b3b05138", size = 28248, upload-time = "2025-04-02T08:25:07.678Z" }, ] [[package]] @@ -5194,106 +5439,96 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "numpy" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/1a/98/ab324fdfbbf064186ca621e21aa3871ddf886ecb78358a9864509241e802/tidb_vector-0.0.9.tar.gz", hash = "sha256:e10680872532808e1bcffa7a92dd2b05bb65d63982f833edb3c6cd590dec7709", size = 16948 } +sdist = { url = "https://files.pythonhosted.org/packages/1a/98/ab324fdfbbf064186ca621e21aa3871ddf886ecb78358a9864509241e802/tidb_vector-0.0.9.tar.gz", hash = "sha256:e10680872532808e1bcffa7a92dd2b05bb65d63982f833edb3c6cd590dec7709", size = 16948, upload-time = "2024-05-08T07:54:36.955Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/5d/bb/0f3b7b4d31537e90f4dd01f50fa58daef48807c789c1c1bdd610204ff103/tidb_vector-0.0.9-py3-none-any.whl", hash = "sha256:db060ee1c981326d3882d0810e0b8b57811f278668f9381168997b360c4296c2", size = 17026 }, + { url = "https://files.pythonhosted.org/packages/5d/bb/0f3b7b4d31537e90f4dd01f50fa58daef48807c789c1c1bdd610204ff103/tidb_vector-0.0.9-py3-none-any.whl", hash = "sha256:db060ee1c981326d3882d0810e0b8b57811f278668f9381168997b360c4296c2", size = 17026, upload-time = "2024-05-08T07:54:34.849Z" }, ] [[package]] name = "tiktoken" -version = "0.8.0" +version = "0.9.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "regex" }, { name = "requests" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/37/02/576ff3a6639e755c4f70997b2d315f56d6d71e0d046f4fb64cb81a3fb099/tiktoken-0.8.0.tar.gz", hash = "sha256:9ccbb2740f24542534369c5635cfd9b2b3c2490754a78ac8831d99f89f94eeb2", size = 35107 } +sdist = { url = "https://files.pythonhosted.org/packages/ea/cf/756fedf6981e82897f2d570dd25fa597eb3f4459068ae0572d7e888cfd6f/tiktoken-0.9.0.tar.gz", hash = "sha256:d02a5ca6a938e0490e1ff957bc48c8b078c88cb83977be1625b1fd8aac792c5d", size = 35991, upload-time = "2025-02-14T06:03:01.003Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f6/1e/ca48e7bfeeccaf76f3a501bd84db1fa28b3c22c9d1a1f41af9fb7579c5f6/tiktoken-0.8.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d622d8011e6d6f239297efa42a2657043aaed06c4f68833550cac9e9bc723ef1", size = 1039700 }, - { url = "https://files.pythonhosted.org/packages/8c/f8/f0101d98d661b34534769c3818f5af631e59c36ac6d07268fbfc89e539ce/tiktoken-0.8.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2efaf6199717b4485031b4d6edb94075e4d79177a172f38dd934d911b588d54a", size = 982413 }, - { url = "https://files.pythonhosted.org/packages/ac/3c/2b95391d9bd520a73830469f80a96e3790e6c0a5ac2444f80f20b4b31051/tiktoken-0.8.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5637e425ce1fc49cf716d88df3092048359a4b3bbb7da762840426e937ada06d", size = 1144242 }, - { url = "https://files.pythonhosted.org/packages/01/c4/c4a4360de845217b6aa9709c15773484b50479f36bb50419c443204e5de9/tiktoken-0.8.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fb0e352d1dbe15aba082883058b3cce9e48d33101bdaac1eccf66424feb5b47", size = 1176588 }, - { url = "https://files.pythonhosted.org/packages/f8/a3/ef984e976822cd6c2227c854f74d2e60cf4cd6fbfca46251199914746f78/tiktoken-0.8.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:56edfefe896c8f10aba372ab5706b9e3558e78db39dd497c940b47bf228bc419", size = 1237261 }, - { url = "https://files.pythonhosted.org/packages/1e/86/eea2309dc258fb86c7d9b10db536434fc16420feaa3b6113df18b23db7c2/tiktoken-0.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:326624128590def898775b722ccc327e90b073714227175ea8febbc920ac0a99", size = 884537 }, - { url = "https://files.pythonhosted.org/packages/c1/22/34b2e136a6f4af186b6640cbfd6f93400783c9ef6cd550d9eab80628d9de/tiktoken-0.8.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:881839cfeae051b3628d9823b2e56b5cc93a9e2efb435f4cf15f17dc45f21586", size = 1039357 }, - { url = "https://files.pythonhosted.org/packages/04/d2/c793cf49c20f5855fd6ce05d080c0537d7418f22c58e71f392d5e8c8dbf7/tiktoken-0.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fe9399bdc3f29d428f16a2f86c3c8ec20be3eac5f53693ce4980371c3245729b", size = 982616 }, - { url = "https://files.pythonhosted.org/packages/b3/a1/79846e5ef911cd5d75c844de3fa496a10c91b4b5f550aad695c5df153d72/tiktoken-0.8.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9a58deb7075d5b69237a3ff4bb51a726670419db6ea62bdcd8bd80c78497d7ab", size = 1144011 }, - { url = "https://files.pythonhosted.org/packages/26/32/e0e3a859136e95c85a572e4806dc58bf1ddf651108ae8b97d5f3ebe1a244/tiktoken-0.8.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2908c0d043a7d03ebd80347266b0e58440bdef5564f84f4d29fb235b5df3b04", size = 1175432 }, - { url = "https://files.pythonhosted.org/packages/c7/89/926b66e9025b97e9fbabeaa59048a736fe3c3e4530a204109571104f921c/tiktoken-0.8.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:294440d21a2a51e12d4238e68a5972095534fe9878be57d905c476017bff99fc", size = 1236576 }, - { url = "https://files.pythonhosted.org/packages/45/e2/39d4aa02a52bba73b2cd21ba4533c84425ff8786cc63c511d68c8897376e/tiktoken-0.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:d8f3192733ac4d77977432947d563d7e1b310b96497acd3c196c9bddb36ed9db", size = 883824 }, + { url = "https://files.pythonhosted.org/packages/4d/ae/4613a59a2a48e761c5161237fc850eb470b4bb93696db89da51b79a871f1/tiktoken-0.9.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:f32cc56168eac4851109e9b5d327637f15fd662aa30dd79f964b7c39fbadd26e", size = 1065987, upload-time = "2025-02-14T06:02:14.174Z" }, + { url = "https://files.pythonhosted.org/packages/3f/86/55d9d1f5b5a7e1164d0f1538a85529b5fcba2b105f92db3622e5d7de6522/tiktoken-0.9.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:45556bc41241e5294063508caf901bf92ba52d8ef9222023f83d2483a3055348", size = 1009155, upload-time = "2025-02-14T06:02:15.384Z" }, + { url = "https://files.pythonhosted.org/packages/03/58/01fb6240df083b7c1916d1dcb024e2b761213c95d576e9f780dfb5625a76/tiktoken-0.9.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:03935988a91d6d3216e2ec7c645afbb3d870b37bcb67ada1943ec48678e7ee33", size = 1142898, upload-time = "2025-02-14T06:02:16.666Z" }, + { url = "https://files.pythonhosted.org/packages/b1/73/41591c525680cd460a6becf56c9b17468d3711b1df242c53d2c7b2183d16/tiktoken-0.9.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8b3d80aad8d2c6b9238fc1a5524542087c52b860b10cbf952429ffb714bc1136", size = 1197535, upload-time = "2025-02-14T06:02:18.595Z" }, + { url = "https://files.pythonhosted.org/packages/7d/7c/1069f25521c8f01a1a182f362e5c8e0337907fae91b368b7da9c3e39b810/tiktoken-0.9.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b2a21133be05dc116b1d0372af051cd2c6aa1d2188250c9b553f9fa49301b336", size = 1259548, upload-time = "2025-02-14T06:02:20.729Z" }, + { url = "https://files.pythonhosted.org/packages/6f/07/c67ad1724b8e14e2b4c8cca04b15da158733ac60136879131db05dda7c30/tiktoken-0.9.0-cp311-cp311-win_amd64.whl", hash = "sha256:11a20e67fdf58b0e2dea7b8654a288e481bb4fc0289d3ad21291f8d0849915fb", size = 893895, upload-time = "2025-02-14T06:02:22.67Z" }, + { url = "https://files.pythonhosted.org/packages/cf/e5/21ff33ecfa2101c1bb0f9b6df750553bd873b7fb532ce2cb276ff40b197f/tiktoken-0.9.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:e88f121c1c22b726649ce67c089b90ddda8b9662545a8aeb03cfef15967ddd03", size = 1065073, upload-time = "2025-02-14T06:02:24.768Z" }, + { url = "https://files.pythonhosted.org/packages/8e/03/a95e7b4863ee9ceec1c55983e4cc9558bcfd8f4f80e19c4f8a99642f697d/tiktoken-0.9.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a6600660f2f72369acb13a57fb3e212434ed38b045fd8cc6cdd74947b4b5d210", size = 1008075, upload-time = "2025-02-14T06:02:26.92Z" }, + { url = "https://files.pythonhosted.org/packages/40/10/1305bb02a561595088235a513ec73e50b32e74364fef4de519da69bc8010/tiktoken-0.9.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:95e811743b5dfa74f4b227927ed86cbc57cad4df859cb3b643be797914e41794", size = 1140754, upload-time = "2025-02-14T06:02:28.124Z" }, + { url = "https://files.pythonhosted.org/packages/1b/40/da42522018ca496432ffd02793c3a72a739ac04c3794a4914570c9bb2925/tiktoken-0.9.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:99376e1370d59bcf6935c933cb9ba64adc29033b7e73f5f7569f3aad86552b22", size = 1196678, upload-time = "2025-02-14T06:02:29.845Z" }, + { url = "https://files.pythonhosted.org/packages/5c/41/1e59dddaae270ba20187ceb8aa52c75b24ffc09f547233991d5fd822838b/tiktoken-0.9.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:badb947c32739fb6ddde173e14885fb3de4d32ab9d8c591cbd013c22b4c31dd2", size = 1259283, upload-time = "2025-02-14T06:02:33.838Z" }, + { url = "https://files.pythonhosted.org/packages/5b/64/b16003419a1d7728d0d8c0d56a4c24325e7b10a21a9dd1fc0f7115c02f0a/tiktoken-0.9.0-cp312-cp312-win_amd64.whl", hash = "sha256:5a62d7a25225bafed786a524c1b9f0910a1128f4232615bf3f8257a73aaa3b16", size = 894897, upload-time = "2025-02-14T06:02:36.265Z" }, ] [[package]] name = "tokenizers" -version = "0.15.2" +version = "0.21.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "huggingface-hub" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c0/44/625db94e91c6196b6574359fa70bfe28e8eabf57a1b894f8f0ec69727fd1/tokenizers-0.15.2.tar.gz", hash = "sha256:e6e9c6e019dd5484be5beafc775ae6c925f4c69a3487040ed09b45e13df2cb91", size = 320256 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/73/11/933d68d395f5486d935e1c15da80bc96bf3f48595652069d19e0e9894386/tokenizers-0.15.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:89cd1cb93e4b12ff39bb2d626ad77e35209de9309a71e4d3d4672667b4b256e7", size = 2578922 }, - { url = "https://files.pythonhosted.org/packages/5f/4f/a4c12cc058a899c1caaa1e689c3df9a698e20e891d4005aa6ec2174a9339/tokenizers-0.15.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:cfed5c64e5be23d7ee0f0e98081a25c2a46b0b77ce99a4f0605b1ec43dd481fa", size = 2412317 }, - { url = "https://files.pythonhosted.org/packages/e9/13/b86ea87b7e3b4a2ca154220dc4eb19a56a3864ec03e9630d15d1bac10da1/tokenizers-0.15.2-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:a907d76dcfda37023ba203ab4ceeb21bc5683436ebefbd895a0841fd52f6f6f2", size = 3643051 }, - { url = "https://files.pythonhosted.org/packages/0f/23/e4985657ea42ad432d6dc2100b2687e70a6bae730f1f8c52f81d9e6ccf3a/tokenizers-0.15.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:20ea60479de6fc7b8ae756b4b097572372d7e4032e2521c1bbf3d90c90a99ff0", size = 3534327 }, - { url = "https://files.pythonhosted.org/packages/34/d5/e1ad46939d6de48d41bbd8b302f87ecde79847855210e75517a832b29490/tokenizers-0.15.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:48e2b9335be2bc0171df9281385c2ed06a15f5cf121c44094338306ab7b33f2c", size = 3398296 }, - { url = "https://files.pythonhosted.org/packages/e7/d1/4d319a035f819af3290ec5a09482ad659d9d2a0aea33890fb5720ce81841/tokenizers-0.15.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:112a1dd436d2cc06e6ffdc0b06d55ac019a35a63afd26475205cb4b1bf0bfbff", size = 3927353 }, - { url = "https://files.pythonhosted.org/packages/e5/39/facfca8e598126a0001d4295e6b1ee670d241aa6f4fcdd97016065b43c5d/tokenizers-0.15.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4620cca5c2817177ee8706f860364cc3a8845bc1e291aaf661fb899e5d1c45b0", size = 4030091 }, - { url = "https://files.pythonhosted.org/packages/15/0b/c09b2c0dc688c82adadaa0d5080983de3ce920f4a5cbadb7eaa5302ad251/tokenizers-0.15.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ccd73a82751c523b3fc31ff8194702e4af4db21dc20e55b30ecc2079c5d43cb7", size = 3577167 }, - { url = "https://files.pythonhosted.org/packages/07/3b/d8e60712e509a6f5d01bf0eb4470452b72277be4883656206d4ccd7e02de/tokenizers-0.15.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:107089f135b4ae7817affe6264f8c7a5c5b4fd9a90f9439ed495f54fcea56fb4", size = 9683503 }, - { url = "https://files.pythonhosted.org/packages/c0/61/1c26c8e54af9bab32743e0484601a60738f33797f91040da2a4104f07e70/tokenizers-0.15.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:0ff110ecc57b7aa4a594396525a3451ad70988e517237fe91c540997c4e50e29", size = 9996038 }, - { url = "https://files.pythonhosted.org/packages/d1/54/451e96d8514b1afbef955f7420e1180e015c3f4eb085ad38189c0e83ee87/tokenizers-0.15.2-cp311-none-win32.whl", hash = "sha256:6d76f00f5c32da36c61f41c58346a4fa7f0a61be02f4301fd30ad59834977cc3", size = 2013591 }, - { url = "https://files.pythonhosted.org/packages/c1/02/40725eebedea8175918bd59ab80b2174d6ef3b3ef9ac8ec996e84c38d3ca/tokenizers-0.15.2-cp311-none-win_amd64.whl", hash = "sha256:cc90102ed17271cf0a1262babe5939e0134b3890345d11a19c3145184b706055", size = 2192797 }, - { url = "https://files.pythonhosted.org/packages/ae/ca/ea4b5aa70d4d26f2d05620c265b07b5a249157767c1673f5753b8bfc7db1/tokenizers-0.15.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:f86593c18d2e6248e72fb91c77d413a815153b8ea4e31f7cd443bdf28e467670", size = 2574444 }, - { url = "https://files.pythonhosted.org/packages/f9/99/5a55a9b6e2db274c0969ad57d989d02efae90f9e558983a561c9b2b7ea1a/tokenizers-0.15.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0774bccc6608eca23eb9d620196687c8b2360624619623cf4ba9dc9bd53e8b51", size = 2411608 }, - { url = "https://files.pythonhosted.org/packages/82/cc/29bb3a25c06b90ce82bb20ef074011481de5c44413a1e1eb10cfd93080fb/tokenizers-0.15.2-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:d0222c5b7c9b26c0b4822a82f6a7011de0a9d3060e1da176f66274b70f846b98", size = 3652367 }, - { url = "https://files.pythonhosted.org/packages/c0/ae/f6a974be9b2e1615f3de3cc9e4fc2897a86357400801c58143c67cbbad2e/tokenizers-0.15.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3835738be1de66624fff2f4f6f6684775da4e9c00bde053be7564cbf3545cc66", size = 3529509 }, - { url = "https://files.pythonhosted.org/packages/d6/42/340b91f675b494c4ecc0a256c5dd88b4003dbfde05afff90b970738fdfb4/tokenizers-0.15.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0143e7d9dcd811855c1ce1ab9bf5d96d29bf5e528fd6c7824d0465741e8c10fd", size = 3396516 }, - { url = "https://files.pythonhosted.org/packages/6f/b2/8a965abc17fff309eb06e98ce429a19a5e04f731a669a6113b9e182f8a79/tokenizers-0.15.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:db35825f6d54215f6b6009a7ff3eedee0848c99a6271c870d2826fbbedf31a38", size = 3918811 }, - { url = "https://files.pythonhosted.org/packages/6c/16/dad7b4aa6e34a395aef7ae7b010d8b5ebefdf3df81510de53d7f17d2f0fc/tokenizers-0.15.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3f5e64b0389a2be47091d8cc53c87859783b837ea1a06edd9d8e04004df55a5c", size = 4025494 }, - { url = "https://files.pythonhosted.org/packages/f6/de/3707df0c1d7bf55e6a4dba724700353bfee8e292fdd8ccfe93416549124d/tokenizers-0.15.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9e0480c452217edd35eca56fafe2029fb4d368b7c0475f8dfa3c5c9c400a7456", size = 3575314 }, - { url = "https://files.pythonhosted.org/packages/2e/dd/7b8da304d152bb46f13bc2ba5bd545480ab6ce39d94a53eef07f7624d235/tokenizers-0.15.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:a33ab881c8fe70474980577e033d0bc9a27b7ab8272896e500708b212995d834", size = 9682779 }, - { url = "https://files.pythonhosted.org/packages/07/aa/66e8a81e07a791ca6ee9d74ee6de1ffbcd3985149f13aeb530bd409baba0/tokenizers-0.15.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a308a607ca9de2c64c1b9ba79ec9a403969715a1b8ba5f998a676826f1a7039d", size = 9995614 }, - { url = "https://files.pythonhosted.org/packages/bf/e1/aed3bc98785c54bd26bf6dd3d2f54cc00de33e8b1f922a23131372eedec8/tokenizers-0.15.2-cp312-none-win32.whl", hash = "sha256:b8fcfa81bcb9447df582c5bc96a031e6df4da2a774b8080d4f02c0c16b42be0b", size = 2011030 }, - { url = "https://files.pythonhosted.org/packages/c9/ea/5800f4941a713b2feed955b6a256aacc1ca68a6699916d2668622c075d38/tokenizers-0.15.2-cp312-none-win_amd64.whl", hash = "sha256:38d7ab43c6825abfc0b661d95f39c7f8af2449364f01d331f3b51c94dcff7221", size = 2180523 }, +sdist = { url = "https://files.pythonhosted.org/packages/92/76/5ac0c97f1117b91b7eb7323dcd61af80d72f790b4df71249a7850c195f30/tokenizers-0.21.1.tar.gz", hash = "sha256:a1bb04dc5b448985f86ecd4b05407f5a8d97cb2c0532199b2a302a604a0165ab", size = 343256, upload-time = "2025-03-13T10:51:18.189Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a5/1f/328aee25f9115bf04262e8b4e5a2050b7b7cf44b59c74e982db7270c7f30/tokenizers-0.21.1-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:e78e413e9e668ad790a29456e677d9d3aa50a9ad311a40905d6861ba7692cf41", size = 2780767, upload-time = "2025-03-13T10:51:09.459Z" }, + { url = "https://files.pythonhosted.org/packages/ae/1a/4526797f3719b0287853f12c5ad563a9be09d446c44ac784cdd7c50f76ab/tokenizers-0.21.1-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:cd51cd0a91ecc801633829fcd1fda9cf8682ed3477c6243b9a095539de4aecf3", size = 2650555, upload-time = "2025-03-13T10:51:07.692Z" }, + { url = "https://files.pythonhosted.org/packages/4d/7a/a209b29f971a9fdc1da86f917fe4524564924db50d13f0724feed37b2a4d/tokenizers-0.21.1-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:28da6b72d4fb14ee200a1bd386ff74ade8992d7f725f2bde2c495a9a98cf4d9f", size = 2937541, upload-time = "2025-03-13T10:50:56.679Z" }, + { url = "https://files.pythonhosted.org/packages/3c/1e/b788b50ffc6191e0b1fc2b0d49df8cff16fe415302e5ceb89f619d12c5bc/tokenizers-0.21.1-cp39-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:34d8cfde551c9916cb92014e040806122295a6800914bab5865deb85623931cf", size = 2819058, upload-time = "2025-03-13T10:50:59.525Z" }, + { url = "https://files.pythonhosted.org/packages/36/aa/3626dfa09a0ecc5b57a8c58eeaeb7dd7ca9a37ad9dd681edab5acd55764c/tokenizers-0.21.1-cp39-abi3-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aaa852d23e125b73d283c98f007e06d4595732104b65402f46e8ef24b588d9f8", size = 3133278, upload-time = "2025-03-13T10:51:04.678Z" }, + { url = "https://files.pythonhosted.org/packages/a4/4d/8fbc203838b3d26269f944a89459d94c858f5b3f9a9b6ee9728cdcf69161/tokenizers-0.21.1-cp39-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a21a15d5c8e603331b8a59548bbe113564136dc0f5ad8306dd5033459a226da0", size = 3144253, upload-time = "2025-03-13T10:51:01.261Z" }, + { url = "https://files.pythonhosted.org/packages/d8/1b/2bd062adeb7c7511b847b32e356024980c0ffcf35f28947792c2d8ad2288/tokenizers-0.21.1-cp39-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2fdbd4c067c60a0ac7eca14b6bd18a5bebace54eb757c706b47ea93204f7a37c", size = 3398225, upload-time = "2025-03-13T10:51:03.243Z" }, + { url = "https://files.pythonhosted.org/packages/8a/63/38be071b0c8e06840bc6046991636bcb30c27f6bb1e670f4f4bc87cf49cc/tokenizers-0.21.1-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2dd9a0061e403546f7377df940e866c3e678d7d4e9643d0461ea442b4f89e61a", size = 3038874, upload-time = "2025-03-13T10:51:06.235Z" }, + { url = "https://files.pythonhosted.org/packages/ec/83/afa94193c09246417c23a3c75a8a0a96bf44ab5630a3015538d0c316dd4b/tokenizers-0.21.1-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:db9484aeb2e200c43b915a1a0150ea885e35f357a5a8fabf7373af333dcc8dbf", size = 9014448, upload-time = "2025-03-13T10:51:10.927Z" }, + { url = "https://files.pythonhosted.org/packages/ae/b3/0e1a37d4f84c0f014d43701c11eb8072704f6efe8d8fc2dcdb79c47d76de/tokenizers-0.21.1-cp39-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:ed248ab5279e601a30a4d67bdb897ecbe955a50f1e7bb62bd99f07dd11c2f5b6", size = 8937877, upload-time = "2025-03-13T10:51:12.688Z" }, + { url = "https://files.pythonhosted.org/packages/ac/33/ff08f50e6d615eb180a4a328c65907feb6ded0b8f990ec923969759dc379/tokenizers-0.21.1-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:9ac78b12e541d4ce67b4dfd970e44c060a2147b9b2a21f509566d556a509c67d", size = 9186645, upload-time = "2025-03-13T10:51:14.723Z" }, + { url = "https://files.pythonhosted.org/packages/5f/aa/8ae85f69a9f6012c6f8011c6f4aa1c96154c816e9eea2e1b758601157833/tokenizers-0.21.1-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:e5a69c1a4496b81a5ee5d2c1f3f7fbdf95e90a0196101b0ee89ed9956b8a168f", size = 9384380, upload-time = "2025-03-13T10:51:16.526Z" }, + { url = "https://files.pythonhosted.org/packages/e8/5b/a5d98c89f747455e8b7a9504910c865d5e51da55e825a7ae641fb5ff0a58/tokenizers-0.21.1-cp39-abi3-win32.whl", hash = "sha256:1039a3a5734944e09de1d48761ade94e00d0fa760c0e0551151d4dd851ba63e3", size = 2239506, upload-time = "2025-03-13T10:51:20.643Z" }, + { url = "https://files.pythonhosted.org/packages/e6/b6/072a8e053ae600dcc2ac0da81a23548e3b523301a442a6ca900e92ac35be/tokenizers-0.21.1-cp39-abi3-win_amd64.whl", hash = "sha256:0f0dcbcc9f6e13e675a66d7a5f2f225a736745ce484c1a4e07476a89ccdad382", size = 2435481, upload-time = "2025-03-13T10:51:19.243Z" }, ] [[package]] name = "toml" version = "0.10.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/be/ba/1f744cdc819428fc6b5084ec34d9b30660f6f9daaf70eead706e3203ec3c/toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f", size = 22253 } +sdist = { url = "https://files.pythonhosted.org/packages/be/ba/1f744cdc819428fc6b5084ec34d9b30660f6f9daaf70eead706e3203ec3c/toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f", size = 22253, upload-time = "2020-11-01T01:40:22.204Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/44/6f/7120676b6d73228c96e17f1f794d8ab046fc910d781c8d151120c3f1569e/toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b", size = 16588 }, + { url = "https://files.pythonhosted.org/packages/44/6f/7120676b6d73228c96e17f1f794d8ab046fc910d781c8d151120c3f1569e/toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b", size = 16588, upload-time = "2020-11-01T01:40:20.672Z" }, ] [[package]] name = "tomli" version = "2.2.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/18/87/302344fed471e44a87289cf4967697d07e532f2421fdaf868a303cbae4ff/tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff", size = 17175 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/43/ca/75707e6efa2b37c77dadb324ae7d9571cb424e61ea73fad7c56c2d14527f/tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249", size = 131077 }, - { url = "https://files.pythonhosted.org/packages/c7/16/51ae563a8615d472fdbffc43a3f3d46588c264ac4f024f63f01283becfbb/tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6", size = 123429 }, - { url = "https://files.pythonhosted.org/packages/f1/dd/4f6cd1e7b160041db83c694abc78e100473c15d54620083dbd5aae7b990e/tomli-2.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a", size = 226067 }, - { url = "https://files.pythonhosted.org/packages/a9/6b/c54ede5dc70d648cc6361eaf429304b02f2871a345bbdd51e993d6cdf550/tomli-2.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee", size = 236030 }, - { url = "https://files.pythonhosted.org/packages/1f/47/999514fa49cfaf7a92c805a86c3c43f4215621855d151b61c602abb38091/tomli-2.2.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e", size = 240898 }, - { url = "https://files.pythonhosted.org/packages/73/41/0a01279a7ae09ee1573b423318e7934674ce06eb33f50936655071d81a24/tomli-2.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4", size = 229894 }, - { url = "https://files.pythonhosted.org/packages/55/18/5d8bc5b0a0362311ce4d18830a5d28943667599a60d20118074ea1b01bb7/tomli-2.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106", size = 245319 }, - { url = "https://files.pythonhosted.org/packages/92/a3/7ade0576d17f3cdf5ff44d61390d4b3febb8a9fc2b480c75c47ea048c646/tomli-2.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8", size = 238273 }, - { url = "https://files.pythonhosted.org/packages/72/6f/fa64ef058ac1446a1e51110c375339b3ec6be245af9d14c87c4a6412dd32/tomli-2.2.1-cp311-cp311-win32.whl", hash = "sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff", size = 98310 }, - { url = "https://files.pythonhosted.org/packages/6a/1c/4a2dcde4a51b81be3530565e92eda625d94dafb46dbeb15069df4caffc34/tomli-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b", size = 108309 }, - { url = "https://files.pythonhosted.org/packages/52/e1/f8af4c2fcde17500422858155aeb0d7e93477a0d59a98e56cbfe75070fd0/tomli-2.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea", size = 132762 }, - { url = "https://files.pythonhosted.org/packages/03/b8/152c68bb84fc00396b83e7bbddd5ec0bd3dd409db4195e2a9b3e398ad2e3/tomli-2.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8", size = 123453 }, - { url = "https://files.pythonhosted.org/packages/c8/d6/fc9267af9166f79ac528ff7e8c55c8181ded34eb4b0e93daa767b8841573/tomli-2.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192", size = 233486 }, - { url = "https://files.pythonhosted.org/packages/5c/51/51c3f2884d7bab89af25f678447ea7d297b53b5a3b5730a7cb2ef6069f07/tomli-2.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222", size = 242349 }, - { url = "https://files.pythonhosted.org/packages/ab/df/bfa89627d13a5cc22402e441e8a931ef2108403db390ff3345c05253935e/tomli-2.2.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77", size = 252159 }, - { url = "https://files.pythonhosted.org/packages/9e/6e/fa2b916dced65763a5168c6ccb91066f7639bdc88b48adda990db10c8c0b/tomli-2.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6", size = 237243 }, - { url = "https://files.pythonhosted.org/packages/b4/04/885d3b1f650e1153cbb93a6a9782c58a972b94ea4483ae4ac5cedd5e4a09/tomli-2.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd", size = 259645 }, - { url = "https://files.pythonhosted.org/packages/9c/de/6b432d66e986e501586da298e28ebeefd3edc2c780f3ad73d22566034239/tomli-2.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e", size = 244584 }, - { url = "https://files.pythonhosted.org/packages/1c/9a/47c0449b98e6e7d1be6cbac02f93dd79003234ddc4aaab6ba07a9a7482e2/tomli-2.2.1-cp312-cp312-win32.whl", hash = "sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98", size = 98875 }, - { url = "https://files.pythonhosted.org/packages/ef/60/9b9638f081c6f1261e2688bd487625cd1e660d0a85bd469e91d8db969734/tomli-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4", size = 109418 }, - { url = "https://files.pythonhosted.org/packages/6e/c2/61d3e0f47e2b74ef40a68b9e6ad5984f6241a942f7cd3bbfbdbd03861ea9/tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc", size = 14257 }, +sdist = { url = "https://files.pythonhosted.org/packages/18/87/302344fed471e44a87289cf4967697d07e532f2421fdaf868a303cbae4ff/tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff", size = 17175, upload-time = "2024-11-27T22:38:36.873Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/43/ca/75707e6efa2b37c77dadb324ae7d9571cb424e61ea73fad7c56c2d14527f/tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249", size = 131077, upload-time = "2024-11-27T22:37:54.956Z" }, + { url = "https://files.pythonhosted.org/packages/c7/16/51ae563a8615d472fdbffc43a3f3d46588c264ac4f024f63f01283becfbb/tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6", size = 123429, upload-time = "2024-11-27T22:37:56.698Z" }, + { url = "https://files.pythonhosted.org/packages/f1/dd/4f6cd1e7b160041db83c694abc78e100473c15d54620083dbd5aae7b990e/tomli-2.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a", size = 226067, upload-time = "2024-11-27T22:37:57.63Z" }, + { url = "https://files.pythonhosted.org/packages/a9/6b/c54ede5dc70d648cc6361eaf429304b02f2871a345bbdd51e993d6cdf550/tomli-2.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee", size = 236030, upload-time = "2024-11-27T22:37:59.344Z" }, + { url = "https://files.pythonhosted.org/packages/1f/47/999514fa49cfaf7a92c805a86c3c43f4215621855d151b61c602abb38091/tomli-2.2.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e", size = 240898, upload-time = "2024-11-27T22:38:00.429Z" }, + { url = "https://files.pythonhosted.org/packages/73/41/0a01279a7ae09ee1573b423318e7934674ce06eb33f50936655071d81a24/tomli-2.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4", size = 229894, upload-time = "2024-11-27T22:38:02.094Z" }, + { url = "https://files.pythonhosted.org/packages/55/18/5d8bc5b0a0362311ce4d18830a5d28943667599a60d20118074ea1b01bb7/tomli-2.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106", size = 245319, upload-time = "2024-11-27T22:38:03.206Z" }, + { url = "https://files.pythonhosted.org/packages/92/a3/7ade0576d17f3cdf5ff44d61390d4b3febb8a9fc2b480c75c47ea048c646/tomli-2.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8", size = 238273, upload-time = "2024-11-27T22:38:04.217Z" }, + { url = "https://files.pythonhosted.org/packages/72/6f/fa64ef058ac1446a1e51110c375339b3ec6be245af9d14c87c4a6412dd32/tomli-2.2.1-cp311-cp311-win32.whl", hash = "sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff", size = 98310, upload-time = "2024-11-27T22:38:05.908Z" }, + { url = "https://files.pythonhosted.org/packages/6a/1c/4a2dcde4a51b81be3530565e92eda625d94dafb46dbeb15069df4caffc34/tomli-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b", size = 108309, upload-time = "2024-11-27T22:38:06.812Z" }, + { url = "https://files.pythonhosted.org/packages/52/e1/f8af4c2fcde17500422858155aeb0d7e93477a0d59a98e56cbfe75070fd0/tomli-2.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea", size = 132762, upload-time = "2024-11-27T22:38:07.731Z" }, + { url = "https://files.pythonhosted.org/packages/03/b8/152c68bb84fc00396b83e7bbddd5ec0bd3dd409db4195e2a9b3e398ad2e3/tomli-2.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8", size = 123453, upload-time = "2024-11-27T22:38:09.384Z" }, + { url = "https://files.pythonhosted.org/packages/c8/d6/fc9267af9166f79ac528ff7e8c55c8181ded34eb4b0e93daa767b8841573/tomli-2.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192", size = 233486, upload-time = "2024-11-27T22:38:10.329Z" }, + { url = "https://files.pythonhosted.org/packages/5c/51/51c3f2884d7bab89af25f678447ea7d297b53b5a3b5730a7cb2ef6069f07/tomli-2.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222", size = 242349, upload-time = "2024-11-27T22:38:11.443Z" }, + { url = "https://files.pythonhosted.org/packages/ab/df/bfa89627d13a5cc22402e441e8a931ef2108403db390ff3345c05253935e/tomli-2.2.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77", size = 252159, upload-time = "2024-11-27T22:38:13.099Z" }, + { url = "https://files.pythonhosted.org/packages/9e/6e/fa2b916dced65763a5168c6ccb91066f7639bdc88b48adda990db10c8c0b/tomli-2.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6", size = 237243, upload-time = "2024-11-27T22:38:14.766Z" }, + { url = "https://files.pythonhosted.org/packages/b4/04/885d3b1f650e1153cbb93a6a9782c58a972b94ea4483ae4ac5cedd5e4a09/tomli-2.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd", size = 259645, upload-time = "2024-11-27T22:38:15.843Z" }, + { url = "https://files.pythonhosted.org/packages/9c/de/6b432d66e986e501586da298e28ebeefd3edc2c780f3ad73d22566034239/tomli-2.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e", size = 244584, upload-time = "2024-11-27T22:38:17.645Z" }, + { url = "https://files.pythonhosted.org/packages/1c/9a/47c0449b98e6e7d1be6cbac02f93dd79003234ddc4aaab6ba07a9a7482e2/tomli-2.2.1-cp312-cp312-win32.whl", hash = "sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98", size = 98875, upload-time = "2024-11-27T22:38:19.159Z" }, + { url = "https://files.pythonhosted.org/packages/ef/60/9b9638f081c6f1261e2688bd487625cd1e660d0a85bd469e91d8db969734/tomli-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4", size = 109418, upload-time = "2024-11-27T22:38:20.064Z" }, + { url = "https://files.pythonhosted.org/packages/6e/c2/61d3e0f47e2b74ef40a68b9e6ad5984f6241a942f7cd3bbfbdbd03861ea9/tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc", size = 14257, upload-time = "2024-11-27T22:38:35.385Z" }, ] [[package]] @@ -5307,7 +5542,7 @@ dependencies = [ { name = "requests" }, { name = "six" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/0c/01/f811af86f1f80d5f289be075c3b281e74bf3fe081cfbe5cfce44954d2c3a/tos-2.7.2.tar.gz", hash = "sha256:3c31257716785bca7b2cac51474ff32543cda94075a7b7aff70d769c15c7b7ed", size = 123407 } +sdist = { url = "https://files.pythonhosted.org/packages/0c/01/f811af86f1f80d5f289be075c3b281e74bf3fe081cfbe5cfce44954d2c3a/tos-2.7.2.tar.gz", hash = "sha256:3c31257716785bca7b2cac51474ff32543cda94075a7b7aff70d769c15c7b7ed", size = 123407, upload-time = "2024-10-16T15:59:08.634Z" } [[package]] name = "tqdm" @@ -5316,14 +5551,14 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a8/4b/29b4ef32e036bb34e4ab51796dd745cdba7ed47ad142a9f4a1eb8e0c744d/tqdm-4.67.1.tar.gz", hash = "sha256:f8aef9c52c08c13a65f30ea34f4e5aac3fd1a34959879d7e59e63027286627f2", size = 169737 } +sdist = { url = "https://files.pythonhosted.org/packages/a8/4b/29b4ef32e036bb34e4ab51796dd745cdba7ed47ad142a9f4a1eb8e0c744d/tqdm-4.67.1.tar.gz", hash = "sha256:f8aef9c52c08c13a65f30ea34f4e5aac3fd1a34959879d7e59e63027286627f2", size = 169737, upload-time = "2024-11-24T20:12:22.481Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d0/30/dc54f88dd4a2b5dc8a0279bdd7270e735851848b762aeb1c1184ed1f6b14/tqdm-4.67.1-py3-none-any.whl", hash = "sha256:26445eca388f82e72884e0d580d5464cd801a3ea01e63e5601bdff9ba6a48de2", size = 78540 }, + { url = "https://files.pythonhosted.org/packages/d0/30/dc54f88dd4a2b5dc8a0279bdd7270e735851848b762aeb1c1184ed1f6b14/tqdm-4.67.1-py3-none-any.whl", hash = "sha256:26445eca388f82e72884e0d580d5464cd801a3ea01e63e5601bdff9ba6a48de2", size = 78540, upload-time = "2024-11-24T20:12:19.698Z" }, ] [[package]] name = "transformers" -version = "4.35.2" +version = "4.51.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "filelock" }, @@ -5337,14 +5572,14 @@ dependencies = [ { name = "tokenizers" }, { name = "tqdm" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/49/97/00142bd2fef5cdaa945ffc2aa0021d127390ef6b0fdc2ac7295cf199a488/transformers-4.35.2.tar.gz", hash = "sha256:2d125e197d77b0cdb6c9201df9fa7e2101493272e448b9fba9341c695bee2f52", size = 6832593 } +sdist = { url = "https://files.pythonhosted.org/packages/f1/11/7414d5bc07690002ce4d7553602107bf969af85144bbd02830f9fb471236/transformers-4.51.3.tar.gz", hash = "sha256:e292fcab3990c6defe6328f0f7d2004283ca81a7a07b2de9a46d67fd81ea1409", size = 8941266, upload-time = "2025-04-14T08:15:00.485Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/12/dd/f17b11a93a9ca27728e12512d167eb1281c151c4c6881d3ab59eb58f4127/transformers-4.35.2-py3-none-any.whl", hash = "sha256:9dfa76f8692379544ead84d98f537be01cd1070de75c74efb13abcbc938fbe2f", size = 7920648 }, + { url = "https://files.pythonhosted.org/packages/a9/b6/5257d04ae327b44db31f15cce39e6020cc986333c715660b1315a9724d82/transformers-4.51.3-py3-none-any.whl", hash = "sha256:fd3279633ceb2b777013234bbf0b4f5c2d23c4626b05497691f00cfda55e8a83", size = 10383940, upload-time = "2025-04-14T08:13:43.023Z" }, ] [[package]] name = "typer" -version = "0.15.2" +version = "0.16.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "click" }, @@ -5352,87 +5587,108 @@ dependencies = [ { name = "shellingham" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/8b/6f/3991f0f1c7fcb2df31aef28e0594d8d54b05393a0e4e34c65e475c2a5d41/typer-0.15.2.tar.gz", hash = "sha256:ab2fab47533a813c49fe1f16b1a370fd5819099c00b119e0633df65f22144ba5", size = 100711 } +sdist = { url = "https://files.pythonhosted.org/packages/c5/8c/7d682431efca5fd290017663ea4588bf6f2c6aad085c7f108c5dbc316e70/typer-0.16.0.tar.gz", hash = "sha256:af377ffaee1dbe37ae9440cb4e8f11686ea5ce4e9bae01b84ae7c63b87f1dd3b", size = 102625, upload-time = "2025-05-26T14:30:31.824Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/7f/fc/5b29fea8cee020515ca82cc68e3b8e1e34bb19a3535ad854cac9257b414c/typer-0.15.2-py3-none-any.whl", hash = "sha256:46a499c6107d645a9c13f7ee46c5d5096cae6f5fc57dd11eccbbb9ae3e44ddfc", size = 45061 }, + { url = "https://files.pythonhosted.org/packages/76/42/3efaf858001d2c2913de7f354563e3a3a2f0decae3efe98427125a8f441e/typer-0.16.0-py3-none-any.whl", hash = "sha256:1f79bed11d4d02d4310e3c1b7ba594183bcedb0ac73b27a9e5f28f6fb5b98855", size = 46317, upload-time = "2025-05-26T14:30:30.523Z" }, ] [[package]] name = "types-aiofiles" -version = "24.1.0.20250326" +version = "24.1.0.20250516" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b3/13/41faafda1d85fc8afa744ee67a2d788b3ba63e5f1c7303e5d10c2d784d2d/types_aiofiles-24.1.0.20250516.tar.gz", hash = "sha256:7fd2a7f793bbe180b7b22cd4f59300fe61fdc9940b3bbc9899ffe32849b95188", size = 14304, upload-time = "2025-05-16T03:08:29.55Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/70/e7/828287ba5d1107db1093a123fe8e481eb5aab55c911f1100f28d0df80d5a/types_aiofiles-24.1.0.20250516-py3-none-any.whl", hash = "sha256:ec265994629146804b656a971c46f393ce860305834b3cacb4b8b6fb7dba7e33", size = 14253, upload-time = "2025-05-16T03:08:28.67Z" }, +] + +[[package]] +name = "types-awscrt" +version = "0.27.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/8d/25/c76a9ee91eefac376ed8905b029272e27c44739e3f148faf5c00afe71e43/types_aiofiles-24.1.0.20250326.tar.gz", hash = "sha256:c4bbe432fd043911ba83fb635456f5cc54f6d05fda2aadf6bef12a84f07a6efe", size = 14369 } +sdist = { url = "https://files.pythonhosted.org/packages/36/6c/583522cfb3c330e92e726af517a91c13247e555e021791a60f1b03c6ff16/types_awscrt-0.27.2.tar.gz", hash = "sha256:acd04f57119eb15626ab0ba9157fc24672421de56e7bd7b9f61681fedee44e91", size = 16304, upload-time = "2025-05-16T03:10:08.712Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/0e/18/1016ffd4c7775f24371f6a0309483dc5597e8245b5add67924e54ea3b83a/types_aiofiles-24.1.0.20250326-py3-none-any.whl", hash = "sha256:dfb58c9aa18bd449e80fb5d7f49dc3dd20d31de920a46223a61798ee4a521a70", size = 14344 }, + { url = "https://files.pythonhosted.org/packages/4c/82/1ee2e5c9d28deac086ab3a6ff07c8bc393ef013a083f546c623699881715/types_awscrt-0.27.2-py3-none-any.whl", hash = "sha256:49a045f25bbd5ad2865f314512afced933aed35ddbafc252e2268efa8a787e4e", size = 37761, upload-time = "2025-05-16T03:10:07.466Z" }, ] [[package]] name = "types-beautifulsoup4" -version = "4.12.0.20250204" +version = "4.12.0.20250516" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "types-html5lib" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/11/6c/00fd71754ac3babe121c73b52e0de7ec05acd627edcb7ee652223c084d69/types_beautifulsoup4-4.12.0.20250204.tar.gz", hash = "sha256:f083d8edcbd01279f8c3995b56cfff2d01f1bb894c3b502ba118d36fbbc495bf", size = 16641 } +sdist = { url = "https://files.pythonhosted.org/packages/6d/d1/32b410f6d65eda94d3dfb0b3d0ca151f12cb1dc4cef731dcf7cbfd8716ff/types_beautifulsoup4-4.12.0.20250516.tar.gz", hash = "sha256:aa19dd73b33b70d6296adf92da8ab8a0c945c507e6fb7d5db553415cc77b417e", size = 16628, upload-time = "2025-05-16T03:09:09.93Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b1/ec/9097e9f7f5901e4d7834c7e0bc8f775f9ffa448ae31471457a1ebafeb4c5/types_beautifulsoup4-4.12.0.20250204-py3-none-any.whl", hash = "sha256:57ce9e75717b63c390fd789c787d267a67eb01fa6d800a03b9bdde2e877ed1eb", size = 17061 }, + { url = "https://files.pythonhosted.org/packages/7c/79/d84de200a80085b32f12c5820d4fd0addcbe7ba6dce8c1c9d8605e833c8e/types_beautifulsoup4-4.12.0.20250516-py3-none-any.whl", hash = "sha256:5923399d4a1ba9cc8f0096fe334cc732e130269541d66261bb42ab039c0376ee", size = 16879, upload-time = "2025-05-16T03:09:09.051Z" }, ] [[package]] name = "types-cachetools" version = "5.5.0.20240820" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/c2/7e/ad6ba4a56b2a994e0f0a04a61a50466b60ee88a13d10a18c83ac14a66c61/types-cachetools-5.5.0.20240820.tar.gz", hash = "sha256:b888ab5c1a48116f7799cd5004b18474cd82b5463acb5ffb2db2fc9c7b053bc0", size = 4198 } +sdist = { url = "https://files.pythonhosted.org/packages/c2/7e/ad6ba4a56b2a994e0f0a04a61a50466b60ee88a13d10a18c83ac14a66c61/types-cachetools-5.5.0.20240820.tar.gz", hash = "sha256:b888ab5c1a48116f7799cd5004b18474cd82b5463acb5ffb2db2fc9c7b053bc0", size = 4198, upload-time = "2024-08-20T02:30:07.525Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/27/4d/fd7cc050e2d236d5570c4d92531c0396573a1e14b31735870e849351c717/types_cachetools-5.5.0.20240820-py3-none-any.whl", hash = "sha256:efb2ed8bf27a4b9d3ed70d33849f536362603a90b8090a328acf0cd42fda82e2", size = 4149 }, + { url = "https://files.pythonhosted.org/packages/27/4d/fd7cc050e2d236d5570c4d92531c0396573a1e14b31735870e849351c717/types_cachetools-5.5.0.20240820-py3-none-any.whl", hash = "sha256:efb2ed8bf27a4b9d3ed70d33849f536362603a90b8090a328acf0cd42fda82e2", size = 4149, upload-time = "2024-08-20T02:30:06.461Z" }, +] + +[[package]] +name = "types-cffi" +version = "1.17.0.20250523" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "types-setuptools" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f7/5f/ac80a2f55757019e5d4809d17544569c47a623565258ca1a836ba951d53f/types_cffi-1.17.0.20250523.tar.gz", hash = "sha256:e7110f314c65590533adae1b30763be08ca71ad856a1ae3fe9b9d8664d49ec22", size = 16858, upload-time = "2025-05-23T03:05:40.983Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f1/86/e26e6ae4dfcbf6031b8422c22cf3a9eb2b6d127770406e7645b6248d8091/types_cffi-1.17.0.20250523-py3-none-any.whl", hash = "sha256:e98c549d8e191f6220e440f9f14315d6775a21a0e588c32c20476be885b2fad9", size = 20010, upload-time = "2025-05-23T03:05:39.136Z" }, ] [[package]] name = "types-colorama" version = "0.4.15.20240311" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/59/73/0fb0b9fe4964b45b2a06ed41b60c352752626db46aa0fb70a49a9e283a75/types-colorama-0.4.15.20240311.tar.gz", hash = "sha256:a28e7f98d17d2b14fb9565d32388e419f4108f557a7d939a66319969b2b99c7a", size = 5608 } +sdist = { url = "https://files.pythonhosted.org/packages/59/73/0fb0b9fe4964b45b2a06ed41b60c352752626db46aa0fb70a49a9e283a75/types-colorama-0.4.15.20240311.tar.gz", hash = "sha256:a28e7f98d17d2b14fb9565d32388e419f4108f557a7d939a66319969b2b99c7a", size = 5608, upload-time = "2024-03-11T02:15:51.557Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b7/83/6944b4fa01efb2e63ac62b791a8ddf0fee358f93be9f64b8f152648ad9d3/types_colorama-0.4.15.20240311-py3-none-any.whl", hash = "sha256:6391de60ddc0db3f147e31ecb230006a6823e81e380862ffca1e4695c13a0b8e", size = 5840 }, + { url = "https://files.pythonhosted.org/packages/b7/83/6944b4fa01efb2e63ac62b791a8ddf0fee358f93be9f64b8f152648ad9d3/types_colorama-0.4.15.20240311-py3-none-any.whl", hash = "sha256:6391de60ddc0db3f147e31ecb230006a6823e81e380862ffca1e4695c13a0b8e", size = 5840, upload-time = "2024-03-11T02:15:50.43Z" }, ] [[package]] name = "types-defusedxml" -version = "0.7.0.20240218" +version = "0.7.0.20250516" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/5c/04/acc0a4ef3ea7eff334e3bc73e7a7af54f06684dfea405c0c81fb28a243ab/types-defusedxml-0.7.0.20240218.tar.gz", hash = "sha256:05688a7724dc66ea74c4af5ca0efc554a150c329cb28c13a64902cab878d06ed", size = 5493 } +sdist = { url = "https://files.pythonhosted.org/packages/55/9d/3ba8b80536402f1a125bc5a44d82ab686aafa55a85f56160e076b2ac30de/types_defusedxml-0.7.0.20250516.tar.gz", hash = "sha256:164c2945077fa450f24ed09633f8b3a80694687fefbbc1cba5f24e4ba570666b", size = 10298, upload-time = "2025-05-16T03:08:18.951Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/82/a3/6c007483045d362f45cc30955eb07644a3e8a0d57ee6293f5b540b127558/types_defusedxml-0.7.0.20240218-py3-none-any.whl", hash = "sha256:2b7f3c5ca14fdbe728fab0b846f5f7eb98c4bd4fd2b83d25f79e923caa790ced", size = 7827 }, + { url = "https://files.pythonhosted.org/packages/2e/7b/567b0978150edccf7fa3aa8f2566ea9c3ffc9481ce7d64428166934d6d7f/types_defusedxml-0.7.0.20250516-py3-none-any.whl", hash = "sha256:00e793e5c385c3e142d7c2acc3b4ccea2fe0828cee11e35501f0ba40386630a0", size = 12576, upload-time = "2025-05-16T03:08:17.892Z" }, ] [[package]] name = "types-deprecated" version = "1.2.15.20250304" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/0e/67/eeefaaabb03b288aad85483d410452c8bbcbf8b2bd876b0e467ebd97415b/types_deprecated-1.2.15.20250304.tar.gz", hash = "sha256:c329030553029de5cc6cb30f269c11f4e00e598c4241290179f63cda7d33f719", size = 8015 } +sdist = { url = "https://files.pythonhosted.org/packages/0e/67/eeefaaabb03b288aad85483d410452c8bbcbf8b2bd876b0e467ebd97415b/types_deprecated-1.2.15.20250304.tar.gz", hash = "sha256:c329030553029de5cc6cb30f269c11f4e00e598c4241290179f63cda7d33f719", size = 8015, upload-time = "2025-03-04T02:48:17.894Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/4d/e3/c18aa72ab84e0bc127a3a94e93be1a6ac2cb281371d3a45376ab7cfdd31c/types_deprecated-1.2.15.20250304-py3-none-any.whl", hash = "sha256:86a65aa550ea8acf49f27e226b8953288cd851de887970fbbdf2239c116c3107", size = 8553 }, + { url = "https://files.pythonhosted.org/packages/4d/e3/c18aa72ab84e0bc127a3a94e93be1a6ac2cb281371d3a45376ab7cfdd31c/types_deprecated-1.2.15.20250304-py3-none-any.whl", hash = "sha256:86a65aa550ea8acf49f27e226b8953288cd851de887970fbbdf2239c116c3107", size = 8553, upload-time = "2025-03-04T02:48:16.666Z" }, ] [[package]] name = "types-docutils" -version = "0.21.0.20241128" +version = "0.21.0.20250526" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/dd/df/64e7ab01a4fc5ce46895dc94e31cffc8b8087c8d91ee54c45ac2d8d82445/types_docutils-0.21.0.20241128.tar.gz", hash = "sha256:4dd059805b83ac6ec5a223699195c4e9eeb0446a4f7f2aeff1759a4a7cc17473", size = 26739 } +sdist = { url = "https://files.pythonhosted.org/packages/34/bf/bb5695f7a9660f79a9cd999ea13ff7331b8f2d03aec3d2fd7c38be4bc8aa/types_docutils-0.21.0.20250526.tar.gz", hash = "sha256:6c7ba387716315df0d86a796baec9d5a71825ed2746cb7763193aafbb70ac86c", size = 38140, upload-time = "2025-05-26T03:10:49.242Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/59/b6/10ba95739f2cbb9c5bd2f6568148d62b468afe01a94c633e8892a2936d8a/types_docutils-0.21.0.20241128-py3-none-any.whl", hash = "sha256:e0409204009639e9b0bf4521eeabe58b5e574ce9c0db08421c2ac26c32be0039", size = 34677 }, + { url = "https://files.pythonhosted.org/packages/35/84/73bca8d1364f6685bd6e00eaa15e653ef96163231fbd7a612f3a845497fb/types_docutils-0.21.0.20250526-py3-none-any.whl", hash = "sha256:44d9f9ed19bb75071deb6804947c123f30bbc617a656420f044e09b9f16b72d1", size = 62000, upload-time = "2025-05-26T03:10:48.101Z" }, ] [[package]] name = "types-flask-cors" -version = "5.0.0.20240902" +version = "5.0.0.20250413" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "flask" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/dc/04/166bb81dcf1a0a4630a00ff2237cc789968c85836ea179496f163cce0678/types-Flask-Cors-5.0.0.20240902.tar.gz", hash = "sha256:8921b273bf7cd9636df136b66408efcfa6338a935e5c8f53f5eff1cee03f3394", size = 5159 } +sdist = { url = "https://files.pythonhosted.org/packages/a4/f3/dd2f0d274ecb77772d3ce83735f75ad14713461e8cf7e6d61a7c272037b1/types_flask_cors-5.0.0.20250413.tar.gz", hash = "sha256:b346d052f4ef3b606b73faf13e868e458f1efdbfedcbe1aba739eb2f54a6cf5f", size = 9921, upload-time = "2025-04-13T04:04:15.515Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/66/ed/3c34a60873fe63fa56b29f589c395618b20a3daa90e91faf770152c22a77/types_Flask_Cors-5.0.0.20240902-py3-none-any.whl", hash = "sha256:595e5f36056cd128ab905832e055f2e5d116fbdc685356eea4490bc77df82137", size = 5303 }, + { url = "https://files.pythonhosted.org/packages/66/34/7d64eb72d80bfd5b9e6dd31e7fe351a1c9a735f5c01e85b1d3b903a9d656/types_flask_cors-5.0.0.20250413-py3-none-any.whl", hash = "sha256:8183fdba764d45a5b40214468a1d5daa0e86c4ee6042d13f38cc428308f27a64", size = 9982, upload-time = "2025-04-13T04:04:14.27Z" }, ] [[package]] @@ -5443,9 +5699,9 @@ dependencies = [ { name = "flask" }, { name = "flask-sqlalchemy" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d6/2a/15d922ddd3fad1ec0e06dab338f20c508becacaf8193ff373aee6986a1cc/types_flask_migrate-4.1.0.20250112.tar.gz", hash = "sha256:f2d2c966378ae7bb0660ec810e9af0a56ca03108235364c2a7b5e90418b0ff67", size = 8650 } +sdist = { url = "https://files.pythonhosted.org/packages/d6/2a/15d922ddd3fad1ec0e06dab338f20c508becacaf8193ff373aee6986a1cc/types_flask_migrate-4.1.0.20250112.tar.gz", hash = "sha256:f2d2c966378ae7bb0660ec810e9af0a56ca03108235364c2a7b5e90418b0ff67", size = 8650, upload-time = "2025-01-12T02:51:25.29Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/36/01/56e26643c54c5101a7bc11d277d15cd871b05a8a3ddbcc9acd3634d7fff8/types_Flask_Migrate-4.1.0.20250112-py3-none-any.whl", hash = "sha256:1814fffc609c2ead784affd011de92f0beecd48044963a8c898dd107dc1b5969", size = 8727 }, + { url = "https://files.pythonhosted.org/packages/36/01/56e26643c54c5101a7bc11d277d15cd871b05a8a3ddbcc9acd3634d7fff8/types_Flask_Migrate-4.1.0.20250112-py3-none-any.whl", hash = "sha256:1814fffc609c2ead784affd011de92f0beecd48044963a8c898dd107dc1b5969", size = 8727, upload-time = "2025-01-12T02:51:23.121Z" }, ] [[package]] @@ -5456,199 +5712,251 @@ dependencies = [ { name = "types-greenlet" }, { name = "types-psutil" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/f8/db/bdade74c3ba3a266eafd625377eb7b9b37c9c724c7472192100baf0fe507/types_gevent-24.11.0.20250401.tar.gz", hash = "sha256:1443f796a442062698e67d818fca50aa88067dee4021d457a7c0c6bedd6f46ca", size = 36980 } +sdist = { url = "https://files.pythonhosted.org/packages/f8/db/bdade74c3ba3a266eafd625377eb7b9b37c9c724c7472192100baf0fe507/types_gevent-24.11.0.20250401.tar.gz", hash = "sha256:1443f796a442062698e67d818fca50aa88067dee4021d457a7c0c6bedd6f46ca", size = 36980, upload-time = "2025-04-01T03:07:30.365Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/25/3d/c8b12d048565ef12ae65d71a0e566f36c6e076b158d3f94d87edddbeea6b/types_gevent-24.11.0.20250401-py3-none-any.whl", hash = "sha256:6764faf861ea99250c38179c58076392c44019ac3393029f71b06c4a15e8c1d1", size = 54863 }, + { url = "https://files.pythonhosted.org/packages/25/3d/c8b12d048565ef12ae65d71a0e566f36c6e076b158d3f94d87edddbeea6b/types_gevent-24.11.0.20250401-py3-none-any.whl", hash = "sha256:6764faf861ea99250c38179c58076392c44019ac3393029f71b06c4a15e8c1d1", size = 54863, upload-time = "2025-04-01T03:07:29.147Z" }, ] [[package]] name = "types-greenlet" version = "3.1.0.20250401" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/c0/c9/50405ed194a02f02a418311311e6ee4dd73eed446608b679e6df8170d5b7/types_greenlet-3.1.0.20250401.tar.gz", hash = "sha256:949389b64c34ca9472f6335189e9fe0b2e9704436d4f0850e39e9b7145909082", size = 8460 } +sdist = { url = "https://files.pythonhosted.org/packages/c0/c9/50405ed194a02f02a418311311e6ee4dd73eed446608b679e6df8170d5b7/types_greenlet-3.1.0.20250401.tar.gz", hash = "sha256:949389b64c34ca9472f6335189e9fe0b2e9704436d4f0850e39e9b7145909082", size = 8460, upload-time = "2025-04-01T03:06:44.216Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a5/f3/36c5a6db23761c810d91227146f20b6e501aa50a51a557bd14e021cd9aea/types_greenlet-3.1.0.20250401-py3-none-any.whl", hash = "sha256:77987f3249b0f21415dc0254057e1ae4125a696a9bba28b0bcb67ee9e3dc14f6", size = 8821 }, + { url = "https://files.pythonhosted.org/packages/a5/f3/36c5a6db23761c810d91227146f20b6e501aa50a51a557bd14e021cd9aea/types_greenlet-3.1.0.20250401-py3-none-any.whl", hash = "sha256:77987f3249b0f21415dc0254057e1ae4125a696a9bba28b0bcb67ee9e3dc14f6", size = 8821, upload-time = "2025-04-01T03:06:42.945Z" }, ] [[package]] name = "types-html5lib" -version = "1.1.11.20241018" +version = "1.1.11.20250516" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d0/ed/9f092ff479e2b5598941855f314a22953bb04b5fb38bcba3f880feb833ba/types_html5lib-1.1.11.20250516.tar.gz", hash = "sha256:65043a6718c97f7d52567cc0cdf41efbfc33b1f92c6c0c5e19f60a7ec69ae720", size = 16136, upload-time = "2025-05-16T03:07:12.231Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cc/3b/cb5b23c7b51bf48b8c9f175abb9dce2f1ecd2d2c25f92ea9f4e3720e9398/types_html5lib-1.1.11.20250516-py3-none-any.whl", hash = "sha256:5e407b14b1bd2b9b1107cbd1e2e19d4a0c46d60febd231c7ab7313d7405663c1", size = 21770, upload-time = "2025-05-16T03:07:11.102Z" }, +] + +[[package]] +name = "types-jmespath" +version = "1.0.2.20250529" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b6/9d/f6fbcc8246f5e46845b4f989c4e17e6fb3ce572f7065b185e515bf8a3be7/types-html5lib-1.1.11.20241018.tar.gz", hash = "sha256:98042555ff78d9e3a51c77c918b1041acbb7eb6c405408d8a9e150ff5beccafa", size = 11370 } +sdist = { url = "https://files.pythonhosted.org/packages/ab/ce/1083f6dcf5e7f25e9abcb67f870799d45f8b184cdb6fd23bbe541d17d9cc/types_jmespath-1.0.2.20250529.tar.gz", hash = "sha256:d3c08397f57fe0510e3b1b02c27f0a5e738729680fb0ea5f4b74f70fb032c129", size = 10138, upload-time = "2025-05-29T03:07:30.24Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ba/7c/f862b1dc31268ef10fe95b43dcdf216ba21a592fafa2d124445cd6b92e93/types_html5lib-1.1.11.20241018-py3-none-any.whl", hash = "sha256:3f1e064d9ed2c289001ae6392c84c93833abb0816165c6ff0abfc304a779f403", size = 17292 }, + { url = "https://files.pythonhosted.org/packages/66/74/78c518aeb310cc809aaf1dd19e646f8d42c472344a720b39e1ba2a65c2e7/types_jmespath-1.0.2.20250529-py3-none-any.whl", hash = "sha256:6344c102233aae954d623d285618079d797884e35f6cd8d2a894ca02640eca07", size = 11409, upload-time = "2025-05-29T03:07:29.012Z" }, +] + +[[package]] +name = "types-jsonschema" +version = "4.23.0.20250516" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "referencing" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a0/ec/27ea5bffdb306bf261f6677a98b6993d93893b2c2e30f7ecc1d2c99d32e7/types_jsonschema-4.23.0.20250516.tar.gz", hash = "sha256:9ace09d9d35c4390a7251ccd7d833b92ccc189d24d1b347f26212afce361117e", size = 14911, upload-time = "2025-05-16T03:09:33.728Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e6/48/73ae8b388e19fc4a2a8060d0876325ec7310cfd09b53a2185186fd35959f/types_jsonschema-4.23.0.20250516-py3-none-any.whl", hash = "sha256:e7d0dd7db7e59e63c26e3230e26ffc64c4704cc5170dc21270b366a35ead1618", size = 15027, upload-time = "2025-05-16T03:09:32.499Z" }, ] [[package]] name = "types-markdown" version = "3.7.0.20250322" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/bd/fd/b4bd01b8c46f021c35a07aa31fe1dc45d21adc9fc8d53064bfa577aae73d/types_markdown-3.7.0.20250322.tar.gz", hash = "sha256:a48ed82dfcb6954592a10f104689d2d44df9125ce51b3cee20e0198a5216d55c", size = 18052 } +sdist = { url = "https://files.pythonhosted.org/packages/bd/fd/b4bd01b8c46f021c35a07aa31fe1dc45d21adc9fc8d53064bfa577aae73d/types_markdown-3.7.0.20250322.tar.gz", hash = "sha256:a48ed82dfcb6954592a10f104689d2d44df9125ce51b3cee20e0198a5216d55c", size = 18052, upload-time = "2025-03-22T02:48:46.193Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/56/59/ee46617bc2b5e43bc06a000fdcd6358a013957e30ad545bed5e3456a4341/types_markdown-3.7.0.20250322-py3-none-any.whl", hash = "sha256:7e855503027b4290355a310fb834871940d9713da7c111f3e98a5e1cbc77acfb", size = 23699 }, + { url = "https://files.pythonhosted.org/packages/56/59/ee46617bc2b5e43bc06a000fdcd6358a013957e30ad545bed5e3456a4341/types_markdown-3.7.0.20250322-py3-none-any.whl", hash = "sha256:7e855503027b4290355a310fb834871940d9713da7c111f3e98a5e1cbc77acfb", size = 23699, upload-time = "2025-03-22T02:48:45.001Z" }, ] [[package]] name = "types-oauthlib" -version = "3.2.0.20250408" +version = "3.2.0.20250516" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d0/03/d746efe82f8c04feea6c527df2a77a89b84eb71e931279d9d1f180644037/types_oauthlib-3.2.0.20250408.tar.gz", hash = "sha256:8fceb310ee5da1f767428a3cb932cba60b281259322743e4b1321409b911ae0e", size = 23977 } +sdist = { url = "https://files.pythonhosted.org/packages/b1/2c/dba2c193ccff2d1e2835589d4075b230d5627b9db363e9c8de153261d6ec/types_oauthlib-3.2.0.20250516.tar.gz", hash = "sha256:56bf2cffdb8443ae718d4e83008e3fbd5f861230b4774e6d7799527758119d9a", size = 24683, upload-time = "2025-05-16T03:07:42.484Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c2/d6/3fa44b77354441a4c4c8b0c6dc534e94b46013aa6ea20d9c7e3fe7d391c1/types_oauthlib-3.2.0.20250408-py3-none-any.whl", hash = "sha256:1a41f0e18beeae05764f910ae59cc5df877807ae8c9e6f03f5f3af15ae4b9c01", size = 44485 }, + { url = "https://files.pythonhosted.org/packages/b8/54/cdd62283338616fd2448f534b29110d79a42aaabffaf5f45e7aed365a366/types_oauthlib-3.2.0.20250516-py3-none-any.whl", hash = "sha256:5799235528bc9bd262827149a1633ff55ae6e5a5f5f151f4dae74359783a31b3", size = 45671, upload-time = "2025-05-16T03:07:41.268Z" }, ] [[package]] name = "types-objgraph" version = "3.6.0.20240907" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/22/48/ba0ec63d392904eee34ef1cbde2d8798f79a3663950e42fbbc25fd1bd6f7/types-objgraph-3.6.0.20240907.tar.gz", hash = "sha256:2e3dee675843ae387889731550b0ddfed06e9420946cf78a4bca565b5fc53634", size = 2928 } +sdist = { url = "https://files.pythonhosted.org/packages/22/48/ba0ec63d392904eee34ef1cbde2d8798f79a3663950e42fbbc25fd1bd6f7/types-objgraph-3.6.0.20240907.tar.gz", hash = "sha256:2e3dee675843ae387889731550b0ddfed06e9420946cf78a4bca565b5fc53634", size = 2928, upload-time = "2024-09-07T02:35:21.214Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/16/c9/6d647a947f3937b19bcc6d52262921ddad60d90060ff66511a4bd7e990c5/types_objgraph-3.6.0.20240907-py3-none-any.whl", hash = "sha256:67207633a9b5789ee1911d740b269c3371081b79c0d8f68b00e7b8539f5c43f5", size = 3314 }, + { url = "https://files.pythonhosted.org/packages/16/c9/6d647a947f3937b19bcc6d52262921ddad60d90060ff66511a4bd7e990c5/types_objgraph-3.6.0.20240907-py3-none-any.whl", hash = "sha256:67207633a9b5789ee1911d740b269c3371081b79c0d8f68b00e7b8539f5c43f5", size = 3314, upload-time = "2024-09-07T02:35:19.865Z" }, ] [[package]] name = "types-olefile" version = "0.47.0.20240806" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/49/18/9d87a1bc394323ce22690308c751680c4301fc3fbe47cd58e16d760b563a/types-olefile-0.47.0.20240806.tar.gz", hash = "sha256:96490f208cbb449a52283855319d73688ba9167ae58858ef8c506bf7ca2c6b67", size = 4369 } +sdist = { url = "https://files.pythonhosted.org/packages/49/18/9d87a1bc394323ce22690308c751680c4301fc3fbe47cd58e16d760b563a/types-olefile-0.47.0.20240806.tar.gz", hash = "sha256:96490f208cbb449a52283855319d73688ba9167ae58858ef8c506bf7ca2c6b67", size = 4369, upload-time = "2024-08-06T02:30:01.966Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a9/4d/f8acae53dd95353f8a789a06ea27423ae41f2067eb6ce92946fdc6a1f7a7/types_olefile-0.47.0.20240806-py3-none-any.whl", hash = "sha256:c760a3deab7adb87a80d33b0e4edbbfbab865204a18d5121746022d7f8555118", size = 4758 }, + { url = "https://files.pythonhosted.org/packages/a9/4d/f8acae53dd95353f8a789a06ea27423ae41f2067eb6ce92946fdc6a1f7a7/types_olefile-0.47.0.20240806-py3-none-any.whl", hash = "sha256:c760a3deab7adb87a80d33b0e4edbbfbab865204a18d5121746022d7f8555118", size = 4758, upload-time = "2024-08-06T02:30:01.15Z" }, ] [[package]] name = "types-openpyxl" -version = "3.1.5.20250306" +version = "3.1.5.20250602" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/5a/45/e86035e4181a9cc039348ae913fe0f7b3ce3e7538f0901436f3b27a9a8f9/types_openpyxl-3.1.5.20250306.tar.gz", hash = "sha256:aa7ad2425e8020ff46a31633becfe1f3c64114498d964c536199f654b464e6bc", size = 100479 } +sdist = { url = "https://files.pythonhosted.org/packages/bc/d4/33cc2f331cde82206aa4ec7d8db408beca65964785f438c6d2505d828178/types_openpyxl-3.1.5.20250602.tar.gz", hash = "sha256:d19831482022fc933780d6e9d6990464c18c2ec5f14786fea862f72c876980b5", size = 100608, upload-time = "2025-06-02T03:14:40.625Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/20/b2/cdbf437c638b61a87afa51b50d122d4106558802c6d8528705774ae5ce0d/types_openpyxl-3.1.5.20250306-py3-none-any.whl", hash = "sha256:f7733dac1dcb07c89ff5ffde8452ee8d272be638defed855f4c48b2990ce5aa7", size = 166070 }, + { url = "https://files.pythonhosted.org/packages/2e/69/5b924a20a4d441ec2160e94085b9fa9358dc27edde10080d71209c59101d/types_openpyxl-3.1.5.20250602-py3-none-any.whl", hash = "sha256:1f82211e086902318f6a14b5d8d865102362fda7cb82f3d63ac4dff47a1f164b", size = 165922, upload-time = "2025-06-02T03:14:39.226Z" }, ] [[package]] name = "types-pexpect" -version = "4.9.0.20241208" +version = "4.9.0.20250516" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/35/8b/2ac0c1db88bdc441d21477f151a074b1789aa3a4deac571b079a097bd987/types_pexpect-4.9.0.20241208.tar.gz", hash = "sha256:bbca0d0819947a719989a5cfe83641d9212bef893e2f0a7a01e47926bc82401d", size = 13222 } +sdist = { url = "https://files.pythonhosted.org/packages/92/a3/3943fcb94c12af29a88c346b588f1eda180b8b99aeb388a046b25072732c/types_pexpect-4.9.0.20250516.tar.gz", hash = "sha256:7baed9ee566fa24034a567cbec56a5cff189a021344e84383b14937b35d83881", size = 13285, upload-time = "2025-05-16T03:08:33.327Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/37/c6/14c23e04bae6a83e051da86c1670684e59acadab333a8497384aa201defd/types_pexpect-4.9.0.20241208-py3-none-any.whl", hash = "sha256:1928f478528454f0fea3495c16cf1ee2e67fca5c9fe97d60b868ac48c1fd5633", size = 17085 }, + { url = "https://files.pythonhosted.org/packages/e1/d4/3128ae3365b46b9c4a33202af79b0e0d9d4308a6348a3317ce2331fea6cb/types_pexpect-4.9.0.20250516-py3-none-any.whl", hash = "sha256:84cbd7ae9da577c0d2629d4e4fd53cf074cd012296e01fd4fa1031e01973c28a", size = 17081, upload-time = "2025-05-16T03:08:32.127Z" }, ] [[package]] name = "types-protobuf" version = "5.29.1.20250403" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/78/6d/62a2e73b966c77609560800004dd49a926920dd4976a9fdd86cf998e7048/types_protobuf-5.29.1.20250403.tar.gz", hash = "sha256:7ff44f15022119c9d7558ce16e78b2d485bf7040b4fadced4dd069bb5faf77a2", size = 59413 } +sdist = { url = "https://files.pythonhosted.org/packages/78/6d/62a2e73b966c77609560800004dd49a926920dd4976a9fdd86cf998e7048/types_protobuf-5.29.1.20250403.tar.gz", hash = "sha256:7ff44f15022119c9d7558ce16e78b2d485bf7040b4fadced4dd069bb5faf77a2", size = 59413, upload-time = "2025-04-02T10:07:17.138Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/69/e3/b74dcc2797b21b39d5a4f08a8b08e20369b4ca250d718df7af41a60dd9f0/types_protobuf-5.29.1.20250403-py3-none-any.whl", hash = "sha256:c71de04106a2d54e5b2173d0a422058fae0ef2d058d70cf369fb797bf61ffa59", size = 73874 }, + { url = "https://files.pythonhosted.org/packages/69/e3/b74dcc2797b21b39d5a4f08a8b08e20369b4ca250d718df7af41a60dd9f0/types_protobuf-5.29.1.20250403-py3-none-any.whl", hash = "sha256:c71de04106a2d54e5b2173d0a422058fae0ef2d058d70cf369fb797bf61ffa59", size = 73874, upload-time = "2025-04-02T10:07:15.755Z" }, ] [[package]] name = "types-psutil" -version = "7.0.0.20250401" +version = "7.0.0.20250601" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ef/fc/3829cb113aa05c268b18369f1f003a4589216931658ebfa69e3d4931ba60/types_psutil-7.0.0.20250401.tar.gz", hash = "sha256:2a7d663c0888a079fc1643ebc109ad12e57a21c9552a9e2035da504191336dbf", size = 20273 } +sdist = { url = "https://files.pythonhosted.org/packages/c8/af/767b92be7de4105f5e2e87a53aac817164527c4a802119ad5b4e23028f7c/types_psutil-7.0.0.20250601.tar.gz", hash = "sha256:71fe9c4477a7e3d4f1233862f0877af87bff057ff398f04f4e5c0ca60aded197", size = 20297, upload-time = "2025-06-01T03:25:16.698Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/58/42/45e01f3bce242c0caad36b968114a00f454169df6c771c092c96727239d8/types_psutil-7.0.0.20250401-py3-none-any.whl", hash = "sha256:ed23f7140368104afe4e05a6085a5fa56fbe8c880a0f4dfe8d63e041106071ed", size = 23173 }, + { url = "https://files.pythonhosted.org/packages/8d/85/864c663a924a34e0d87bd10ead4134bb4ab6269fa02daaa5dd644ac478c5/types_psutil-7.0.0.20250601-py3-none-any.whl", hash = "sha256:0c372e2d1b6529938a080a6ba4a9358e3dfc8526d82fabf40c1ef9325e4ca52e", size = 23106, upload-time = "2025-06-01T03:25:15.386Z" }, ] [[package]] name = "types-psycopg2" -version = "2.9.21.20250318" +version = "2.9.21.20250516" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/09/29/9e86192ffa0a7ffc48d222f510026ec92aa93c7321ee24128480553661ec/types_psycopg2-2.9.21.20250318.tar.gz", hash = "sha256:eb6eac5bfb16adfd5f16b818918b9e26a40ede147e0f2bbffdf53a6ef7025a87", size = 26614 } +sdist = { url = "https://files.pythonhosted.org/packages/68/55/3f94eff9d1a1402f39e19523a90117fe6c97d7fc61957e7ee3e3052c75e1/types_psycopg2-2.9.21.20250516.tar.gz", hash = "sha256:6721018279175cce10b9582202e2a2b4a0da667857ccf82a97691bdb5ecd610f", size = 26514, upload-time = "2025-05-16T03:07:45.786Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a7/9c/34da1d5c2fe53c91f3382f45e18c58141cebef38e7204f676a93d1af6a1c/types_psycopg2-2.9.21.20250318-py3-none-any.whl", hash = "sha256:7296d111ad950bbd2fc979a1ab0572acae69047f922280e77db657c00d2c79c0", size = 24939 }, + { url = "https://files.pythonhosted.org/packages/39/50/f5d74945ab09b9a3e966ad39027ac55998f917eca72ede7929eab962b5db/types_psycopg2-2.9.21.20250516-py3-none-any.whl", hash = "sha256:2a9212d1e5e507017b31486ce8147634d06b85d652769d7a2d91d53cb4edbd41", size = 24846, upload-time = "2025-05-16T03:07:44.849Z" }, ] [[package]] name = "types-pygments" -version = "2.19.0.20250305" +version = "2.19.0.20250516" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "types-docutils" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/e6/be/88f777c75022b111f9e9fe4cdb430bf92892fe90188b0fd037601ded2ea1/types_pygments-2.19.0.20250305.tar.gz", hash = "sha256:044c50e80ecd4128c00a7268f20355e16f5c55466d3d49dfda09be920af40b4b", size = 18521 } +sdist = { url = "https://files.pythonhosted.org/packages/71/9a/c1ea3f59001e9d13b93ec8acf02c75b47832423f17471295b8ceebc48a65/types_pygments-2.19.0.20250516.tar.gz", hash = "sha256:b53fd07e197f0e7be38ee19598bd99c78be5ca5f9940849c843be74a2f81ab58", size = 18485, upload-time = "2025-05-16T03:09:30.05Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/6f/c6/b6d3ad345b76425e46d25a2da1758603d80c3a59405bdcbbbaa86d8c8070/types_pygments-2.19.0.20250305-py3-none-any.whl", hash = "sha256:ca88aae5ec426f9b107c0f7adc36dc096d2882d930a49f679eaf4b8b643db35d", size = 25638 }, + { url = "https://files.pythonhosted.org/packages/a7/0b/32ce3ad35983bf4f603c43cfb00559b37bb5ed90ac4ef9f1d5564b8e4034/types_pygments-2.19.0.20250516-py3-none-any.whl", hash = "sha256:db27de8b59591389cd7d14792483892c021c73b8389ef55fef40a48aa371fbcc", size = 25440, upload-time = "2025-05-16T03:09:29.185Z" }, ] [[package]] name = "types-pymysql" -version = "1.1.0.20241103" +version = "1.1.0.20250516" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b3/ac/5a23decbcf53893df11636b7d61cc000a97b0ed45e09cee94d6c75f159ec/types-PyMySQL-1.1.0.20241103.tar.gz", hash = "sha256:a7628542919a0ba87625fb79eefb2a2de45fb4ad32afe6e561e8f2f27fb58b8c", size = 14987 } +sdist = { url = "https://files.pythonhosted.org/packages/db/11/cdaa90b82cb25c5e04e75f0b0616872aa5775b001096779375084f8dbbcf/types_pymysql-1.1.0.20250516.tar.gz", hash = "sha256:fea4a9776101cf893dfc868f42ce10d2e46dcc498c792cc7c9c0fe00cb744234", size = 19640, upload-time = "2025-05-16T03:06:54.568Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/3e/04/d02323dd4dfd6e0af4ecbb88a00215c37aa79894a2d158390700c84c8597/types_PyMySQL-1.1.0.20241103-py3-none-any.whl", hash = "sha256:1a32efd8a74b5bf74c4de92a86c1cc6edaf3802dcfd5546635ab501eb5e3c096", size = 15610 }, + { url = "https://files.pythonhosted.org/packages/ab/64/129656e04ddda35d69faae914ce67cf60d83407ddd7afdef1e7c50bbb74a/types_pymysql-1.1.0.20250516-py3-none-any.whl", hash = "sha256:41c87a832e3ff503d5120cc6cebd64f6dcb3c407d9580a98b2cb3e3bcd109aa6", size = 20328, upload-time = "2025-05-16T03:06:53.681Z" }, +] + +[[package]] +name = "types-pyopenssl" +version = "24.1.0.20240722" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cryptography" }, + { name = "types-cffi" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/93/29/47a346550fd2020dac9a7a6d033ea03fccb92fa47c726056618cc889745e/types-pyOpenSSL-24.1.0.20240722.tar.gz", hash = "sha256:47913b4678a01d879f503a12044468221ed8576263c1540dcb0484ca21b08c39", size = 8458, upload-time = "2024-07-22T02:32:22.558Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/98/05/c868a850b6fbb79c26f5f299b768ee0adc1f9816d3461dcf4287916f655b/types_pyOpenSSL-24.1.0.20240722-py3-none-any.whl", hash = "sha256:6a7a5d2ec042537934cfb4c9d4deb0e16c4c6250b09358df1f083682fe6fda54", size = 7499, upload-time = "2024-07-22T02:32:21.232Z" }, ] [[package]] name = "types-python-dateutil" -version = "2.9.0.20241206" +version = "2.9.0.20250516" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a9/60/47d92293d9bc521cd2301e423a358abfac0ad409b3a1606d8fbae1321961/types_python_dateutil-2.9.0.20241206.tar.gz", hash = "sha256:18f493414c26ffba692a72369fea7a154c502646301ebfe3d56a04b3767284cb", size = 13802 } +sdist = { url = "https://files.pythonhosted.org/packages/ef/88/d65ed807393285204ab6e2801e5d11fbbea811adcaa979a2ed3b67a5ef41/types_python_dateutil-2.9.0.20250516.tar.gz", hash = "sha256:13e80d6c9c47df23ad773d54b2826bd52dbbb41be87c3f339381c1700ad21ee5", size = 13943, upload-time = "2025-05-16T03:06:58.385Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/0f/b3/ca41df24db5eb99b00d97f89d7674a90cb6b3134c52fb8121b6d8d30f15c/types_python_dateutil-2.9.0.20241206-py3-none-any.whl", hash = "sha256:e248a4bc70a486d3e3ec84d0dc30eec3a5f979d6e7ee4123ae043eedbb987f53", size = 14384 }, + { url = "https://files.pythonhosted.org/packages/c5/3f/b0e8db149896005adc938a1e7f371d6d7e9eca4053a29b108978ed15e0c2/types_python_dateutil-2.9.0.20250516-py3-none-any.whl", hash = "sha256:2b2b3f57f9c6a61fba26a9c0ffb9ea5681c9b83e69cd897c6b5f668d9c0cab93", size = 14356, upload-time = "2025-05-16T03:06:57.249Z" }, ] [[package]] name = "types-pytz" -version = "2025.2.0.20250326" +version = "2025.2.0.20250516" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/4b/66/38c89861242f2c61c8315ddbcc7d7bbf64979f4b0bdc48db0ba62aeec330/types_pytz-2025.2.0.20250326.tar.gz", hash = "sha256:deda02de24f527066fc8d6a19e284ab3f3ae716a42b4adb6b40e75e408c08d36", size = 10595 } +sdist = { url = "https://files.pythonhosted.org/packages/bd/72/b0e711fd90409f5a76c75349055d3eb19992c110f0d2d6aabbd6cfbc14bf/types_pytz-2025.2.0.20250516.tar.gz", hash = "sha256:e1216306f8c0d5da6dafd6492e72eb080c9a166171fa80dd7a1990fd8be7a7b3", size = 10940, upload-time = "2025-05-16T03:07:01.91Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/4e/e0/17f3a6670db5c95dc195f346e2e7290f22ba8327c188133959389b578cbd/types_pytz-2025.2.0.20250326-py3-none-any.whl", hash = "sha256:3c397fd1b845cd2b3adc9398607764ced9e578a98a5d1fbb4a9bc9253edfb162", size = 10222 }, + { url = "https://files.pythonhosted.org/packages/c1/ba/e205cd11c1c7183b23c97e4bcd1de7bc0633e2e867601c32ecfc6ad42675/types_pytz-2025.2.0.20250516-py3-none-any.whl", hash = "sha256:e0e0c8a57e2791c19f718ed99ab2ba623856b11620cb6b637e5f62ce285a7451", size = 10136, upload-time = "2025-05-16T03:07:01.075Z" }, ] [[package]] name = "types-pywin32" -version = "310.0.0.20250319" +version = "310.0.0.20250516" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/63/3b/7a16e65d5a0866ad4cf798cc0b221746723128f81da6b5078862004493d1/types_pywin32-310.0.0.20250319.tar.gz", hash = "sha256:4d28fb85b3f268a92905a7242df48c530c847cfe4cdb112386101ab6407660d8", size = 327181 } +sdist = { url = "https://files.pythonhosted.org/packages/6c/bc/c7be2934a37cc8c645c945ca88450b541e482c4df3ac51e5556377d34811/types_pywin32-310.0.0.20250516.tar.gz", hash = "sha256:91e5bfc033f65c9efb443722eff8101e31d690dd9a540fa77525590d3da9cc9d", size = 328459, upload-time = "2025-05-16T03:07:57.411Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/8b/68/16447b609d968c65e63085f4c411e245c651ea3db4e5523973d038a7acfa/types_pywin32-310.0.0.20250319-py3-none-any.whl", hash = "sha256:baeb558a82251f7d430d135036b054740893902fdee3f9fe568322730ff49779", size = 389909 }, + { url = "https://files.pythonhosted.org/packages/9b/72/469e4cc32399dbe6c843e38fdb6d04fee755e984e137c0da502f74d3ac59/types_pywin32-310.0.0.20250516-py3-none-any.whl", hash = "sha256:f9ef83a1ec3e5aae2b0e24c5f55ab41272b5dfeaabb9a0451d33684c9545e41a", size = 390411, upload-time = "2025-05-16T03:07:56.282Z" }, ] [[package]] name = "types-pyyaml" -version = "6.0.12.20250402" +version = "6.0.12.20250516" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/2d/68/609eed7402f87c9874af39d35942744e39646d1ea9011765ec87b01b2a3c/types_pyyaml-6.0.12.20250402.tar.gz", hash = "sha256:d7c13c3e6d335b6af4b0122a01ff1d270aba84ab96d1a1a1063ecba3e13ec075", size = 17282 } +sdist = { url = "https://files.pythonhosted.org/packages/4e/22/59e2aeb48ceeee1f7cd4537db9568df80d62bdb44a7f9e743502ea8aab9c/types_pyyaml-6.0.12.20250516.tar.gz", hash = "sha256:9f21a70216fc0fa1b216a8176db5f9e0af6eb35d2f2932acb87689d03a5bf6ba", size = 17378, upload-time = "2025-05-16T03:08:04.897Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ed/56/1fe61db05685fbb512c07ea9323f06ea727125951f1eb4dff110b3311da3/types_pyyaml-6.0.12.20250402-py3-none-any.whl", hash = "sha256:652348fa9e7a203d4b0d21066dfb00760d3cbd5a15ebb7cf8d33c88a49546681", size = 20329 }, + { url = "https://files.pythonhosted.org/packages/99/5f/e0af6f7f6a260d9af67e1db4f54d732abad514252a7a378a6c4d17dd1036/types_pyyaml-6.0.12.20250516-py3-none-any.whl", hash = "sha256:8478208feaeb53a34cb5d970c56a7cd76b72659442e733e268a94dc72b2d0530", size = 20312, upload-time = "2025-05-16T03:08:04.019Z" }, ] [[package]] name = "types-regex" version = "2024.11.6.20250403" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/c7/75/012b90c8557d3abb3b58a9073a94d211c8f75c9b2e26bf0d8af7ecf7bc78/types_regex-2024.11.6.20250403.tar.gz", hash = "sha256:3fdf2a70bbf830de4b3a28e9649a52d43dabb57cdb18fbfe2252eefb53666665", size = 12394 } +sdist = { url = "https://files.pythonhosted.org/packages/c7/75/012b90c8557d3abb3b58a9073a94d211c8f75c9b2e26bf0d8af7ecf7bc78/types_regex-2024.11.6.20250403.tar.gz", hash = "sha256:3fdf2a70bbf830de4b3a28e9649a52d43dabb57cdb18fbfe2252eefb53666665", size = 12394, upload-time = "2025-04-03T02:54:35.379Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/61/49/67200c4708f557be6aa4ecdb1fa212d67a10558c5240251efdc799cca22f/types_regex-2024.11.6.20250403-py3-none-any.whl", hash = "sha256:e22c0f67d73f4b4af6086a340f387b6f7d03bed8a0bb306224b75c51a29b0001", size = 10396 }, + { url = "https://files.pythonhosted.org/packages/61/49/67200c4708f557be6aa4ecdb1fa212d67a10558c5240251efdc799cca22f/types_regex-2024.11.6.20250403-py3-none-any.whl", hash = "sha256:e22c0f67d73f4b4af6086a340f387b6f7d03bed8a0bb306224b75c51a29b0001", size = 10396, upload-time = "2025-04-03T02:54:34.555Z" }, ] [[package]] name = "types-requests" -version = "2.32.0.20250328" +version = "2.32.0.20250602" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "urllib3" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/00/7d/eb174f74e3f5634eaacb38031bbe467dfe2e545bc255e5c90096ec46bc46/types_requests-2.32.0.20250328.tar.gz", hash = "sha256:c9e67228ea103bd811c96984fac36ed2ae8da87a36a633964a21f199d60baf32", size = 22995 } +sdist = { url = "https://files.pythonhosted.org/packages/48/b0/5321e6eeba5d59e4347fcf9bf06a5052f085c3aa0f4876230566d6a4dc97/types_requests-2.32.0.20250602.tar.gz", hash = "sha256:ee603aeefec42051195ae62ca7667cd909a2f8128fdf8aad9e8a5219ecfab3bf", size = 23042, upload-time = "2025-06-02T03:15:02.958Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/cc/15/3700282a9d4ea3b37044264d3e4d1b1f0095a4ebf860a99914fd544e3be3/types_requests-2.32.0.20250328-py3-none-any.whl", hash = "sha256:72ff80f84b15eb3aa7a8e2625fffb6a93f2ad5a0c20215fc1dcfa61117bcb2a2", size = 20663 }, + { url = "https://files.pythonhosted.org/packages/da/18/9b782980e575c6581d5c0c1c99f4c6f89a1d7173dad072ee96b2756c02e6/types_requests-2.32.0.20250602-py3-none-any.whl", hash = "sha256:f4f335f87779b47ce10b8b8597b409130299f6971ead27fead4fe7ba6ea3e726", size = 20638, upload-time = "2025-06-02T03:15:01.959Z" }, ] [[package]] name = "types-requests-oauthlib" -version = "2.0.0.20250306" +version = "2.0.0.20250516" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "types-oauthlib" }, { name = "types-requests" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/52/52/acf02c00fb1005bbda26cd898b0bb5e832b0f2eb5b99a6fbab8e3edb30f2/types_requests_oauthlib-2.0.0.20250306.tar.gz", hash = "sha256:92e5f1ed35689b1804fdcd60b7ac39b0bd440a4b96693685879bc835b334797f", size = 11066 } +sdist = { url = "https://files.pythonhosted.org/packages/fc/7b/1803a83dbccf0698a9fb70a444d12f1dcb0f49a5d8a6327a1e53fac19e15/types_requests_oauthlib-2.0.0.20250516.tar.gz", hash = "sha256:2a384b6ca080bd1eb30a88e14836237dc43d217892fddf869f03aea65213e0d4", size = 11034, upload-time = "2025-05-16T03:09:45.119Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e8/3c/1bc76f1097cc4978cc97df11524f47559f8927fb2a2807375947bd185189/types_requests_oauthlib-2.0.0.20250516-py3-none-any.whl", hash = "sha256:faf417c259a3ae54c1b72c77032c07af3025ed90164c905fb785d21e8580139c", size = 14343, upload-time = "2025-05-16T03:09:43.874Z" }, +] + +[[package]] +name = "types-s3transfer" +version = "0.13.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/42/c1/45038f259d6741c252801044e184fec4dbaeff939a58f6160d7c32bf4975/types_s3transfer-0.13.0.tar.gz", hash = "sha256:203dadcb9865c2f68fb44bc0440e1dc05b79197ba4a641c0976c26c9af75ef52", size = 14175, upload-time = "2025-05-28T02:16:07.614Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c8/5d/6bbe4bf6a79fb727945291aef88b5ecbdba857a603f1bbcf1a6be0d3f442/types_s3transfer-0.13.0-py3-none-any.whl", hash = "sha256:79c8375cbf48a64bff7654c02df1ec4b20d74f8c5672fc13e382f593ca5565b3", size = 19588, upload-time = "2025-05-28T02:16:06.709Z" }, +] + +[[package]] +name = "types-setuptools" +version = "80.9.0.20250529" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/79/66/1b276526aad4696a9519919e637801f2c103419d2c248a6feb2729e034d1/types_setuptools-80.9.0.20250529.tar.gz", hash = "sha256:79e088ba0cba2186c8d6499cbd3e143abb142d28a44b042c28d3148b1e353c91", size = 41337, upload-time = "2025-05-29T03:07:34.487Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/54/b1/f4ba392a3341cd9d613f2dce855e82471073c5ec34996fe84ac3857956d0/types_requests_oauthlib-2.0.0.20250306-py3-none-any.whl", hash = "sha256:37707de81d9ce54894afcccd70d4a845dbe4c59e747908faaeba59a96453d993", size = 14446 }, + { url = "https://files.pythonhosted.org/packages/1b/d8/83790d67ec771bf029a45ff1bd1aedbb738d8aa58c09dd0cc3033eea0e69/types_setuptools-80.9.0.20250529-py3-none-any.whl", hash = "sha256:00dfcedd73e333a430e10db096e4d46af93faf9314f832f13b6bbe3d6757e95f", size = 63263, upload-time = "2025-05-29T03:07:33.064Z" }, ] [[package]] @@ -5658,71 +5966,71 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "numpy" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/4e/55/c71a25fd3fc9200df4d0b5fd2f6d74712a82f9a8bbdd90cefb9e6aee39dd/types_shapely-2.0.0.20250404.tar.gz", hash = "sha256:863f540b47fa626c33ae64eae06df171f9ab0347025d4458d2df496537296b4f", size = 25066 } +sdist = { url = "https://files.pythonhosted.org/packages/4e/55/c71a25fd3fc9200df4d0b5fd2f6d74712a82f9a8bbdd90cefb9e6aee39dd/types_shapely-2.0.0.20250404.tar.gz", hash = "sha256:863f540b47fa626c33ae64eae06df171f9ab0347025d4458d2df496537296b4f", size = 25066, upload-time = "2025-04-04T02:54:30.592Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ce/ff/7f4d414eb81534ba2476f3d54f06f1463c2ebf5d663fd10cff16ba607dd6/types_shapely-2.0.0.20250404-py3-none-any.whl", hash = "sha256:170fb92f5c168a120db39b3287697fdec5c93ef3e1ad15e52552c36b25318821", size = 36350 }, + { url = "https://files.pythonhosted.org/packages/ce/ff/7f4d414eb81534ba2476f3d54f06f1463c2ebf5d663fd10cff16ba607dd6/types_shapely-2.0.0.20250404-py3-none-any.whl", hash = "sha256:170fb92f5c168a120db39b3287697fdec5c93ef3e1ad15e52552c36b25318821", size = 36350, upload-time = "2025-04-04T02:54:29.506Z" }, ] [[package]] name = "types-simplejson" version = "3.20.0.20250326" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/af/14/e26fc55e1ea56f9ea470917d3e2f8240e6d043ca914181021d04115ae0f7/types_simplejson-3.20.0.20250326.tar.gz", hash = "sha256:b2689bc91e0e672d7a5a947b4cb546b76ae7ddc2899c6678e72a10bf96cd97d2", size = 10489 } +sdist = { url = "https://files.pythonhosted.org/packages/af/14/e26fc55e1ea56f9ea470917d3e2f8240e6d043ca914181021d04115ae0f7/types_simplejson-3.20.0.20250326.tar.gz", hash = "sha256:b2689bc91e0e672d7a5a947b4cb546b76ae7ddc2899c6678e72a10bf96cd97d2", size = 10489, upload-time = "2025-03-26T02:53:35.825Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/76/bf/d3f3a5ba47fd18115e8446d39f025b85905d2008677c29ee4d03b4cddd57/types_simplejson-3.20.0.20250326-py3-none-any.whl", hash = "sha256:db1ddea7b8f7623b27a137578f22fc6c618db8c83ccfb1828ca0d2f0ec11efa7", size = 10462 }, + { url = "https://files.pythonhosted.org/packages/76/bf/d3f3a5ba47fd18115e8446d39f025b85905d2008677c29ee4d03b4cddd57/types_simplejson-3.20.0.20250326-py3-none-any.whl", hash = "sha256:db1ddea7b8f7623b27a137578f22fc6c618db8c83ccfb1828ca0d2f0ec11efa7", size = 10462, upload-time = "2025-03-26T02:53:35.036Z" }, ] [[package]] name = "types-six" -version = "1.17.0.20250403" +version = "1.17.0.20250515" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/81/78/a711162eca091781522be07bf10694f2b731bbdbb885ec037a807e7658c5/types_six-1.17.0.20250403.tar.gz", hash = "sha256:82076f86e6e672a95adbf8b52625b1b3c72a8b9a893180344c1a02a6daabead6", size = 15521 } +sdist = { url = "https://files.pythonhosted.org/packages/cc/78/344047eeced8d230140aa3d9503aa969acb61c6095e7308bbc1ff1de3865/types_six-1.17.0.20250515.tar.gz", hash = "sha256:f4f7f0398cb79304e88397336e642b15e96fbeacf5b96d7625da366b069d2d18", size = 15598, upload-time = "2025-05-15T03:04:19.806Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a6/d1/14a6a959a3f53105034ebe346a1f3c375296637292780f6e952fcf634974/types_six-1.17.0.20250403-py3-none-any.whl", hash = "sha256:0bbb20fc34a18163afe7cac70b85864bd6937e6d73413c5b8f424def28760ae8", size = 19951 }, + { url = "https://files.pythonhosted.org/packages/d1/85/5ee1c8e35b33b9c8ea1816d5a4e119c27f8bb1539b73b1f636f07aa64750/types_six-1.17.0.20250515-py3-none-any.whl", hash = "sha256:adfaa9568caf35e03d80ffa4ed765c33b282579c869b40bf4b6009c7d8db3fb1", size = 19987, upload-time = "2025-05-15T03:04:18.556Z" }, ] [[package]] name = "types-tensorflow" -version = "2.18.0.20250404" +version = "2.18.0.20250516" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "numpy" }, { name = "types-protobuf" }, { name = "types-requests" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/71/00/bb410bcce70bf801c1082553809f03b6b75e0c3328958933255eb4a0d34d/types_tensorflow-2.18.0.20250404.tar.gz", hash = "sha256:b38a427bbec805e4879d248f070baea802673c04cc5ccbe5979d742faa160670", size = 253083 } +sdist = { url = "https://files.pythonhosted.org/packages/4b/18/b726d886e7af565c4439d2c8d32e510651be40807e2a66aaea2ed75d7c82/types_tensorflow-2.18.0.20250516.tar.gz", hash = "sha256:5777e1848e52b1f4a87b44ce1ec738b7407a744669bab87ec0f5f1e0ce6bd1fe", size = 257705, upload-time = "2025-05-16T03:09:41.222Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/43/a0/d666566b8d49e2fd19fc89e4f0f4363d19333b212d750f966c0ed0a7bd06/types_tensorflow-2.18.0.20250404-py3-none-any.whl", hash = "sha256:4ad86534e6cfd6b36b2c97239ef9d122c44b167b25630b7c873a1483f9befd15", size = 324931 }, + { url = "https://files.pythonhosted.org/packages/96/fd/0d8fbc7172fa7cca345c61a949952df8906f6da161dfbb4305c670aeabad/types_tensorflow-2.18.0.20250516-py3-none-any.whl", hash = "sha256:e8681f8c2a60f87f562df1472790c1e930895e7e463c4c65d1be98d8d908e45e", size = 329211, upload-time = "2025-05-16T03:09:40.111Z" }, ] [[package]] name = "types-tqdm" -version = "4.67.0.20250404" +version = "4.67.0.20250516" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "types-requests" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/f4/a1/75e0d6f96f8c34a1bad6c232566182f2ae53ffdf7ab9c75afb61b2a07354/types_tqdm-4.67.0.20250404.tar.gz", hash = "sha256:e9997c655ffbba3ab78f4418b5511c05a54e76824d073d212166dc73aa56c768", size = 17159 } +sdist = { url = "https://files.pythonhosted.org/packages/bd/07/eb40de2dc2ff2d1a53180330981b1bdb42313ab4e1b11195d8d64c878b3c/types_tqdm-4.67.0.20250516.tar.gz", hash = "sha256:230ccab8a332d34f193fc007eb132a6ef54b4512452e718bf21ae0a7caeb5a6b", size = 17232, upload-time = "2025-05-16T03:09:52.091Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/35/85/2c09e94d2554e8314cbc74f897c1b3012930b34a22f6905bb0b9fb79f40e/types_tqdm-4.67.0.20250404-py3-none-any.whl", hash = "sha256:4a9b897eb4036f757240f4cb4a794f296265c04de46fdd058e453891f0186eed", size = 24053 }, + { url = "https://files.pythonhosted.org/packages/3b/92/df621429f098fc573a63a8ba348e731c3051b397df0cff278f8887f28d24/types_tqdm-4.67.0.20250516-py3-none-any.whl", hash = "sha256:1dd9b2c65273f2342f37e5179bc6982df86b6669b3376efc12aef0a29e35d36d", size = 24032, upload-time = "2025-05-16T03:09:51.226Z" }, ] [[package]] name = "types-ujson" version = "5.10.0.20250326" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/cc/5c/c974451c4babdb4ae3588925487edde492d59a8403010b4642a554d09954/types_ujson-5.10.0.20250326.tar.gz", hash = "sha256:5469e05f2c31ecb3c4c0267cc8fe41bcd116826fbb4ded69801a645c687dd014", size = 8340 } +sdist = { url = "https://files.pythonhosted.org/packages/cc/5c/c974451c4babdb4ae3588925487edde492d59a8403010b4642a554d09954/types_ujson-5.10.0.20250326.tar.gz", hash = "sha256:5469e05f2c31ecb3c4c0267cc8fe41bcd116826fbb4ded69801a645c687dd014", size = 8340, upload-time = "2025-03-26T02:53:39.197Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/3e/c9/8a73a5f8fa6e70fc02eed506d5ac0ae9ceafbd2b8c9ad34a7de0f29900d6/types_ujson-5.10.0.20250326-py3-none-any.whl", hash = "sha256:acc0913f569def62ef6a892c8a47703f65d05669a3252391a97765cf207dca5b", size = 7644 }, + { url = "https://files.pythonhosted.org/packages/3e/c9/8a73a5f8fa6e70fc02eed506d5ac0ae9ceafbd2b8c9ad34a7de0f29900d6/types_ujson-5.10.0.20250326-py3-none-any.whl", hash = "sha256:acc0913f569def62ef6a892c8a47703f65d05669a3252391a97765cf207dca5b", size = 7644, upload-time = "2025-03-26T02:53:38.2Z" }, ] [[package]] name = "typing-extensions" -version = "4.13.1" +version = "4.14.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/76/ad/cd3e3465232ec2416ae9b983f27b9e94dc8171d56ac99b345319a9475967/typing_extensions-4.13.1.tar.gz", hash = "sha256:98795af00fb9640edec5b8e31fc647597b4691f099ad75f469a2616be1a76dff", size = 106633 } +sdist = { url = "https://files.pythonhosted.org/packages/d1/bc/51647cd02527e87d05cb083ccc402f93e441606ff1f01739a62c8ad09ba5/typing_extensions-4.14.0.tar.gz", hash = "sha256:8676b788e32f02ab42d9e7c61324048ae4c6d844a399eebace3d4979d75ceef4", size = 107423, upload-time = "2025-06-02T14:52:11.399Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/df/c5/e7a0b0f5ed69f94c8ab7379c599e6036886bffcde609969a5325f47f1332/typing_extensions-4.13.1-py3-none-any.whl", hash = "sha256:4b6cf02909eb5495cfbc3f6e8fd49217e6cc7944e145cdda8caa3734777f9e69", size = 45739 }, + { url = "https://files.pythonhosted.org/packages/69/e0/552843e0d356fbb5256d21449fa957fa4eff3bbc135a74a691ee70c7c5da/typing_extensions-4.14.0-py3-none-any.whl", hash = "sha256:a1514509136dd0b477638fc68d6a91497af5076466ad0fa6c338e44e359944af", size = 43839, upload-time = "2025-06-02T14:52:10.026Z" }, ] [[package]] @@ -5733,46 +6041,70 @@ dependencies = [ { name = "mypy-extensions" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/dc/74/1789779d91f1961fa9438e9a8710cdae6bd138c80d7303996933d117264a/typing_inspect-0.9.0.tar.gz", hash = "sha256:b23fc42ff6f6ef6954e4852c1fb512cdd18dbea03134f91f856a95ccc9461f78", size = 13825 } +sdist = { url = "https://files.pythonhosted.org/packages/dc/74/1789779d91f1961fa9438e9a8710cdae6bd138c80d7303996933d117264a/typing_inspect-0.9.0.tar.gz", hash = "sha256:b23fc42ff6f6ef6954e4852c1fb512cdd18dbea03134f91f856a95ccc9461f78", size = 13825, upload-time = "2023-05-24T20:25:47.612Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/65/f3/107a22063bf27bdccf2024833d3445f4eea42b2e598abfbd46f6a63b6cb0/typing_inspect-0.9.0-py3-none-any.whl", hash = "sha256:9ee6fc59062311ef8547596ab6b955e1b8aa46242d854bfc78f4f6b0eff35f9f", size = 8827 }, + { url = "https://files.pythonhosted.org/packages/65/f3/107a22063bf27bdccf2024833d3445f4eea42b2e598abfbd46f6a63b6cb0/typing_inspect-0.9.0-py3-none-any.whl", hash = "sha256:9ee6fc59062311ef8547596ab6b955e1b8aa46242d854bfc78f4f6b0eff35f9f", size = 8827, upload-time = "2023-05-24T20:25:45.287Z" }, +] + +[[package]] +name = "typing-inspection" +version = "0.4.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f8/b1/0c11f5058406b3af7609f121aaa6b609744687f1d158b3c3a5bf4cc94238/typing_inspection-0.4.1.tar.gz", hash = "sha256:6ae134cc0203c33377d43188d4064e9b357dba58cff3185f22924610e70a9d28", size = 75726, upload-time = "2025-05-21T18:55:23.885Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/17/69/cd203477f944c353c31bade965f880aa1061fd6bf05ded0726ca845b6ff7/typing_inspection-0.4.1-py3-none-any.whl", hash = "sha256:389055682238f53b04f7badcb49b989835495a96700ced5dab2d8feae4b26f51", size = 14552, upload-time = "2025-05-21T18:55:22.152Z" }, ] [[package]] name = "tzdata" version = "2025.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/95/32/1a225d6164441be760d75c2c42e2780dc0873fe382da3e98a2e1e48361e5/tzdata-2025.2.tar.gz", hash = "sha256:b60a638fcc0daffadf82fe0f57e53d06bdec2f36c4df66280ae79bce6bd6f2b9", size = 196380 } +sdist = { url = "https://files.pythonhosted.org/packages/95/32/1a225d6164441be760d75c2c42e2780dc0873fe382da3e98a2e1e48361e5/tzdata-2025.2.tar.gz", hash = "sha256:b60a638fcc0daffadf82fe0f57e53d06bdec2f36c4df66280ae79bce6bd6f2b9", size = 196380, upload-time = "2025-03-23T13:54:43.652Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5c/23/c7abc0ca0a1526a0774eca151daeb8de62ec457e77262b66b359c3c7679e/tzdata-2025.2-py2.py3-none-any.whl", hash = "sha256:1a403fada01ff9221ca8044d701868fa132215d84beb92242d9acd2147f667a8", size = 347839, upload-time = "2025-03-23T13:54:41.845Z" }, +] + +[[package]] +name = "tzlocal" +version = "5.3.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "tzdata", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/8b/2e/c14812d3d4d9cd1773c6be938f89e5735a1f11a9f184ac3639b93cef35d5/tzlocal-5.3.1.tar.gz", hash = "sha256:cceffc7edecefea1f595541dbd6e990cb1ea3d19bf01b2809f362a03dd7921fd", size = 30761, upload-time = "2025-03-05T21:17:41.549Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/5c/23/c7abc0ca0a1526a0774eca151daeb8de62ec457e77262b66b359c3c7679e/tzdata-2025.2-py2.py3-none-any.whl", hash = "sha256:1a403fada01ff9221ca8044d701868fa132215d84beb92242d9acd2147f667a8", size = 347839 }, + { url = "https://files.pythonhosted.org/packages/c2/14/e2a54fabd4f08cd7af1c07030603c3356b74da07f7cc056e600436edfa17/tzlocal-5.3.1-py3-none-any.whl", hash = "sha256:eb1a66c3ef5847adf7a834f1be0800581b683b5608e74f86ecbcef8ab91bb85d", size = 18026, upload-time = "2025-03-05T21:17:39.857Z" }, ] [[package]] name = "ujson" version = "5.9.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/6e/54/6f2bdac7117e89a47de4511c9f01732a283457ab1bf856e1e51aa861619e/ujson-5.9.0.tar.gz", hash = "sha256:89cc92e73d5501b8a7f48575eeb14ad27156ad092c2e9fc7e3cf949f07e75532", size = 7154214 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c0/ca/ae3a6ca5b4f82ce654d6ac3dde5e59520537e20939592061ba506f4e569a/ujson-5.9.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3b23bbb46334ce51ddb5dded60c662fbf7bb74a37b8f87221c5b0fec1ec6454b", size = 57753 }, - { url = "https://files.pythonhosted.org/packages/34/5f/c27fa9a1562c96d978c39852b48063c3ca480758f3088dcfc0f3b09f8e93/ujson-5.9.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6974b3a7c17bbf829e6c3bfdc5823c67922e44ff169851a755eab79a3dd31ec0", size = 54092 }, - { url = "https://files.pythonhosted.org/packages/19/f3/1431713de9e5992e5e33ba459b4de28f83904233958855d27da820a101f9/ujson-5.9.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b5964ea916edfe24af1f4cc68488448fbb1ec27a3ddcddc2b236da575c12c8ae", size = 51675 }, - { url = "https://files.pythonhosted.org/packages/d3/93/de6fff3ae06351f3b1c372f675fe69bc180f93d237c9e496c05802173dd6/ujson-5.9.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8ba7cac47dd65ff88571eceeff48bf30ed5eb9c67b34b88cb22869b7aa19600d", size = 53246 }, - { url = "https://files.pythonhosted.org/packages/26/73/db509fe1d7da62a15c0769c398cec66bdfc61a8bdffaf7dfa9d973e3d65c/ujson-5.9.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6bbd91a151a8f3358c29355a491e915eb203f607267a25e6ab10531b3b157c5e", size = 58182 }, - { url = "https://files.pythonhosted.org/packages/fc/a8/6be607fa3e1fa3e1c9b53f5de5acad33b073b6cc9145803e00bcafa729a8/ujson-5.9.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:829a69d451a49c0de14a9fecb2a2d544a9b2c884c2b542adb243b683a6f15908", size = 584493 }, - { url = "https://files.pythonhosted.org/packages/c8/c7/33822c2f1a8175e841e2bc378ffb2c1109ce9280f14cedb1b2fa0caf3145/ujson-5.9.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:a807ae73c46ad5db161a7e883eec0fbe1bebc6a54890152ccc63072c4884823b", size = 656038 }, - { url = "https://files.pythonhosted.org/packages/51/b8/5309fbb299d5fcac12bbf3db20896db5178392904abe6b992da233dc69d6/ujson-5.9.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:8fc2aa18b13d97b3c8ccecdf1a3c405f411a6e96adeee94233058c44ff92617d", size = 597643 }, - { url = "https://files.pythonhosted.org/packages/5f/64/7b63043b95dd78feed401b9973958af62645a6d19b72b6e83d1ea5af07e0/ujson-5.9.0-cp311-cp311-win32.whl", hash = "sha256:70e06849dfeb2548be48fdd3ceb53300640bc8100c379d6e19d78045e9c26120", size = 38342 }, - { url = "https://files.pythonhosted.org/packages/7a/13/a3cd1fc3a1126d30b558b6235c05e2d26eeaacba4979ee2fd2b5745c136d/ujson-5.9.0-cp311-cp311-win_amd64.whl", hash = "sha256:7309d063cd392811acc49b5016728a5e1b46ab9907d321ebbe1c2156bc3c0b99", size = 41923 }, - { url = "https://files.pythonhosted.org/packages/16/7e/c37fca6cd924931fa62d615cdbf5921f34481085705271696eff38b38867/ujson-5.9.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:20509a8c9f775b3a511e308bbe0b72897ba6b800767a7c90c5cca59d20d7c42c", size = 57834 }, - { url = "https://files.pythonhosted.org/packages/fb/44/2753e902ee19bf6ccaf0bda02f1f0037f92a9769a5d31319905e3de645b4/ujson-5.9.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b28407cfe315bd1b34f1ebe65d3bd735d6b36d409b334100be8cdffae2177b2f", size = 54119 }, - { url = "https://files.pythonhosted.org/packages/d2/06/2317433e394450bc44afe32b6c39d5a51014da4c6f6cfc2ae7bf7b4a2922/ujson-5.9.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9d302bd17989b6bd90d49bade66943c78f9e3670407dbc53ebcf61271cadc399", size = 51658 }, - { url = "https://files.pythonhosted.org/packages/5b/3a/2acf0da085d96953580b46941504aa3c91a1dd38701b9e9bfa43e2803467/ujson-5.9.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9f21315f51e0db8ee245e33a649dd2d9dce0594522de6f278d62f15f998e050e", size = 53370 }, - { url = "https://files.pythonhosted.org/packages/03/32/737e6c4b1841720f88ae88ec91f582dc21174bd40742739e1fa16a0c9ffa/ujson-5.9.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5635b78b636a54a86fdbf6f027e461aa6c6b948363bdf8d4fbb56a42b7388320", size = 58278 }, - { url = "https://files.pythonhosted.org/packages/8a/dc/3fda97f1ad070ccf2af597fb67dde358bc698ffecebe3bc77991d60e4fe5/ujson-5.9.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:82b5a56609f1235d72835ee109163c7041b30920d70fe7dac9176c64df87c164", size = 584418 }, - { url = "https://files.pythonhosted.org/packages/d7/57/e4083d774fcd8ff3089c0ff19c424abe33f23e72c6578a8172bf65131992/ujson-5.9.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:5ca35f484622fd208f55041b042d9d94f3b2c9c5add4e9af5ee9946d2d30db01", size = 656126 }, - { url = "https://files.pythonhosted.org/packages/0d/c3/8c6d5f6506ca9fcedd5a211e30a7d5ee053dc05caf23dae650e1f897effb/ujson-5.9.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:829b824953ebad76d46e4ae709e940bb229e8999e40881338b3cc94c771b876c", size = 597795 }, - { url = "https://files.pythonhosted.org/packages/34/5a/a231f0cd305a34cf2d16930304132db3a7a8c3997b367dd38fc8f8dfae36/ujson-5.9.0-cp312-cp312-win32.whl", hash = "sha256:25fa46e4ff0a2deecbcf7100af3a5d70090b461906f2299506485ff31d9ec437", size = 38495 }, - { url = "https://files.pythonhosted.org/packages/30/b7/18b841b44760ed298acdb150608dccdc045c41655e0bae4441f29bcab872/ujson-5.9.0-cp312-cp312-win_amd64.whl", hash = "sha256:60718f1720a61560618eff3b56fd517d107518d3c0160ca7a5a66ac949c6cf1c", size = 42088 }, +sdist = { url = "https://files.pythonhosted.org/packages/6e/54/6f2bdac7117e89a47de4511c9f01732a283457ab1bf856e1e51aa861619e/ujson-5.9.0.tar.gz", hash = "sha256:89cc92e73d5501b8a7f48575eeb14ad27156ad092c2e9fc7e3cf949f07e75532", size = 7154214, upload-time = "2023-12-10T22:50:34.812Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c0/ca/ae3a6ca5b4f82ce654d6ac3dde5e59520537e20939592061ba506f4e569a/ujson-5.9.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3b23bbb46334ce51ddb5dded60c662fbf7bb74a37b8f87221c5b0fec1ec6454b", size = 57753, upload-time = "2023-12-10T22:49:03.939Z" }, + { url = "https://files.pythonhosted.org/packages/34/5f/c27fa9a1562c96d978c39852b48063c3ca480758f3088dcfc0f3b09f8e93/ujson-5.9.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6974b3a7c17bbf829e6c3bfdc5823c67922e44ff169851a755eab79a3dd31ec0", size = 54092, upload-time = "2023-12-10T22:49:05.194Z" }, + { url = "https://files.pythonhosted.org/packages/19/f3/1431713de9e5992e5e33ba459b4de28f83904233958855d27da820a101f9/ujson-5.9.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b5964ea916edfe24af1f4cc68488448fbb1ec27a3ddcddc2b236da575c12c8ae", size = 51675, upload-time = "2023-12-10T22:49:06.449Z" }, + { url = "https://files.pythonhosted.org/packages/d3/93/de6fff3ae06351f3b1c372f675fe69bc180f93d237c9e496c05802173dd6/ujson-5.9.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8ba7cac47dd65ff88571eceeff48bf30ed5eb9c67b34b88cb22869b7aa19600d", size = 53246, upload-time = "2023-12-10T22:49:07.691Z" }, + { url = "https://files.pythonhosted.org/packages/26/73/db509fe1d7da62a15c0769c398cec66bdfc61a8bdffaf7dfa9d973e3d65c/ujson-5.9.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6bbd91a151a8f3358c29355a491e915eb203f607267a25e6ab10531b3b157c5e", size = 58182, upload-time = "2023-12-10T22:49:08.89Z" }, + { url = "https://files.pythonhosted.org/packages/fc/a8/6be607fa3e1fa3e1c9b53f5de5acad33b073b6cc9145803e00bcafa729a8/ujson-5.9.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:829a69d451a49c0de14a9fecb2a2d544a9b2c884c2b542adb243b683a6f15908", size = 584493, upload-time = "2023-12-10T22:49:11.043Z" }, + { url = "https://files.pythonhosted.org/packages/c8/c7/33822c2f1a8175e841e2bc378ffb2c1109ce9280f14cedb1b2fa0caf3145/ujson-5.9.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:a807ae73c46ad5db161a7e883eec0fbe1bebc6a54890152ccc63072c4884823b", size = 656038, upload-time = "2023-12-10T22:49:12.651Z" }, + { url = "https://files.pythonhosted.org/packages/51/b8/5309fbb299d5fcac12bbf3db20896db5178392904abe6b992da233dc69d6/ujson-5.9.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:8fc2aa18b13d97b3c8ccecdf1a3c405f411a6e96adeee94233058c44ff92617d", size = 597643, upload-time = "2023-12-10T22:49:14.883Z" }, + { url = "https://files.pythonhosted.org/packages/5f/64/7b63043b95dd78feed401b9973958af62645a6d19b72b6e83d1ea5af07e0/ujson-5.9.0-cp311-cp311-win32.whl", hash = "sha256:70e06849dfeb2548be48fdd3ceb53300640bc8100c379d6e19d78045e9c26120", size = 38342, upload-time = "2023-12-10T22:49:16.854Z" }, + { url = "https://files.pythonhosted.org/packages/7a/13/a3cd1fc3a1126d30b558b6235c05e2d26eeaacba4979ee2fd2b5745c136d/ujson-5.9.0-cp311-cp311-win_amd64.whl", hash = "sha256:7309d063cd392811acc49b5016728a5e1b46ab9907d321ebbe1c2156bc3c0b99", size = 41923, upload-time = "2023-12-10T22:49:17.983Z" }, + { url = "https://files.pythonhosted.org/packages/16/7e/c37fca6cd924931fa62d615cdbf5921f34481085705271696eff38b38867/ujson-5.9.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:20509a8c9f775b3a511e308bbe0b72897ba6b800767a7c90c5cca59d20d7c42c", size = 57834, upload-time = "2023-12-10T22:49:19.799Z" }, + { url = "https://files.pythonhosted.org/packages/fb/44/2753e902ee19bf6ccaf0bda02f1f0037f92a9769a5d31319905e3de645b4/ujson-5.9.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b28407cfe315bd1b34f1ebe65d3bd735d6b36d409b334100be8cdffae2177b2f", size = 54119, upload-time = "2023-12-10T22:49:21.039Z" }, + { url = "https://files.pythonhosted.org/packages/d2/06/2317433e394450bc44afe32b6c39d5a51014da4c6f6cfc2ae7bf7b4a2922/ujson-5.9.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9d302bd17989b6bd90d49bade66943c78f9e3670407dbc53ebcf61271cadc399", size = 51658, upload-time = "2023-12-10T22:49:22.494Z" }, + { url = "https://files.pythonhosted.org/packages/5b/3a/2acf0da085d96953580b46941504aa3c91a1dd38701b9e9bfa43e2803467/ujson-5.9.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9f21315f51e0db8ee245e33a649dd2d9dce0594522de6f278d62f15f998e050e", size = 53370, upload-time = "2023-12-10T22:49:24.045Z" }, + { url = "https://files.pythonhosted.org/packages/03/32/737e6c4b1841720f88ae88ec91f582dc21174bd40742739e1fa16a0c9ffa/ujson-5.9.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5635b78b636a54a86fdbf6f027e461aa6c6b948363bdf8d4fbb56a42b7388320", size = 58278, upload-time = "2023-12-10T22:49:25.261Z" }, + { url = "https://files.pythonhosted.org/packages/8a/dc/3fda97f1ad070ccf2af597fb67dde358bc698ffecebe3bc77991d60e4fe5/ujson-5.9.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:82b5a56609f1235d72835ee109163c7041b30920d70fe7dac9176c64df87c164", size = 584418, upload-time = "2023-12-10T22:49:27.573Z" }, + { url = "https://files.pythonhosted.org/packages/d7/57/e4083d774fcd8ff3089c0ff19c424abe33f23e72c6578a8172bf65131992/ujson-5.9.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:5ca35f484622fd208f55041b042d9d94f3b2c9c5add4e9af5ee9946d2d30db01", size = 656126, upload-time = "2023-12-10T22:49:29.509Z" }, + { url = "https://files.pythonhosted.org/packages/0d/c3/8c6d5f6506ca9fcedd5a211e30a7d5ee053dc05caf23dae650e1f897effb/ujson-5.9.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:829b824953ebad76d46e4ae709e940bb229e8999e40881338b3cc94c771b876c", size = 597795, upload-time = "2023-12-10T22:49:31.029Z" }, + { url = "https://files.pythonhosted.org/packages/34/5a/a231f0cd305a34cf2d16930304132db3a7a8c3997b367dd38fc8f8dfae36/ujson-5.9.0-cp312-cp312-win32.whl", hash = "sha256:25fa46e4ff0a2deecbcf7100af3a5d70090b461906f2299506485ff31d9ec437", size = 38495, upload-time = "2023-12-10T22:49:33.2Z" }, + { url = "https://files.pythonhosted.org/packages/30/b7/18b841b44760ed298acdb150608dccdc045c41655e0bae4441f29bcab872/ujson-5.9.0-cp312-cp312-win_amd64.whl", hash = "sha256:60718f1720a61560618eff3b56fd517d107518d3c0160ca7a5a66ac949c6cf1c", size = 42088, upload-time = "2023-12-10T22:49:34.921Z" }, ] [[package]] @@ -5802,9 +6134,9 @@ dependencies = [ { name = "unstructured-client" }, { name = "wrapt" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/64/31/98c4c78e305d1294888adf87fd5ee30577a4c393951341ca32b43f167f1e/unstructured-0.16.25.tar.gz", hash = "sha256:73b9b0f51dbb687af572ecdb849a6811710b9cac797ddeab8ee80fa07d8aa5e6", size = 1683097 } +sdist = { url = "https://files.pythonhosted.org/packages/64/31/98c4c78e305d1294888adf87fd5ee30577a4c393951341ca32b43f167f1e/unstructured-0.16.25.tar.gz", hash = "sha256:73b9b0f51dbb687af572ecdb849a6811710b9cac797ddeab8ee80fa07d8aa5e6", size = 1683097, upload-time = "2025-03-07T11:19:39.507Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/12/4f/ad08585b5c8a33c82ea119494c4d3023f4796958c56e668b15cc282ec0a0/unstructured-0.16.25-py3-none-any.whl", hash = "sha256:14719ccef2830216cf1c5bf654f75e2bf07b17ca5dcee9da5ac74618130fd337", size = 1769286 }, + { url = "https://files.pythonhosted.org/packages/12/4f/ad08585b5c8a33c82ea119494c4d3023f4796958c56e668b15cc282ec0a0/unstructured-0.16.25-py3-none-any.whl", hash = "sha256:14719ccef2830216cf1c5bf654f75e2bf07b17ca5dcee9da5ac74618130fd337", size = 1769286, upload-time = "2025-03-07T11:19:37.299Z" }, ] [package.optional-dependencies] @@ -5826,24 +6158,20 @@ pptx = [ [[package]] name = "unstructured-client" -version = "0.28.1" +version = "0.36.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "aiofiles" }, { name = "cryptography" }, - { name = "eval-type-backport" }, { name = "httpx" }, - { name = "jsonpath-python" }, { name = "nest-asyncio" }, { name = "pydantic" }, { name = "pypdf" }, - { name = "python-dateutil" }, { name = "requests-toolbelt" }, - { name = "typing-inspect" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c3/a1/8b0bf11e8c092aeb704b579f5855b5cfb0d5278a6c542312cddad5a8097e/unstructured_client-0.28.1.tar.gz", hash = "sha256:aac11fe5dd6b8dfdbc15aad3205fe791a3834dac29bb9f499fd515643554f709", size = 48607 } +sdist = { url = "https://files.pythonhosted.org/packages/9d/4d/d829dbef1138251de771cd52b277d93fb1c4e79d56be3e44e6d2ce76bd62/unstructured_client-0.36.0.tar.gz", hash = "sha256:ab293498100275c0e1d74c926c82dae2b3ba3fbb88945c0ba03b4b7a29197e4a", size = 86010, upload-time = "2025-05-29T00:11:11.429Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/09/69/38e51f6ce07f2a454f53a364f6ef9acdbfc73841fe736d9c7cd152525048/unstructured_client-0.28.1-py3-none-any.whl", hash = "sha256:0112688908f544681a67abf314e0d2023dfa120c8e5d9fa6d31390b914a06d72", size = 62865 }, + { url = "https://files.pythonhosted.org/packages/b9/4a/ae162e583bbdd0996f92ad18871a737d710260d8c0cfd78f1be6aa0ac150/unstructured_client-0.36.0-py3-none-any.whl", hash = "sha256:d0ecf3ac4d481437d858147904ff6e41205032cf8353af5cdd3ebaa190481d6a", size = 195765, upload-time = "2025-05-29T00:11:09.677Z" }, ] [[package]] @@ -5853,49 +6181,70 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "httpx" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/94/a6/a9178fef247687917701a60eb66542eb5361c58af40c033ba8174ff7366d/upstash_vector-0.6.0.tar.gz", hash = "sha256:a716ed4d0251362208518db8b194158a616d37d1ccbb1155f619df690599e39b", size = 15075 } +sdist = { url = "https://files.pythonhosted.org/packages/94/a6/a9178fef247687917701a60eb66542eb5361c58af40c033ba8174ff7366d/upstash_vector-0.6.0.tar.gz", hash = "sha256:a716ed4d0251362208518db8b194158a616d37d1ccbb1155f619df690599e39b", size = 15075, upload-time = "2024-09-27T12:02:13.533Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/5d/45/95073b83b7fd7b83f10ea314f197bae3989bfe022e736b90145fe9ea4362/upstash_vector-0.6.0-py3-none-any.whl", hash = "sha256:d0bdad7765b8a7f5c205b7a9c81ca4b9a4cee3ee4952afc7d5ea5fb76c3f3c3c", size = 15061 }, + { url = "https://files.pythonhosted.org/packages/5d/45/95073b83b7fd7b83f10ea314f197bae3989bfe022e736b90145fe9ea4362/upstash_vector-0.6.0-py3-none-any.whl", hash = "sha256:d0bdad7765b8a7f5c205b7a9c81ca4b9a4cee3ee4952afc7d5ea5fb76c3f3c3c", size = 15061, upload-time = "2024-09-27T12:02:12.041Z" }, ] [[package]] name = "uritemplate" -version = "4.1.1" +version = "4.2.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d2/5a/4742fdba39cd02a56226815abfa72fe0aa81c33bed16ed045647d6000eba/uritemplate-4.1.1.tar.gz", hash = "sha256:4346edfc5c3b79f694bccd6d6099a322bbeb628dbf2cd86eea55a456ce5124f0", size = 273898 } +sdist = { url = "https://files.pythonhosted.org/packages/98/60/f174043244c5306c9988380d2cb10009f91563fc4b31293d27e17201af56/uritemplate-4.2.0.tar.gz", hash = "sha256:480c2ed180878955863323eea31b0ede668795de182617fef9c6ca09e6ec9d0e", size = 33267, upload-time = "2025-06-02T15:12:06.318Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/81/c0/7461b49cd25aeece13766f02ee576d1db528f1c37ce69aee300e075b485b/uritemplate-4.1.1-py2.py3-none-any.whl", hash = "sha256:830c08b8d99bdd312ea4ead05994a38e8936266f84b9a7878232db50b044e02e", size = 10356 }, + { url = "https://files.pythonhosted.org/packages/a9/99/3ae339466c9183ea5b8ae87b34c0b897eda475d2aec2307cae60e5cd4f29/uritemplate-4.2.0-py3-none-any.whl", hash = "sha256:962201ba1c4edcab02e60f9a0d3821e82dfc5d2d6662a21abd533879bdb8a686", size = 11488, upload-time = "2025-06-02T15:12:03.405Z" }, ] [[package]] name = "urllib3" -version = "2.3.0" +version = "2.4.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/aa/63/e53da845320b757bf29ef6a9062f5c669fe997973f966045cb019c3f4b66/urllib3-2.3.0.tar.gz", hash = "sha256:f8c5449b3cf0861679ce7e0503c7b44b5ec981bec0d1d3795a07f1ba96f0204d", size = 307268 } +sdist = { url = "https://files.pythonhosted.org/packages/8a/78/16493d9c386d8e60e442a35feac5e00f0913c0f4b7c217c11e8ec2ff53e0/urllib3-2.4.0.tar.gz", hash = "sha256:414bc6535b787febd7567804cc015fee39daab8ad86268f1310a9250697de466", size = 390672, upload-time = "2025-04-10T15:23:39.232Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c8/19/4ec628951a74043532ca2cf5d97b7b14863931476d117c471e8e2b1eb39f/urllib3-2.3.0-py3-none-any.whl", hash = "sha256:1cee9ad369867bfdbbb48b7dd50374c0967a0bb7710050facf0dd6911440e3df", size = 128369 }, + { url = "https://files.pythonhosted.org/packages/6b/11/cc635220681e93a0183390e26485430ca2c7b5f9d33b15c74c2861cb8091/urllib3-2.4.0-py3-none-any.whl", hash = "sha256:4e16665048960a0900c702d4a66415956a584919c03361cac9f1df5c5dd7e813", size = 128680, upload-time = "2025-04-10T15:23:37.377Z" }, +] + +[[package]] +name = "uuid-utils" +version = "0.11.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/24/7f/7d83b937889d65682d95b40c94ba226b353d3f532290ee3acb17c8746e49/uuid_utils-0.11.0.tar.gz", hash = "sha256:18cf2b7083da7f3cca0517647213129eb16d20d7ed0dd74b3f4f8bff2aa334ea", size = 18854, upload-time = "2025-05-22T11:23:15.596Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d2/20/4a34f2a6e77b1f0f3334b111e4d2411fc8646ab2987892a36507e2d6a498/uuid_utils-0.11.0-cp39-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:094445ccd323bc5507e28e9d6d86b983513efcf19ab59c2dd75239cef765631a", size = 593779, upload-time = "2025-05-22T11:22:41.36Z" }, + { url = "https://files.pythonhosted.org/packages/a1/a1/1897cd3d37144f698392ec8aae89da2c00c6d34acd77f75312477f4510ab/uuid_utils-0.11.0-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:6430b53d343215f85269ffd74e1d1f4b25ae1031acf0ac24ff3d5721f6a06f48", size = 300848, upload-time = "2025-05-22T11:22:43.221Z" }, + { url = "https://files.pythonhosted.org/packages/d4/36/3ae8896de8a5320a9e7529452ed29af0082daf8c3787f17c5cbf9defc651/uuid_utils-0.11.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:be2e6e4318d23195887fa74fa1d64565a34f7127fdcf22918954981d79765f68", size = 336053, upload-time = "2025-05-22T11:22:44.741Z" }, + { url = "https://files.pythonhosted.org/packages/fe/b6/751e84cd056074a40ca9ac21db6ca4802e31d78207309c0d9c8ff69cd43b/uuid_utils-0.11.0-cp39-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d37289ab72aa30b5550bfa64d91431c62c89e4969bdf989988aa97f918d5f803", size = 338529, upload-time = "2025-05-22T11:22:46.303Z" }, + { url = "https://files.pythonhosted.org/packages/3b/c2/f6a1c00a1b067a886fc57c24da46bb0bcb753c92afb898871c6df3ae606f/uuid_utils-0.11.0-cp39-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1012595220f945fe09641f1365a8a06915bf432cac1b31ebd262944934a9b787", size = 480378, upload-time = "2025-05-22T11:22:47.482Z" }, + { url = "https://files.pythonhosted.org/packages/60/ea/cefc0521e07a35e85416d145382ac4817957cdec037271d0c9e27cbc7d45/uuid_utils-0.11.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:35cd3fc718a673e4516e87afb9325558969eca513aa734515b9031d1b651bbb1", size = 332220, upload-time = "2025-05-22T11:22:48.55Z" }, + { url = "https://files.pythonhosted.org/packages/03/91/5929f209bd4660a7e3b4d47d26189d3cf33e14297312a5f51f5451805fec/uuid_utils-0.11.0-cp39-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ed325e0c40e0f59ae82b347f534df954b50cedf12bf60d025625538530e1965d", size = 359052, upload-time = "2025-05-22T11:22:49.617Z" }, + { url = "https://files.pythonhosted.org/packages/d8/0d/32034d5b13bc07dd95f23122cb743b4eeca8e6d88173ea3c7100c67b6269/uuid_utils-0.11.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:5c8b7cf201990ee3140956e541967bd556a7365ec738cb504b04187ad89c757a", size = 515186, upload-time = "2025-05-22T11:22:50.808Z" }, + { url = "https://files.pythonhosted.org/packages/e7/43/ccf2474f723d6de5e214c22999ffb34219acf83d1e3fff6a4734172e10c0/uuid_utils-0.11.0-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:9966df55bed5d538ba2e9cc40115796480f437f9007727116ef99dc2f42bd5fa", size = 535318, upload-time = "2025-05-22T11:22:52.304Z" }, + { url = "https://files.pythonhosted.org/packages/fb/05/f668b4ad2b3542cd021c4b27d1ff4e425f854f299bcf7ee36f304399a58c/uuid_utils-0.11.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:cb04b6c604968424b7e6398d54debbdd5b771b39fc1e648c6eabf3f1dc20582e", size = 502691, upload-time = "2025-05-22T11:22:53.483Z" }, + { url = "https://files.pythonhosted.org/packages/9e/0b/b906301638eef837c89b19206989dbe27794c591d794ecc06167d9a47c41/uuid_utils-0.11.0-cp39-abi3-win32.whl", hash = "sha256:18420eb3316bb514f09f2da15750ac135478c3a12a704e2c5fb59eab642bb255", size = 180147, upload-time = "2025-05-22T11:22:54.598Z" }, + { url = "https://files.pythonhosted.org/packages/56/99/ad24ee5ecfc5fbd4a4490bb59c0e72ce604d5eef08683d345546ff6a6f2d/uuid_utils-0.11.0-cp39-abi3-win_amd64.whl", hash = "sha256:37c4805af61a7cce899597d34e7c3dd5cb6a8b4b93a90fbca3826b071ba544df", size = 183574, upload-time = "2025-05-22T11:22:55.581Z" }, + { url = "https://files.pythonhosted.org/packages/0e/76/2301b1d34defc8c234596ffb6e6d456cd7ef061d108e10a14ceda5ec5d4b/uuid_utils-0.11.0-cp39-abi3-win_arm64.whl", hash = "sha256:4065cf17bbe97f6d8ccc7dc6a0bae7d28fd4797d7f32028a5abd979aeb7bf7c9", size = 181014, upload-time = "2025-05-22T11:22:56.575Z" }, ] [[package]] name = "uuid6" version = "2024.7.10" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/2c/56/2560a9f1ccab9e12b1b3478a3c870796cf4d8ee5652bb19b61751cced14a/uuid6-2024.7.10.tar.gz", hash = "sha256:2d29d7f63f593caaeea0e0d0dd0ad8129c9c663b29e19bdf882e864bedf18fb0", size = 8705 } +sdist = { url = "https://files.pythonhosted.org/packages/2c/56/2560a9f1ccab9e12b1b3478a3c870796cf4d8ee5652bb19b61751cced14a/uuid6-2024.7.10.tar.gz", hash = "sha256:2d29d7f63f593caaeea0e0d0dd0ad8129c9c663b29e19bdf882e864bedf18fb0", size = 8705, upload-time = "2024-07-10T16:39:37.592Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d3/3e/4ae6af487ce5781ed71d5fe10aca72e7cbc4d4f45afc31b120287082a8dd/uuid6-2024.7.10-py3-none-any.whl", hash = "sha256:93432c00ba403751f722829ad21759ff9db051dea140bf81493271e8e4dd18b7", size = 6376 }, + { url = "https://files.pythonhosted.org/packages/d3/3e/4ae6af487ce5781ed71d5fe10aca72e7cbc4d4f45afc31b120287082a8dd/uuid6-2024.7.10-py3-none-any.whl", hash = "sha256:93432c00ba403751f722829ad21759ff9db051dea140bf81493271e8e4dd18b7", size = 6376, upload-time = "2024-07-10T16:39:36.148Z" }, ] [[package]] name = "uvicorn" -version = "0.34.0" +version = "0.34.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "click" }, { name = "h11" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/4b/4d/938bd85e5bf2edeec766267a5015ad969730bb91e31b44021dfe8b22df6c/uvicorn-0.34.0.tar.gz", hash = "sha256:404051050cd7e905de2c9a7e61790943440b3416f49cb409f965d9dcd0fa73e9", size = 76568 } +sdist = { url = "https://files.pythonhosted.org/packages/de/ad/713be230bcda622eaa35c28f0d328c3675c371238470abdea52417f17a8e/uvicorn-0.34.3.tar.gz", hash = "sha256:35919a9a979d7a59334b6b10e05d77c1d0d574c50e0fc98b8b1a0f165708b55a", size = 76631, upload-time = "2025-06-01T07:48:17.531Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/61/14/33a3a1352cfa71812a3a21e8c9bfb83f60b0011f5e36f2b1399d51928209/uvicorn-0.34.0-py3-none-any.whl", hash = "sha256:023dc038422502fa28a09c7a30bf2b6991512da7dcdb8fd35fe57cfc154126f4", size = 62315 }, + { url = "https://files.pythonhosted.org/packages/6d/0d/8adfeaa62945f90d19ddc461c55f4a50c258af7662d34b6a3d5d1f8646f6/uvicorn-0.34.3-py3-none-any.whl", hash = "sha256:16246631db62bdfbf069b0645177d6e8a77ba950cfedbfd093acef9444e4d885", size = 62431, upload-time = "2025-06-01T07:48:15.664Z" }, ] [package.optional-dependencies] @@ -5913,38 +6262,38 @@ standard = [ name = "uvloop" version = "0.21.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/af/c0/854216d09d33c543f12a44b393c402e89a920b1a0a7dc634c42de91b9cf6/uvloop-0.21.0.tar.gz", hash = "sha256:3bf12b0fda68447806a7ad847bfa591613177275d35b6724b1ee573faa3704e3", size = 2492741 } +sdist = { url = "https://files.pythonhosted.org/packages/af/c0/854216d09d33c543f12a44b393c402e89a920b1a0a7dc634c42de91b9cf6/uvloop-0.21.0.tar.gz", hash = "sha256:3bf12b0fda68447806a7ad847bfa591613177275d35b6724b1ee573faa3704e3", size = 2492741, upload-time = "2024-10-14T23:38:35.489Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/57/a7/4cf0334105c1160dd6819f3297f8700fda7fc30ab4f61fbf3e725acbc7cc/uvloop-0.21.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c0f3fa6200b3108919f8bdabb9a7f87f20e7097ea3c543754cabc7d717d95cf8", size = 1447410 }, - { url = "https://files.pythonhosted.org/packages/8c/7c/1517b0bbc2dbe784b563d6ab54f2ef88c890fdad77232c98ed490aa07132/uvloop-0.21.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0878c2640cf341b269b7e128b1a5fed890adc4455513ca710d77d5e93aa6d6a0", size = 805476 }, - { url = "https://files.pythonhosted.org/packages/ee/ea/0bfae1aceb82a503f358d8d2fa126ca9dbdb2ba9c7866974faec1cb5875c/uvloop-0.21.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b9fb766bb57b7388745d8bcc53a359b116b8a04c83a2288069809d2b3466c37e", size = 3960855 }, - { url = "https://files.pythonhosted.org/packages/8a/ca/0864176a649838b838f36d44bf31c451597ab363b60dc9e09c9630619d41/uvloop-0.21.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8a375441696e2eda1c43c44ccb66e04d61ceeffcd76e4929e527b7fa401b90fb", size = 3973185 }, - { url = "https://files.pythonhosted.org/packages/30/bf/08ad29979a936d63787ba47a540de2132169f140d54aa25bc8c3df3e67f4/uvloop-0.21.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:baa0e6291d91649c6ba4ed4b2f982f9fa165b5bbd50a9e203c416a2797bab3c6", size = 3820256 }, - { url = "https://files.pythonhosted.org/packages/da/e2/5cf6ef37e3daf2f06e651aae5ea108ad30df3cb269102678b61ebf1fdf42/uvloop-0.21.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4509360fcc4c3bd2c70d87573ad472de40c13387f5fda8cb58350a1d7475e58d", size = 3937323 }, - { url = "https://files.pythonhosted.org/packages/8c/4c/03f93178830dc7ce8b4cdee1d36770d2f5ebb6f3d37d354e061eefc73545/uvloop-0.21.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:359ec2c888397b9e592a889c4d72ba3d6befba8b2bb01743f72fffbde663b59c", size = 1471284 }, - { url = "https://files.pythonhosted.org/packages/43/3e/92c03f4d05e50f09251bd8b2b2b584a2a7f8fe600008bcc4523337abe676/uvloop-0.21.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f7089d2dc73179ce5ac255bdf37c236a9f914b264825fdaacaded6990a7fb4c2", size = 821349 }, - { url = "https://files.pythonhosted.org/packages/a6/ef/a02ec5da49909dbbfb1fd205a9a1ac4e88ea92dcae885e7c961847cd51e2/uvloop-0.21.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:baa4dcdbd9ae0a372f2167a207cd98c9f9a1ea1188a8a526431eef2f8116cc8d", size = 4580089 }, - { url = "https://files.pythonhosted.org/packages/06/a7/b4e6a19925c900be9f98bec0a75e6e8f79bb53bdeb891916609ab3958967/uvloop-0.21.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:86975dca1c773a2c9864f4c52c5a55631038e387b47eaf56210f873887b6c8dc", size = 4693770 }, - { url = "https://files.pythonhosted.org/packages/ce/0c/f07435a18a4b94ce6bd0677d8319cd3de61f3a9eeb1e5f8ab4e8b5edfcb3/uvloop-0.21.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:461d9ae6660fbbafedd07559c6a2e57cd553b34b0065b6550685f6653a98c1cb", size = 4451321 }, - { url = "https://files.pythonhosted.org/packages/8f/eb/f7032be105877bcf924709c97b1bf3b90255b4ec251f9340cef912559f28/uvloop-0.21.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:183aef7c8730e54c9a3ee3227464daed66e37ba13040bb3f350bc2ddc040f22f", size = 4659022 }, + { url = "https://files.pythonhosted.org/packages/57/a7/4cf0334105c1160dd6819f3297f8700fda7fc30ab4f61fbf3e725acbc7cc/uvloop-0.21.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c0f3fa6200b3108919f8bdabb9a7f87f20e7097ea3c543754cabc7d717d95cf8", size = 1447410, upload-time = "2024-10-14T23:37:33.612Z" }, + { url = "https://files.pythonhosted.org/packages/8c/7c/1517b0bbc2dbe784b563d6ab54f2ef88c890fdad77232c98ed490aa07132/uvloop-0.21.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0878c2640cf341b269b7e128b1a5fed890adc4455513ca710d77d5e93aa6d6a0", size = 805476, upload-time = "2024-10-14T23:37:36.11Z" }, + { url = "https://files.pythonhosted.org/packages/ee/ea/0bfae1aceb82a503f358d8d2fa126ca9dbdb2ba9c7866974faec1cb5875c/uvloop-0.21.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b9fb766bb57b7388745d8bcc53a359b116b8a04c83a2288069809d2b3466c37e", size = 3960855, upload-time = "2024-10-14T23:37:37.683Z" }, + { url = "https://files.pythonhosted.org/packages/8a/ca/0864176a649838b838f36d44bf31c451597ab363b60dc9e09c9630619d41/uvloop-0.21.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8a375441696e2eda1c43c44ccb66e04d61ceeffcd76e4929e527b7fa401b90fb", size = 3973185, upload-time = "2024-10-14T23:37:40.226Z" }, + { url = "https://files.pythonhosted.org/packages/30/bf/08ad29979a936d63787ba47a540de2132169f140d54aa25bc8c3df3e67f4/uvloop-0.21.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:baa0e6291d91649c6ba4ed4b2f982f9fa165b5bbd50a9e203c416a2797bab3c6", size = 3820256, upload-time = "2024-10-14T23:37:42.839Z" }, + { url = "https://files.pythonhosted.org/packages/da/e2/5cf6ef37e3daf2f06e651aae5ea108ad30df3cb269102678b61ebf1fdf42/uvloop-0.21.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4509360fcc4c3bd2c70d87573ad472de40c13387f5fda8cb58350a1d7475e58d", size = 3937323, upload-time = "2024-10-14T23:37:45.337Z" }, + { url = "https://files.pythonhosted.org/packages/8c/4c/03f93178830dc7ce8b4cdee1d36770d2f5ebb6f3d37d354e061eefc73545/uvloop-0.21.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:359ec2c888397b9e592a889c4d72ba3d6befba8b2bb01743f72fffbde663b59c", size = 1471284, upload-time = "2024-10-14T23:37:47.833Z" }, + { url = "https://files.pythonhosted.org/packages/43/3e/92c03f4d05e50f09251bd8b2b2b584a2a7f8fe600008bcc4523337abe676/uvloop-0.21.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f7089d2dc73179ce5ac255bdf37c236a9f914b264825fdaacaded6990a7fb4c2", size = 821349, upload-time = "2024-10-14T23:37:50.149Z" }, + { url = "https://files.pythonhosted.org/packages/a6/ef/a02ec5da49909dbbfb1fd205a9a1ac4e88ea92dcae885e7c961847cd51e2/uvloop-0.21.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:baa4dcdbd9ae0a372f2167a207cd98c9f9a1ea1188a8a526431eef2f8116cc8d", size = 4580089, upload-time = "2024-10-14T23:37:51.703Z" }, + { url = "https://files.pythonhosted.org/packages/06/a7/b4e6a19925c900be9f98bec0a75e6e8f79bb53bdeb891916609ab3958967/uvloop-0.21.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:86975dca1c773a2c9864f4c52c5a55631038e387b47eaf56210f873887b6c8dc", size = 4693770, upload-time = "2024-10-14T23:37:54.122Z" }, + { url = "https://files.pythonhosted.org/packages/ce/0c/f07435a18a4b94ce6bd0677d8319cd3de61f3a9eeb1e5f8ab4e8b5edfcb3/uvloop-0.21.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:461d9ae6660fbbafedd07559c6a2e57cd553b34b0065b6550685f6653a98c1cb", size = 4451321, upload-time = "2024-10-14T23:37:55.766Z" }, + { url = "https://files.pythonhosted.org/packages/8f/eb/f7032be105877bcf924709c97b1bf3b90255b4ec251f9340cef912559f28/uvloop-0.21.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:183aef7c8730e54c9a3ee3227464daed66e37ba13040bb3f350bc2ddc040f22f", size = 4659022, upload-time = "2024-10-14T23:37:58.195Z" }, ] [[package]] name = "validators" -version = "0.21.0" +version = "0.35.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/1f/c5/4095e7a5a6fecc2eca953ad058a3609135d833f986f84951f7e26790d651/validators-0.21.0.tar.gz", hash = "sha256:245b98ab778ed9352a7269c6a8f6c2a839bed5b2a7e3e60273ce399d247dd4b3", size = 20937 } +sdist = { url = "https://files.pythonhosted.org/packages/53/66/a435d9ae49850b2f071f7ebd8119dd4e84872b01630d6736761e6e7fd847/validators-0.35.0.tar.gz", hash = "sha256:992d6c48a4e77c81f1b4daba10d16c3a9bb0dbb79b3a19ea847ff0928e70497a", size = 73399, upload-time = "2025-05-01T05:42:06.7Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ad/50/18dbf2ac594234ee6249bfe3425fa424c18eeb96f29dcd47f199ed6c51bc/validators-0.21.0-py3-none-any.whl", hash = "sha256:3470db6f2384c49727ee319afa2e97aec3f8fad736faa6067e0fd7f9eaf2c551", size = 27686 }, + { url = "https://files.pythonhosted.org/packages/fa/6e/3e955517e22cbdd565f2f8b2e73d52528b14b8bcfdb04f62466b071de847/validators-0.35.0-py3-none-any.whl", hash = "sha256:e8c947097eae7892cb3d26868d637f79f47b4a0554bc6b80065dfe5aac3705dd", size = 44712, upload-time = "2025-05-01T05:42:04.203Z" }, ] [[package]] name = "vine" version = "5.1.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/bd/e4/d07b5f29d283596b9727dd5275ccbceb63c44a1a82aa9e4bfd20426762ac/vine-5.1.0.tar.gz", hash = "sha256:8b62e981d35c41049211cf62a0a1242d8c1ee9bd15bb196ce38aefd6799e61e0", size = 48980 } +sdist = { url = "https://files.pythonhosted.org/packages/bd/e4/d07b5f29d283596b9727dd5275ccbceb63c44a1a82aa9e4bfd20426762ac/vine-5.1.0.tar.gz", hash = "sha256:8b62e981d35c41049211cf62a0a1242d8c1ee9bd15bb196ce38aefd6799e61e0", size = 48980, upload-time = "2023-11-05T08:46:53.857Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/03/ff/7c0c86c43b3cbb927e0ccc0255cb4057ceba4799cd44ae95174ce8e8b5b2/vine-5.1.0-py3-none-any.whl", hash = "sha256:40fdf3c48b2cfe1c38a49e9ae2da6fda88e4794c810050a728bd7413811fb1dc", size = 9636 }, + { url = "https://files.pythonhosted.org/packages/03/ff/7c0c86c43b3cbb927e0ccc0255cb4057ceba4799cd44ae95174ce8e8b5b2/vine-5.1.0-py3-none-any.whl", hash = "sha256:40fdf3c48b2cfe1c38a49e9ae2da6fda88e4794c810050a728bd7413811fb1dc", size = 9636, upload-time = "2023-11-05T08:46:51.205Z" }, ] [[package]] @@ -5960,9 +6309,42 @@ dependencies = [ { name = "retry" }, { name = "six" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/8a/c5/62f2fbf0359b31d4e8f766e9ee3096c23d08fc294df1ab6ac117c2d1440c/volcengine_compat-1.0.156.tar.gz", hash = "sha256:e357d096828e31a202dc6047bbc5bf6fff3f54a98cd35a99ab5f965ea741a267", size = 329616 } +sdist = { url = "https://files.pythonhosted.org/packages/8a/c5/62f2fbf0359b31d4e8f766e9ee3096c23d08fc294df1ab6ac117c2d1440c/volcengine_compat-1.0.156.tar.gz", hash = "sha256:e357d096828e31a202dc6047bbc5bf6fff3f54a98cd35a99ab5f965ea741a267", size = 329616, upload-time = "2024-10-13T09:19:09.149Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/37/da/7ccbe82470dc27e1cfd0466dc637248be906eb8447c28a40c1c74cf617ee/volcengine_compat-1.0.156-py3-none-any.whl", hash = "sha256:4abc149a7601ebad8fa2d28fab50c7945145cf74daecb71bca797b0bdc82c5a5", size = 677272 }, + { url = "https://files.pythonhosted.org/packages/37/da/7ccbe82470dc27e1cfd0466dc637248be906eb8447c28a40c1c74cf617ee/volcengine_compat-1.0.156-py3-none-any.whl", hash = "sha256:4abc149a7601ebad8fa2d28fab50c7945145cf74daecb71bca797b0bdc82c5a5", size = 677272, upload-time = "2024-10-13T09:17:19.944Z" }, +] + +[[package]] +name = "wandb" +version = "0.19.11" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "docker-pycreds" }, + { name = "gitpython" }, + { name = "platformdirs" }, + { name = "protobuf" }, + { name = "psutil" }, + { name = "pydantic" }, + { name = "pyyaml" }, + { name = "requests" }, + { name = "sentry-sdk" }, + { name = "setproctitle" }, + { name = "setuptools" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/39/98/0ff2925a21b998d4b84731429f4554ca3d9b5cad42c09c075e7306c3aca0/wandb-0.19.11.tar.gz", hash = "sha256:3f50a27dfadbb25946a513ffe856c0e8e538b5626ef207aa50b00c3b0356bff8", size = 39511477, upload-time = "2025-05-07T20:50:01.341Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4f/2c/f8bab58c73fdde4442f1baffd9ea5d1bb3113906a97a27e8d9ab72db7a69/wandb-0.19.11-py3-none-any.whl", hash = "sha256:ff3bf050ba25ebae7aedc9a775ffab90c28068832edfe5458423f488c2558f82", size = 6481327, upload-time = "2025-05-07T20:49:33.461Z" }, + { url = "https://files.pythonhosted.org/packages/45/4a/34b364280f690f4c6d7660f528fba9f13bdecabc4c869d266a4632cf836e/wandb-0.19.11-py3-none-macosx_10_14_x86_64.whl", hash = "sha256:0823fd9aa6343f40c04e01959997ca8c6d6adf1bd81c8d45261fa4915f1c6b67", size = 20555751, upload-time = "2025-05-07T20:49:36.392Z" }, + { url = "https://files.pythonhosted.org/packages/d8/e6/a27868fdb83a60df37b9d15e52c3353dd88d74442f27ae48cf765c6b9554/wandb-0.19.11-py3-none-macosx_11_0_arm64.whl", hash = "sha256:c758ef5439599d9023db5b3cf1698477055d82f9fae48af2779f63f1d289167c", size = 20377587, upload-time = "2025-05-07T20:49:39.126Z" }, + { url = "https://files.pythonhosted.org/packages/21/f7/d5cf5b58c2b3015364c7b2b6af6a440cbeda4103b67332e1e64b30f6252d/wandb-0.19.11-py3-none-macosx_11_0_x86_64.whl", hash = "sha256:de2dfd4911e7691735e271654c735e7b90cdee9d29a3796fbf06e9e92d48f3d7", size = 20985041, upload-time = "2025-05-07T20:49:41.571Z" }, + { url = "https://files.pythonhosted.org/packages/68/06/8b827f16a0b8f18002d2fffa7c5a7fd447946e0d0c68aeec0dd7eb18cdd3/wandb-0.19.11-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cfff738850770d26b13f8f3fe400a6456f1e39e87f3f29d5aa241b249476df95", size = 20017696, upload-time = "2025-05-07T20:49:44.04Z" }, + { url = "https://files.pythonhosted.org/packages/f9/31/eeb2878b26566c04c3e9b8b20b3ec3c54a2be50535088d36a37c008e07a3/wandb-0.19.11-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e8ff673007448df11cc69379ae0df28ead866800dc1ec7bc151b402db0bbcf40", size = 21425857, upload-time = "2025-05-07T20:49:46.347Z" }, + { url = "https://files.pythonhosted.org/packages/10/30/08988360678ae78334bb16625c28260fcaba49f500b89f8766807cb74d71/wandb-0.19.11-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:858bc5023fa1b3285d89d15f62be78afdb28301064daa49ea3f4ebde5dcedad2", size = 20023145, upload-time = "2025-05-07T20:49:48.965Z" }, + { url = "https://files.pythonhosted.org/packages/c8/e9/a639c42c8ca517c4d25e8970d64d0c5a9bd35b784faed5f47d9cca3dcd12/wandb-0.19.11-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:90e4b57649896acb16c3dd41b3093df1a169c2f1d94ff15d76af86b8a60dcdac", size = 21504842, upload-time = "2025-05-07T20:49:51.628Z" }, + { url = "https://files.pythonhosted.org/packages/44/74/dbe9277dd935b77dd16939cdf15357766fec0813a6e336cf5f1d07eb016e/wandb-0.19.11-py3-none-win32.whl", hash = "sha256:38dea43c7926d8800405a73b80b9adfe81eb315fc6f2ac6885c77eb966634421", size = 20767584, upload-time = "2025-05-07T20:49:56.629Z" }, + { url = "https://files.pythonhosted.org/packages/36/d5/215cac3edec5c5ac6e7231beb9d22466d5d4e4a132fa3a1d044f7d682c15/wandb-0.19.11-py3-none-win_amd64.whl", hash = "sha256:73402003c56ddc2198878492ab2bff55bb49bce5587eae5960e737d27c0c48f7", size = 20767588, upload-time = "2025-05-07T20:49:58.85Z" }, ] [[package]] @@ -5972,107 +6354,139 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/03/e2/8ed598c42057de7aa5d97c472254af4906ff0a59a66699d426fc9ef795d7/watchfiles-1.0.5.tar.gz", hash = "sha256:b7529b5dcc114679d43827d8c35a07c493ad6f083633d573d81c660abc5979e9", size = 94537 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/39/f4/41b591f59021786ef517e1cdc3b510383551846703e03f204827854a96f8/watchfiles-1.0.5-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:237f9be419e977a0f8f6b2e7b0475ababe78ff1ab06822df95d914a945eac827", size = 405336 }, - { url = "https://files.pythonhosted.org/packages/ae/06/93789c135be4d6d0e4f63e96eea56dc54050b243eacc28439a26482b5235/watchfiles-1.0.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e0da39ff917af8b27a4bdc5a97ac577552a38aac0d260a859c1517ea3dc1a7c4", size = 395977 }, - { url = "https://files.pythonhosted.org/packages/d2/db/1cd89bd83728ca37054512d4d35ab69b5f12b8aa2ac9be3b0276b3bf06cc/watchfiles-1.0.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2cfcb3952350e95603f232a7a15f6c5f86c5375e46f0bd4ae70d43e3e063c13d", size = 455232 }, - { url = "https://files.pythonhosted.org/packages/40/90/d8a4d44ffe960517e487c9c04f77b06b8abf05eb680bed71c82b5f2cad62/watchfiles-1.0.5-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:68b2dddba7a4e6151384e252a5632efcaa9bc5d1c4b567f3cb621306b2ca9f63", size = 459151 }, - { url = "https://files.pythonhosted.org/packages/6c/da/267a1546f26465dead1719caaba3ce660657f83c9d9c052ba98fb8856e13/watchfiles-1.0.5-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:95cf944fcfc394c5f9de794ce581914900f82ff1f855326f25ebcf24d5397418", size = 489054 }, - { url = "https://files.pythonhosted.org/packages/b1/31/33850dfd5c6efb6f27d2465cc4c6b27c5a6f5ed53c6fa63b7263cf5f60f6/watchfiles-1.0.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ecf6cd9f83d7c023b1aba15d13f705ca7b7d38675c121f3cc4a6e25bd0857ee9", size = 523955 }, - { url = "https://files.pythonhosted.org/packages/09/84/b7d7b67856efb183a421f1416b44ca975cb2ea6c4544827955dfb01f7dc2/watchfiles-1.0.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:852de68acd6212cd6d33edf21e6f9e56e5d98c6add46f48244bd479d97c967c6", size = 502234 }, - { url = "https://files.pythonhosted.org/packages/71/87/6dc5ec6882a2254cfdd8b0718b684504e737273903b65d7338efaba08b52/watchfiles-1.0.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d5730f3aa35e646103b53389d5bc77edfbf578ab6dab2e005142b5b80a35ef25", size = 454750 }, - { url = "https://files.pythonhosted.org/packages/3d/6c/3786c50213451a0ad15170d091570d4a6554976cf0df19878002fc96075a/watchfiles-1.0.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:18b3bd29954bc4abeeb4e9d9cf0b30227f0f206c86657674f544cb032296acd5", size = 631591 }, - { url = "https://files.pythonhosted.org/packages/1b/b3/1427425ade4e359a0deacce01a47a26024b2ccdb53098f9d64d497f6684c/watchfiles-1.0.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:ba5552a1b07c8edbf197055bc9d518b8f0d98a1c6a73a293bc0726dce068ed01", size = 625370 }, - { url = "https://files.pythonhosted.org/packages/15/ba/f60e053b0b5b8145d682672024aa91370a29c5c921a88977eb565de34086/watchfiles-1.0.5-cp311-cp311-win32.whl", hash = "sha256:2f1fefb2e90e89959447bc0420fddd1e76f625784340d64a2f7d5983ef9ad246", size = 277791 }, - { url = "https://files.pythonhosted.org/packages/50/ed/7603c4e164225c12c0d4e8700b64bb00e01a6c4eeea372292a3856be33a4/watchfiles-1.0.5-cp311-cp311-win_amd64.whl", hash = "sha256:b6e76ceb1dd18c8e29c73f47d41866972e891fc4cc7ba014f487def72c1cf096", size = 291622 }, - { url = "https://files.pythonhosted.org/packages/a2/c2/99bb7c96b4450e36877fde33690ded286ff555b5a5c1d925855d556968a1/watchfiles-1.0.5-cp311-cp311-win_arm64.whl", hash = "sha256:266710eb6fddc1f5e51843c70e3bebfb0f5e77cf4f27129278c70554104d19ed", size = 283699 }, - { url = "https://files.pythonhosted.org/packages/2a/8c/4f0b9bdb75a1bfbd9c78fad7d8854369283f74fe7cf03eb16be77054536d/watchfiles-1.0.5-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:b5eb568c2aa6018e26da9e6c86f3ec3fd958cee7f0311b35c2630fa4217d17f2", size = 401511 }, - { url = "https://files.pythonhosted.org/packages/dc/4e/7e15825def77f8bd359b6d3f379f0c9dac4eb09dd4ddd58fd7d14127179c/watchfiles-1.0.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0a04059f4923ce4e856b4b4e5e783a70f49d9663d22a4c3b3298165996d1377f", size = 392715 }, - { url = "https://files.pythonhosted.org/packages/58/65/b72fb817518728e08de5840d5d38571466c1b4a3f724d190cec909ee6f3f/watchfiles-1.0.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e380c89983ce6e6fe2dd1e1921b9952fb4e6da882931abd1824c092ed495dec", size = 454138 }, - { url = "https://files.pythonhosted.org/packages/3e/a4/86833fd2ea2e50ae28989f5950b5c3f91022d67092bfec08f8300d8b347b/watchfiles-1.0.5-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fe43139b2c0fdc4a14d4f8d5b5d967f7a2777fd3d38ecf5b1ec669b0d7e43c21", size = 458592 }, - { url = "https://files.pythonhosted.org/packages/38/7e/42cb8df8be9a37e50dd3a818816501cf7a20d635d76d6bd65aae3dbbff68/watchfiles-1.0.5-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ee0822ce1b8a14fe5a066f93edd20aada932acfe348bede8aa2149f1a4489512", size = 487532 }, - { url = "https://files.pythonhosted.org/packages/fc/fd/13d26721c85d7f3df6169d8b495fcac8ab0dc8f0945ebea8845de4681dab/watchfiles-1.0.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a0dbcb1c2d8f2ab6e0a81c6699b236932bd264d4cef1ac475858d16c403de74d", size = 522865 }, - { url = "https://files.pythonhosted.org/packages/a1/0d/7f9ae243c04e96c5455d111e21b09087d0eeaf9a1369e13a01c7d3d82478/watchfiles-1.0.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a2014a2b18ad3ca53b1f6c23f8cd94a18ce930c1837bd891262c182640eb40a6", size = 499887 }, - { url = "https://files.pythonhosted.org/packages/8e/0f/a257766998e26aca4b3acf2ae97dff04b57071e991a510857d3799247c67/watchfiles-1.0.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10f6ae86d5cb647bf58f9f655fcf577f713915a5d69057a0371bc257e2553234", size = 454498 }, - { url = "https://files.pythonhosted.org/packages/81/79/8bf142575a03e0af9c3d5f8bcae911ee6683ae93a625d349d4ecf4c8f7df/watchfiles-1.0.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:1a7bac2bde1d661fb31f4d4e8e539e178774b76db3c2c17c4bb3e960a5de07a2", size = 630663 }, - { url = "https://files.pythonhosted.org/packages/f1/80/abe2e79f610e45c63a70d271caea90c49bbf93eb00fa947fa9b803a1d51f/watchfiles-1.0.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4ab626da2fc1ac277bbf752446470b367f84b50295264d2d313e28dc4405d663", size = 625410 }, - { url = "https://files.pythonhosted.org/packages/91/6f/bc7fbecb84a41a9069c2c6eb6319f7f7df113adf113e358c57fc1aff7ff5/watchfiles-1.0.5-cp312-cp312-win32.whl", hash = "sha256:9f4571a783914feda92018ef3901dab8caf5b029325b5fe4558c074582815249", size = 277965 }, - { url = "https://files.pythonhosted.org/packages/99/a5/bf1c297ea6649ec59e935ab311f63d8af5faa8f0b86993e3282b984263e3/watchfiles-1.0.5-cp312-cp312-win_amd64.whl", hash = "sha256:360a398c3a19672cf93527f7e8d8b60d8275119c5d900f2e184d32483117a705", size = 291693 }, - { url = "https://files.pythonhosted.org/packages/7f/7b/fd01087cc21db5c47e5beae507b87965db341cce8a86f9eb12bf5219d4e0/watchfiles-1.0.5-cp312-cp312-win_arm64.whl", hash = "sha256:1a2902ede862969077b97523987c38db28abbe09fb19866e711485d9fbf0d417", size = 283287 }, +sdist = { url = "https://files.pythonhosted.org/packages/03/e2/8ed598c42057de7aa5d97c472254af4906ff0a59a66699d426fc9ef795d7/watchfiles-1.0.5.tar.gz", hash = "sha256:b7529b5dcc114679d43827d8c35a07c493ad6f083633d573d81c660abc5979e9", size = 94537, upload-time = "2025-04-08T10:36:26.722Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/39/f4/41b591f59021786ef517e1cdc3b510383551846703e03f204827854a96f8/watchfiles-1.0.5-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:237f9be419e977a0f8f6b2e7b0475ababe78ff1ab06822df95d914a945eac827", size = 405336, upload-time = "2025-04-08T10:34:59.359Z" }, + { url = "https://files.pythonhosted.org/packages/ae/06/93789c135be4d6d0e4f63e96eea56dc54050b243eacc28439a26482b5235/watchfiles-1.0.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e0da39ff917af8b27a4bdc5a97ac577552a38aac0d260a859c1517ea3dc1a7c4", size = 395977, upload-time = "2025-04-08T10:35:00.522Z" }, + { url = "https://files.pythonhosted.org/packages/d2/db/1cd89bd83728ca37054512d4d35ab69b5f12b8aa2ac9be3b0276b3bf06cc/watchfiles-1.0.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2cfcb3952350e95603f232a7a15f6c5f86c5375e46f0bd4ae70d43e3e063c13d", size = 455232, upload-time = "2025-04-08T10:35:01.698Z" }, + { url = "https://files.pythonhosted.org/packages/40/90/d8a4d44ffe960517e487c9c04f77b06b8abf05eb680bed71c82b5f2cad62/watchfiles-1.0.5-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:68b2dddba7a4e6151384e252a5632efcaa9bc5d1c4b567f3cb621306b2ca9f63", size = 459151, upload-time = "2025-04-08T10:35:03.358Z" }, + { url = "https://files.pythonhosted.org/packages/6c/da/267a1546f26465dead1719caaba3ce660657f83c9d9c052ba98fb8856e13/watchfiles-1.0.5-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:95cf944fcfc394c5f9de794ce581914900f82ff1f855326f25ebcf24d5397418", size = 489054, upload-time = "2025-04-08T10:35:04.561Z" }, + { url = "https://files.pythonhosted.org/packages/b1/31/33850dfd5c6efb6f27d2465cc4c6b27c5a6f5ed53c6fa63b7263cf5f60f6/watchfiles-1.0.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ecf6cd9f83d7c023b1aba15d13f705ca7b7d38675c121f3cc4a6e25bd0857ee9", size = 523955, upload-time = "2025-04-08T10:35:05.786Z" }, + { url = "https://files.pythonhosted.org/packages/09/84/b7d7b67856efb183a421f1416b44ca975cb2ea6c4544827955dfb01f7dc2/watchfiles-1.0.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:852de68acd6212cd6d33edf21e6f9e56e5d98c6add46f48244bd479d97c967c6", size = 502234, upload-time = "2025-04-08T10:35:07.187Z" }, + { url = "https://files.pythonhosted.org/packages/71/87/6dc5ec6882a2254cfdd8b0718b684504e737273903b65d7338efaba08b52/watchfiles-1.0.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d5730f3aa35e646103b53389d5bc77edfbf578ab6dab2e005142b5b80a35ef25", size = 454750, upload-time = "2025-04-08T10:35:08.859Z" }, + { url = "https://files.pythonhosted.org/packages/3d/6c/3786c50213451a0ad15170d091570d4a6554976cf0df19878002fc96075a/watchfiles-1.0.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:18b3bd29954bc4abeeb4e9d9cf0b30227f0f206c86657674f544cb032296acd5", size = 631591, upload-time = "2025-04-08T10:35:10.64Z" }, + { url = "https://files.pythonhosted.org/packages/1b/b3/1427425ade4e359a0deacce01a47a26024b2ccdb53098f9d64d497f6684c/watchfiles-1.0.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:ba5552a1b07c8edbf197055bc9d518b8f0d98a1c6a73a293bc0726dce068ed01", size = 625370, upload-time = "2025-04-08T10:35:12.412Z" }, + { url = "https://files.pythonhosted.org/packages/15/ba/f60e053b0b5b8145d682672024aa91370a29c5c921a88977eb565de34086/watchfiles-1.0.5-cp311-cp311-win32.whl", hash = "sha256:2f1fefb2e90e89959447bc0420fddd1e76f625784340d64a2f7d5983ef9ad246", size = 277791, upload-time = "2025-04-08T10:35:13.719Z" }, + { url = "https://files.pythonhosted.org/packages/50/ed/7603c4e164225c12c0d4e8700b64bb00e01a6c4eeea372292a3856be33a4/watchfiles-1.0.5-cp311-cp311-win_amd64.whl", hash = "sha256:b6e76ceb1dd18c8e29c73f47d41866972e891fc4cc7ba014f487def72c1cf096", size = 291622, upload-time = "2025-04-08T10:35:15.071Z" }, + { url = "https://files.pythonhosted.org/packages/a2/c2/99bb7c96b4450e36877fde33690ded286ff555b5a5c1d925855d556968a1/watchfiles-1.0.5-cp311-cp311-win_arm64.whl", hash = "sha256:266710eb6fddc1f5e51843c70e3bebfb0f5e77cf4f27129278c70554104d19ed", size = 283699, upload-time = "2025-04-08T10:35:16.732Z" }, + { url = "https://files.pythonhosted.org/packages/2a/8c/4f0b9bdb75a1bfbd9c78fad7d8854369283f74fe7cf03eb16be77054536d/watchfiles-1.0.5-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:b5eb568c2aa6018e26da9e6c86f3ec3fd958cee7f0311b35c2630fa4217d17f2", size = 401511, upload-time = "2025-04-08T10:35:17.956Z" }, + { url = "https://files.pythonhosted.org/packages/dc/4e/7e15825def77f8bd359b6d3f379f0c9dac4eb09dd4ddd58fd7d14127179c/watchfiles-1.0.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0a04059f4923ce4e856b4b4e5e783a70f49d9663d22a4c3b3298165996d1377f", size = 392715, upload-time = "2025-04-08T10:35:19.202Z" }, + { url = "https://files.pythonhosted.org/packages/58/65/b72fb817518728e08de5840d5d38571466c1b4a3f724d190cec909ee6f3f/watchfiles-1.0.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e380c89983ce6e6fe2dd1e1921b9952fb4e6da882931abd1824c092ed495dec", size = 454138, upload-time = "2025-04-08T10:35:20.586Z" }, + { url = "https://files.pythonhosted.org/packages/3e/a4/86833fd2ea2e50ae28989f5950b5c3f91022d67092bfec08f8300d8b347b/watchfiles-1.0.5-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fe43139b2c0fdc4a14d4f8d5b5d967f7a2777fd3d38ecf5b1ec669b0d7e43c21", size = 458592, upload-time = "2025-04-08T10:35:21.87Z" }, + { url = "https://files.pythonhosted.org/packages/38/7e/42cb8df8be9a37e50dd3a818816501cf7a20d635d76d6bd65aae3dbbff68/watchfiles-1.0.5-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ee0822ce1b8a14fe5a066f93edd20aada932acfe348bede8aa2149f1a4489512", size = 487532, upload-time = "2025-04-08T10:35:23.143Z" }, + { url = "https://files.pythonhosted.org/packages/fc/fd/13d26721c85d7f3df6169d8b495fcac8ab0dc8f0945ebea8845de4681dab/watchfiles-1.0.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a0dbcb1c2d8f2ab6e0a81c6699b236932bd264d4cef1ac475858d16c403de74d", size = 522865, upload-time = "2025-04-08T10:35:24.702Z" }, + { url = "https://files.pythonhosted.org/packages/a1/0d/7f9ae243c04e96c5455d111e21b09087d0eeaf9a1369e13a01c7d3d82478/watchfiles-1.0.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a2014a2b18ad3ca53b1f6c23f8cd94a18ce930c1837bd891262c182640eb40a6", size = 499887, upload-time = "2025-04-08T10:35:25.969Z" }, + { url = "https://files.pythonhosted.org/packages/8e/0f/a257766998e26aca4b3acf2ae97dff04b57071e991a510857d3799247c67/watchfiles-1.0.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10f6ae86d5cb647bf58f9f655fcf577f713915a5d69057a0371bc257e2553234", size = 454498, upload-time = "2025-04-08T10:35:27.353Z" }, + { url = "https://files.pythonhosted.org/packages/81/79/8bf142575a03e0af9c3d5f8bcae911ee6683ae93a625d349d4ecf4c8f7df/watchfiles-1.0.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:1a7bac2bde1d661fb31f4d4e8e539e178774b76db3c2c17c4bb3e960a5de07a2", size = 630663, upload-time = "2025-04-08T10:35:28.685Z" }, + { url = "https://files.pythonhosted.org/packages/f1/80/abe2e79f610e45c63a70d271caea90c49bbf93eb00fa947fa9b803a1d51f/watchfiles-1.0.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4ab626da2fc1ac277bbf752446470b367f84b50295264d2d313e28dc4405d663", size = 625410, upload-time = "2025-04-08T10:35:30.42Z" }, + { url = "https://files.pythonhosted.org/packages/91/6f/bc7fbecb84a41a9069c2c6eb6319f7f7df113adf113e358c57fc1aff7ff5/watchfiles-1.0.5-cp312-cp312-win32.whl", hash = "sha256:9f4571a783914feda92018ef3901dab8caf5b029325b5fe4558c074582815249", size = 277965, upload-time = "2025-04-08T10:35:32.023Z" }, + { url = "https://files.pythonhosted.org/packages/99/a5/bf1c297ea6649ec59e935ab311f63d8af5faa8f0b86993e3282b984263e3/watchfiles-1.0.5-cp312-cp312-win_amd64.whl", hash = "sha256:360a398c3a19672cf93527f7e8d8b60d8275119c5d900f2e184d32483117a705", size = 291693, upload-time = "2025-04-08T10:35:33.225Z" }, + { url = "https://files.pythonhosted.org/packages/7f/7b/fd01087cc21db5c47e5beae507b87965db341cce8a86f9eb12bf5219d4e0/watchfiles-1.0.5-cp312-cp312-win_arm64.whl", hash = "sha256:1a2902ede862969077b97523987c38db28abbe09fb19866e711485d9fbf0d417", size = 283287, upload-time = "2025-04-08T10:35:34.568Z" }, ] [[package]] name = "wcwidth" version = "0.2.13" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/6c/63/53559446a878410fc5a5974feb13d31d78d752eb18aeba59c7fef1af7598/wcwidth-0.2.13.tar.gz", hash = "sha256:72ea0c06399eb286d978fdedb6923a9eb47e1c486ce63e9b4e64fc18303972b5", size = 101301 } +sdist = { url = "https://files.pythonhosted.org/packages/6c/63/53559446a878410fc5a5974feb13d31d78d752eb18aeba59c7fef1af7598/wcwidth-0.2.13.tar.gz", hash = "sha256:72ea0c06399eb286d978fdedb6923a9eb47e1c486ce63e9b4e64fc18303972b5", size = 101301, upload-time = "2024-01-06T02:10:57.829Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/fd/84/fd2ba7aafacbad3c4201d395674fc6348826569da3c0937e75505ead3528/wcwidth-0.2.13-py2.py3-none-any.whl", hash = "sha256:3da69048e4540d84af32131829ff948f1e022c1c6bdb8d6102117aac784f6859", size = 34166 }, + { url = "https://files.pythonhosted.org/packages/fd/84/fd2ba7aafacbad3c4201d395674fc6348826569da3c0937e75505ead3528/wcwidth-0.2.13-py2.py3-none-any.whl", hash = "sha256:3da69048e4540d84af32131829ff948f1e022c1c6bdb8d6102117aac784f6859", size = 34166, upload-time = "2024-01-06T02:10:55.763Z" }, +] + +[[package]] +name = "weave" +version = "0.51.50" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "diskcache" }, + { name = "emoji" }, + { name = "gql", extra = ["aiohttp", "requests"] }, + { name = "jsonschema" }, + { name = "nest-asyncio" }, + { name = "numpy" }, + { name = "packaging" }, + { name = "pydantic" }, + { name = "rich" }, + { name = "tenacity" }, + { name = "uuid-utils" }, + { name = "wandb" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/fe/d1/47ab7923eb389ec7b1ca0138d9929dc50bfd0dfac324f42167f99fa02798/weave-0.51.50.tar.gz", hash = "sha256:773434765a3230bf8f4dfe9e04f9c7dfd90b03f18bb5e069186ce67d1f7c4dd8", size = 410739, upload-time = "2025-06-02T21:20:45.208Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e4/4a/72ed6f8435759f44090c8ae81d3a50716f0c3e527e733a78e77b9a834372/weave-0.51.50-py3-none-any.whl", hash = "sha256:23fb74ec95f57fe30f31019f32f6626f39993aa228024eb9cf8fe83e58b698de", size = 524057, upload-time = "2025-06-02T21:20:43.205Z" }, ] [[package]] name = "weaviate-client" -version = "3.21.0" +version = "3.24.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "authlib" }, { name = "requests" }, - { name = "tqdm" }, { name = "validators" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/b4/a5/c6777a8507249d7a63f4f5d9696eb5f45beac87db0eddfa4438d408cc3b4/weaviate-client-3.21.0.tar.gz", hash = "sha256:ec94ac554883c765e94da8b2947c4f0fa4a0378ed3bbe9f3653df3a5b1745a6d", size = 186970 } +sdist = { url = "https://files.pythonhosted.org/packages/1f/c1/3285a21d8885f2b09aabb65edb9a8e062a35c2d7175e1bb024fa096582ab/weaviate-client-3.24.2.tar.gz", hash = "sha256:6914c48c9a7e5ad0be9399271f9cb85d6f59ab77476c6d4e56a3925bf149edaa", size = 199332, upload-time = "2023-10-04T08:37:54.26Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/df/5b/57b55ad36eb071b57e79f1ea7fba5bfe6a2fe49702607f56726569665d60/weaviate_client-3.21.0-py3-none-any.whl", hash = "sha256:420444ded7106fb000f4f8b2321b5f5fa2387825aa7a303d702accf61026f9d2", size = 99944 }, + { url = "https://files.pythonhosted.org/packages/ab/98/3136d05f93e30cf29e1db280eaadf766df18d812dfe7994bcced653b2340/weaviate_client-3.24.2-py3-none-any.whl", hash = "sha256:bc50ca5fcebcd48de0d00f66700b0cf7c31a97c4cd3d29b4036d77c5d1d9479b", size = 107968, upload-time = "2023-10-04T08:37:52.511Z" }, ] [[package]] name = "webencodings" version = "0.5.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/0b/02/ae6ceac1baeda530866a85075641cec12989bd8d31af6d5ab4a3e8c92f47/webencodings-0.5.1.tar.gz", hash = "sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923", size = 9721 } +sdist = { url = "https://files.pythonhosted.org/packages/0b/02/ae6ceac1baeda530866a85075641cec12989bd8d31af6d5ab4a3e8c92f47/webencodings-0.5.1.tar.gz", hash = "sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923", size = 9721, upload-time = "2017-04-05T20:21:34.189Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f4/24/2a3e3df732393fed8b3ebf2ec078f05546de641fe1b667ee316ec1dcf3b7/webencodings-0.5.1-py2.py3-none-any.whl", hash = "sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78", size = 11774 }, + { url = "https://files.pythonhosted.org/packages/f4/24/2a3e3df732393fed8b3ebf2ec078f05546de641fe1b667ee316ec1dcf3b7/webencodings-0.5.1-py2.py3-none-any.whl", hash = "sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78", size = 11774, upload-time = "2017-04-05T20:21:32.581Z" }, ] [[package]] name = "websocket-client" version = "1.8.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e6/30/fba0d96b4b5fbf5948ed3f4681f7da2f9f64512e1d303f94b4cc174c24a5/websocket_client-1.8.0.tar.gz", hash = "sha256:3239df9f44da632f96012472805d40a23281a991027ce11d2f45a6f24ac4c3da", size = 54648 } +sdist = { url = "https://files.pythonhosted.org/packages/e6/30/fba0d96b4b5fbf5948ed3f4681f7da2f9f64512e1d303f94b4cc174c24a5/websocket_client-1.8.0.tar.gz", hash = "sha256:3239df9f44da632f96012472805d40a23281a991027ce11d2f45a6f24ac4c3da", size = 54648, upload-time = "2024-04-23T22:16:16.976Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/5a/84/44687a29792a70e111c5c477230a72c4b957d88d16141199bf9acb7537a3/websocket_client-1.8.0-py3-none-any.whl", hash = "sha256:17b44cc997f5c498e809b22cdf2d9c7a9e71c02c8cc2b6c56e7c2d1239bfa526", size = 58826 }, + { url = "https://files.pythonhosted.org/packages/5a/84/44687a29792a70e111c5c477230a72c4b957d88d16141199bf9acb7537a3/websocket_client-1.8.0-py3-none-any.whl", hash = "sha256:17b44cc997f5c498e809b22cdf2d9c7a9e71c02c8cc2b6c56e7c2d1239bfa526", size = 58826, upload-time = "2024-04-23T22:16:14.422Z" }, ] [[package]] name = "websockets" -version = "14.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/94/54/8359678c726243d19fae38ca14a334e740782336c9f19700858c4eb64a1e/websockets-14.2.tar.gz", hash = "sha256:5059ed9c54945efb321f097084b4c7e52c246f2c869815876a69d1efc4ad6eb5", size = 164394 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/15/b6/504695fb9a33df0ca56d157f5985660b5fc5b4bf8c78f121578d2d653392/websockets-14.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:3bdc8c692c866ce5fefcaf07d2b55c91d6922ac397e031ef9b774e5b9ea42166", size = 163088 }, - { url = "https://files.pythonhosted.org/packages/81/26/ebfb8f6abe963c795122439c6433c4ae1e061aaedfc7eff32d09394afbae/websockets-14.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c93215fac5dadc63e51bcc6dceca72e72267c11def401d6668622b47675b097f", size = 160745 }, - { url = "https://files.pythonhosted.org/packages/a1/c6/1435ad6f6dcbff80bb95e8986704c3174da8866ddb751184046f5c139ef6/websockets-14.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1c9b6535c0e2cf8a6bf938064fb754aaceb1e6a4a51a80d884cd5db569886910", size = 160995 }, - { url = "https://files.pythonhosted.org/packages/96/63/900c27cfe8be1a1f2433fc77cd46771cf26ba57e6bdc7cf9e63644a61863/websockets-14.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a52a6d7cf6938e04e9dceb949d35fbdf58ac14deea26e685ab6368e73744e4c", size = 170543 }, - { url = "https://files.pythonhosted.org/packages/00/8b/bec2bdba92af0762d42d4410593c1d7d28e9bfd952c97a3729df603dc6ea/websockets-14.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9f05702e93203a6ff5226e21d9b40c037761b2cfb637187c9802c10f58e40473", size = 169546 }, - { url = "https://files.pythonhosted.org/packages/6b/a9/37531cb5b994f12a57dec3da2200ef7aadffef82d888a4c29a0d781568e4/websockets-14.2-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:22441c81a6748a53bfcb98951d58d1af0661ab47a536af08920d129b4d1c3473", size = 169911 }, - { url = "https://files.pythonhosted.org/packages/60/d5/a6eadba2ed9f7e65d677fec539ab14a9b83de2b484ab5fe15d3d6d208c28/websockets-14.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:efd9b868d78b194790e6236d9cbc46d68aba4b75b22497eb4ab64fa640c3af56", size = 170183 }, - { url = "https://files.pythonhosted.org/packages/76/57/a338ccb00d1df881c1d1ee1f2a20c9c1b5b29b51e9e0191ee515d254fea6/websockets-14.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:1a5a20d5843886d34ff8c57424cc65a1deda4375729cbca4cb6b3353f3ce4142", size = 169623 }, - { url = "https://files.pythonhosted.org/packages/64/22/e5f7c33db0cb2c1d03b79fd60d189a1da044e2661f5fd01d629451e1db89/websockets-14.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:34277a29f5303d54ec6468fb525d99c99938607bc96b8d72d675dee2b9f5bf1d", size = 169583 }, - { url = "https://files.pythonhosted.org/packages/aa/2e/2b4662237060063a22e5fc40d46300a07142afe30302b634b4eebd717c07/websockets-14.2-cp311-cp311-win32.whl", hash = "sha256:02687db35dbc7d25fd541a602b5f8e451a238ffa033030b172ff86a93cb5dc2a", size = 163969 }, - { url = "https://files.pythonhosted.org/packages/94/a5/0cda64e1851e73fc1ecdae6f42487babb06e55cb2f0dc8904b81d8ef6857/websockets-14.2-cp311-cp311-win_amd64.whl", hash = "sha256:862e9967b46c07d4dcd2532e9e8e3c2825e004ffbf91a5ef9dde519ee2effb0b", size = 164408 }, - { url = "https://files.pythonhosted.org/packages/c1/81/04f7a397653dc8bec94ddc071f34833e8b99b13ef1a3804c149d59f92c18/websockets-14.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:1f20522e624d7ffbdbe259c6b6a65d73c895045f76a93719aa10cd93b3de100c", size = 163096 }, - { url = "https://files.pythonhosted.org/packages/ec/c5/de30e88557e4d70988ed4d2eabd73fd3e1e52456b9f3a4e9564d86353b6d/websockets-14.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:647b573f7d3ada919fd60e64d533409a79dcf1ea21daeb4542d1d996519ca967", size = 160758 }, - { url = "https://files.pythonhosted.org/packages/e5/8c/d130d668781f2c77d106c007b6c6c1d9db68239107c41ba109f09e6c218a/websockets-14.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6af99a38e49f66be5a64b1e890208ad026cda49355661549c507152113049990", size = 160995 }, - { url = "https://files.pythonhosted.org/packages/a6/bc/f6678a0ff17246df4f06765e22fc9d98d1b11a258cc50c5968b33d6742a1/websockets-14.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:091ab63dfc8cea748cc22c1db2814eadb77ccbf82829bac6b2fbe3401d548eda", size = 170815 }, - { url = "https://files.pythonhosted.org/packages/d8/b2/8070cb970c2e4122a6ef38bc5b203415fd46460e025652e1ee3f2f43a9a3/websockets-14.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b374e8953ad477d17e4851cdc66d83fdc2db88d9e73abf755c94510ebddceb95", size = 169759 }, - { url = "https://files.pythonhosted.org/packages/81/da/72f7caabd94652e6eb7e92ed2d3da818626e70b4f2b15a854ef60bf501ec/websockets-14.2-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a39d7eceeea35db85b85e1169011bb4321c32e673920ae9c1b6e0978590012a3", size = 170178 }, - { url = "https://files.pythonhosted.org/packages/31/e0/812725b6deca8afd3a08a2e81b3c4c120c17f68c9b84522a520b816cda58/websockets-14.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0a6f3efd47ffd0d12080594f434faf1cd2549b31e54870b8470b28cc1d3817d9", size = 170453 }, - { url = "https://files.pythonhosted.org/packages/66/d3/8275dbc231e5ba9bb0c4f93144394b4194402a7a0c8ffaca5307a58ab5e3/websockets-14.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:065ce275e7c4ffb42cb738dd6b20726ac26ac9ad0a2a48e33ca632351a737267", size = 169830 }, - { url = "https://files.pythonhosted.org/packages/a3/ae/e7d1a56755ae15ad5a94e80dd490ad09e345365199600b2629b18ee37bc7/websockets-14.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e9d0e53530ba7b8b5e389c02282f9d2aa47581514bd6049d3a7cffe1385cf5fe", size = 169824 }, - { url = "https://files.pythonhosted.org/packages/b6/32/88ccdd63cb261e77b882e706108d072e4f1c839ed723bf91a3e1f216bf60/websockets-14.2-cp312-cp312-win32.whl", hash = "sha256:20e6dd0984d7ca3037afcb4494e48c74ffb51e8013cac71cf607fffe11df7205", size = 163981 }, - { url = "https://files.pythonhosted.org/packages/b3/7d/32cdb77990b3bdc34a306e0a0f73a1275221e9a66d869f6ff833c95b56ef/websockets-14.2-cp312-cp312-win_amd64.whl", hash = "sha256:44bba1a956c2c9d268bdcdf234d5e5ff4c9b6dc3e300545cbe99af59dda9dcce", size = 164421 }, - { url = "https://files.pythonhosted.org/packages/7b/c8/d529f8a32ce40d98309f4470780631e971a5a842b60aec864833b3615786/websockets-14.2-py3-none-any.whl", hash = "sha256:7a6ceec4ea84469f15cf15807a747e9efe57e369c384fa86e022b3bea679b79b", size = 157416 }, +version = "15.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/21/e6/26d09fab466b7ca9c7737474c52be4f76a40301b08362eb2dbc19dcc16c1/websockets-15.0.1.tar.gz", hash = "sha256:82544de02076bafba038ce055ee6412d68da13ab47f0c60cab827346de828dee", size = 177016, upload-time = "2025-03-05T20:03:41.606Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9f/32/18fcd5919c293a398db67443acd33fde142f283853076049824fc58e6f75/websockets-15.0.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:823c248b690b2fd9303ba00c4f66cd5e2d8c3ba4aa968b2779be9532a4dad431", size = 175423, upload-time = "2025-03-05T20:01:56.276Z" }, + { url = "https://files.pythonhosted.org/packages/76/70/ba1ad96b07869275ef42e2ce21f07a5b0148936688c2baf7e4a1f60d5058/websockets-15.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678999709e68425ae2593acf2e3ebcbcf2e69885a5ee78f9eb80e6e371f1bf57", size = 173082, upload-time = "2025-03-05T20:01:57.563Z" }, + { url = "https://files.pythonhosted.org/packages/86/f2/10b55821dd40eb696ce4704a87d57774696f9451108cff0d2824c97e0f97/websockets-15.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d50fd1ee42388dcfb2b3676132c78116490976f1300da28eb629272d5d93e905", size = 173330, upload-time = "2025-03-05T20:01:59.063Z" }, + { url = "https://files.pythonhosted.org/packages/a5/90/1c37ae8b8a113d3daf1065222b6af61cc44102da95388ac0018fcb7d93d9/websockets-15.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d99e5546bf73dbad5bf3547174cd6cb8ba7273062a23808ffea025ecb1cf8562", size = 182878, upload-time = "2025-03-05T20:02:00.305Z" }, + { url = "https://files.pythonhosted.org/packages/8e/8d/96e8e288b2a41dffafb78e8904ea7367ee4f891dafc2ab8d87e2124cb3d3/websockets-15.0.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:66dd88c918e3287efc22409d426c8f729688d89a0c587c88971a0faa2c2f3792", size = 181883, upload-time = "2025-03-05T20:02:03.148Z" }, + { url = "https://files.pythonhosted.org/packages/93/1f/5d6dbf551766308f6f50f8baf8e9860be6182911e8106da7a7f73785f4c4/websockets-15.0.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8dd8327c795b3e3f219760fa603dcae1dcc148172290a8ab15158cf85a953413", size = 182252, upload-time = "2025-03-05T20:02:05.29Z" }, + { url = "https://files.pythonhosted.org/packages/d4/78/2d4fed9123e6620cbf1706c0de8a1632e1a28e7774d94346d7de1bba2ca3/websockets-15.0.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8fdc51055e6ff4adeb88d58a11042ec9a5eae317a0a53d12c062c8a8865909e8", size = 182521, upload-time = "2025-03-05T20:02:07.458Z" }, + { url = "https://files.pythonhosted.org/packages/e7/3b/66d4c1b444dd1a9823c4a81f50231b921bab54eee2f69e70319b4e21f1ca/websockets-15.0.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:693f0192126df6c2327cce3baa7c06f2a117575e32ab2308f7f8216c29d9e2e3", size = 181958, upload-time = "2025-03-05T20:02:09.842Z" }, + { url = "https://files.pythonhosted.org/packages/08/ff/e9eed2ee5fed6f76fdd6032ca5cd38c57ca9661430bb3d5fb2872dc8703c/websockets-15.0.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:54479983bd5fb469c38f2f5c7e3a24f9a4e70594cd68cd1fa6b9340dadaff7cf", size = 181918, upload-time = "2025-03-05T20:02:11.968Z" }, + { url = "https://files.pythonhosted.org/packages/d8/75/994634a49b7e12532be6a42103597b71098fd25900f7437d6055ed39930a/websockets-15.0.1-cp311-cp311-win32.whl", hash = "sha256:16b6c1b3e57799b9d38427dda63edcbe4926352c47cf88588c0be4ace18dac85", size = 176388, upload-time = "2025-03-05T20:02:13.32Z" }, + { url = "https://files.pythonhosted.org/packages/98/93/e36c73f78400a65f5e236cd376713c34182e6663f6889cd45a4a04d8f203/websockets-15.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:27ccee0071a0e75d22cb35849b1db43f2ecd3e161041ac1ee9d2352ddf72f065", size = 176828, upload-time = "2025-03-05T20:02:14.585Z" }, + { url = "https://files.pythonhosted.org/packages/51/6b/4545a0d843594f5d0771e86463606a3988b5a09ca5123136f8a76580dd63/websockets-15.0.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:3e90baa811a5d73f3ca0bcbf32064d663ed81318ab225ee4f427ad4e26e5aff3", size = 175437, upload-time = "2025-03-05T20:02:16.706Z" }, + { url = "https://files.pythonhosted.org/packages/f4/71/809a0f5f6a06522af902e0f2ea2757f71ead94610010cf570ab5c98e99ed/websockets-15.0.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:592f1a9fe869c778694f0aa806ba0374e97648ab57936f092fd9d87f8bc03665", size = 173096, upload-time = "2025-03-05T20:02:18.832Z" }, + { url = "https://files.pythonhosted.org/packages/3d/69/1a681dd6f02180916f116894181eab8b2e25b31e484c5d0eae637ec01f7c/websockets-15.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0701bc3cfcb9164d04a14b149fd74be7347a530ad3bbf15ab2c678a2cd3dd9a2", size = 173332, upload-time = "2025-03-05T20:02:20.187Z" }, + { url = "https://files.pythonhosted.org/packages/a6/02/0073b3952f5bce97eafbb35757f8d0d54812b6174ed8dd952aa08429bcc3/websockets-15.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8b56bdcdb4505c8078cb6c7157d9811a85790f2f2b3632c7d1462ab5783d215", size = 183152, upload-time = "2025-03-05T20:02:22.286Z" }, + { url = "https://files.pythonhosted.org/packages/74/45/c205c8480eafd114b428284840da0b1be9ffd0e4f87338dc95dc6ff961a1/websockets-15.0.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0af68c55afbd5f07986df82831c7bff04846928ea8d1fd7f30052638788bc9b5", size = 182096, upload-time = "2025-03-05T20:02:24.368Z" }, + { url = "https://files.pythonhosted.org/packages/14/8f/aa61f528fba38578ec553c145857a181384c72b98156f858ca5c8e82d9d3/websockets-15.0.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:64dee438fed052b52e4f98f76c5790513235efaa1ef7f3f2192c392cd7c91b65", size = 182523, upload-time = "2025-03-05T20:02:25.669Z" }, + { url = "https://files.pythonhosted.org/packages/ec/6d/0267396610add5bc0d0d3e77f546d4cd287200804fe02323797de77dbce9/websockets-15.0.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d5f6b181bb38171a8ad1d6aa58a67a6aa9d4b38d0f8c5f496b9e42561dfc62fe", size = 182790, upload-time = "2025-03-05T20:02:26.99Z" }, + { url = "https://files.pythonhosted.org/packages/02/05/c68c5adbf679cf610ae2f74a9b871ae84564462955d991178f95a1ddb7dd/websockets-15.0.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:5d54b09eba2bada6011aea5375542a157637b91029687eb4fdb2dab11059c1b4", size = 182165, upload-time = "2025-03-05T20:02:30.291Z" }, + { url = "https://files.pythonhosted.org/packages/29/93/bb672df7b2f5faac89761cb5fa34f5cec45a4026c383a4b5761c6cea5c16/websockets-15.0.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3be571a8b5afed347da347bfcf27ba12b069d9d7f42cb8c7028b5e98bbb12597", size = 182160, upload-time = "2025-03-05T20:02:31.634Z" }, + { url = "https://files.pythonhosted.org/packages/ff/83/de1f7709376dc3ca9b7eeb4b9a07b4526b14876b6d372a4dc62312bebee0/websockets-15.0.1-cp312-cp312-win32.whl", hash = "sha256:c338ffa0520bdb12fbc527265235639fb76e7bc7faafbb93f6ba80d9c06578a9", size = 176395, upload-time = "2025-03-05T20:02:33.017Z" }, + { url = "https://files.pythonhosted.org/packages/7d/71/abf2ebc3bbfa40f391ce1428c7168fb20582d0ff57019b69ea20fa698043/websockets-15.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:fcd5cf9e305d7b8338754470cf69cf81f420459dbae8a3b40cee57417f4614a7", size = 176841, upload-time = "2025-03-05T20:02:34.498Z" }, + { url = "https://files.pythonhosted.org/packages/fa/a8/5b41e0da817d64113292ab1f8247140aac61cbf6cfd085d6a0fa77f4984f/websockets-15.0.1-py3-none-any.whl", hash = "sha256:f7a866fbc1e97b5c617ee4116daaa09b722101d4a3c170c787450ba409f9736f", size = 169743, upload-time = "2025-03-05T20:03:39.41Z" }, +] + +[[package]] +name = "webvtt-py" +version = "0.5.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5e/f6/7c9c964681fb148e0293e6860108d378e09ccab2218f9063fd3eb87f840a/webvtt-py-0.5.1.tar.gz", hash = "sha256:2040dd325277ddadc1e0c6cc66cbc4a1d9b6b49b24c57a0c3364374c3e8a3dc1", size = 55128, upload-time = "2024-05-30T13:40:17.189Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f3/ed/aad7e0f5a462d679f7b4d2e0d8502c3096740c883b5bbed5103146480937/webvtt_py-0.5.1-py3-none-any.whl", hash = "sha256:9d517d286cfe7fc7825e9d4e2079647ce32f5678eb58e39ef544ffbb932610b7", size = 19802, upload-time = "2024-05-30T13:40:14.661Z" }, ] [[package]] @@ -6082,40 +6496,40 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "markupsafe" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/9f/69/83029f1f6300c5fb2471d621ab06f6ec6b3324685a2ce0f9777fd4a8b71e/werkzeug-3.1.3.tar.gz", hash = "sha256:60723ce945c19328679790e3282cc758aa4a6040e4bb330f53d30fa546d44746", size = 806925 } +sdist = { url = "https://files.pythonhosted.org/packages/9f/69/83029f1f6300c5fb2471d621ab06f6ec6b3324685a2ce0f9777fd4a8b71e/werkzeug-3.1.3.tar.gz", hash = "sha256:60723ce945c19328679790e3282cc758aa4a6040e4bb330f53d30fa546d44746", size = 806925, upload-time = "2024-11-08T15:52:18.093Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/52/24/ab44c871b0f07f491e5d2ad12c9bd7358e527510618cb1b803a88e986db1/werkzeug-3.1.3-py3-none-any.whl", hash = "sha256:54b78bf3716d19a65be4fceccc0d1d7b89e608834989dfae50ea87564639213e", size = 224498 }, + { url = "https://files.pythonhosted.org/packages/52/24/ab44c871b0f07f491e5d2ad12c9bd7358e527510618cb1b803a88e986db1/werkzeug-3.1.3-py3-none-any.whl", hash = "sha256:54b78bf3716d19a65be4fceccc0d1d7b89e608834989dfae50ea87564639213e", size = 224498, upload-time = "2024-11-08T15:52:16.132Z" }, ] [[package]] name = "wrapt" version = "1.17.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/c3/fc/e91cc220803d7bc4db93fb02facd8461c37364151b8494762cc88b0fbcef/wrapt-1.17.2.tar.gz", hash = "sha256:41388e9d4d1522446fe79d3213196bd9e3b301a336965b9e27ca2788ebd122f3", size = 55531 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/cd/f7/a2aab2cbc7a665efab072344a8949a71081eed1d2f451f7f7d2b966594a2/wrapt-1.17.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ff04ef6eec3eee8a5efef2401495967a916feaa353643defcc03fc74fe213b58", size = 53308 }, - { url = "https://files.pythonhosted.org/packages/50/ff/149aba8365fdacef52b31a258c4dc1c57c79759c335eff0b3316a2664a64/wrapt-1.17.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4db983e7bca53819efdbd64590ee96c9213894272c776966ca6306b73e4affda", size = 38488 }, - { url = "https://files.pythonhosted.org/packages/65/46/5a917ce85b5c3b490d35c02bf71aedaa9f2f63f2d15d9949cc4ba56e8ba9/wrapt-1.17.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9abc77a4ce4c6f2a3168ff34b1da9b0f311a8f1cfd694ec96b0603dff1c79438", size = 38776 }, - { url = "https://files.pythonhosted.org/packages/ca/74/336c918d2915a4943501c77566db41d1bd6e9f4dbc317f356b9a244dfe83/wrapt-1.17.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0b929ac182f5ace000d459c59c2c9c33047e20e935f8e39371fa6e3b85d56f4a", size = 83776 }, - { url = "https://files.pythonhosted.org/packages/09/99/c0c844a5ccde0fe5761d4305485297f91d67cf2a1a824c5f282e661ec7ff/wrapt-1.17.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f09b286faeff3c750a879d336fb6d8713206fc97af3adc14def0cdd349df6000", size = 75420 }, - { url = "https://files.pythonhosted.org/packages/b4/b0/9fc566b0fe08b282c850063591a756057c3247b2362b9286429ec5bf1721/wrapt-1.17.2-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1a7ed2d9d039bd41e889f6fb9364554052ca21ce823580f6a07c4ec245c1f5d6", size = 83199 }, - { url = "https://files.pythonhosted.org/packages/9d/4b/71996e62d543b0a0bd95dda485219856def3347e3e9380cc0d6cf10cfb2f/wrapt-1.17.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:129a150f5c445165ff941fc02ee27df65940fcb8a22a61828b1853c98763a64b", size = 82307 }, - { url = "https://files.pythonhosted.org/packages/39/35/0282c0d8789c0dc9bcc738911776c762a701f95cfe113fb8f0b40e45c2b9/wrapt-1.17.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:1fb5699e4464afe5c7e65fa51d4f99e0b2eadcc176e4aa33600a3df7801d6662", size = 75025 }, - { url = "https://files.pythonhosted.org/packages/4f/6d/90c9fd2c3c6fee181feecb620d95105370198b6b98a0770cba090441a828/wrapt-1.17.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9a2bce789a5ea90e51a02dfcc39e31b7f1e662bc3317979aa7e5538e3a034f72", size = 81879 }, - { url = "https://files.pythonhosted.org/packages/8f/fa/9fb6e594f2ce03ef03eddbdb5f4f90acb1452221a5351116c7c4708ac865/wrapt-1.17.2-cp311-cp311-win32.whl", hash = "sha256:4afd5814270fdf6380616b321fd31435a462019d834f83c8611a0ce7484c7317", size = 36419 }, - { url = "https://files.pythonhosted.org/packages/47/f8/fb1773491a253cbc123c5d5dc15c86041f746ed30416535f2a8df1f4a392/wrapt-1.17.2-cp311-cp311-win_amd64.whl", hash = "sha256:acc130bc0375999da18e3d19e5a86403667ac0c4042a094fefb7eec8ebac7cf3", size = 38773 }, - { url = "https://files.pythonhosted.org/packages/a1/bd/ab55f849fd1f9a58ed7ea47f5559ff09741b25f00c191231f9f059c83949/wrapt-1.17.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:d5e2439eecc762cd85e7bd37161d4714aa03a33c5ba884e26c81559817ca0925", size = 53799 }, - { url = "https://files.pythonhosted.org/packages/53/18/75ddc64c3f63988f5a1d7e10fb204ffe5762bc663f8023f18ecaf31a332e/wrapt-1.17.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:3fc7cb4c1c744f8c05cd5f9438a3caa6ab94ce8344e952d7c45a8ed59dd88392", size = 38821 }, - { url = "https://files.pythonhosted.org/packages/48/2a/97928387d6ed1c1ebbfd4efc4133a0633546bec8481a2dd5ec961313a1c7/wrapt-1.17.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8fdbdb757d5390f7c675e558fd3186d590973244fab0c5fe63d373ade3e99d40", size = 38919 }, - { url = "https://files.pythonhosted.org/packages/73/54/3bfe5a1febbbccb7a2f77de47b989c0b85ed3a6a41614b104204a788c20e/wrapt-1.17.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5bb1d0dbf99411f3d871deb6faa9aabb9d4e744d67dcaaa05399af89d847a91d", size = 88721 }, - { url = "https://files.pythonhosted.org/packages/25/cb/7262bc1b0300b4b64af50c2720ef958c2c1917525238d661c3e9a2b71b7b/wrapt-1.17.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d18a4865f46b8579d44e4fe1e2bcbc6472ad83d98e22a26c963d46e4c125ef0b", size = 80899 }, - { url = "https://files.pythonhosted.org/packages/2a/5a/04cde32b07a7431d4ed0553a76fdb7a61270e78c5fd5a603e190ac389f14/wrapt-1.17.2-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc570b5f14a79734437cb7b0500376b6b791153314986074486e0b0fa8d71d98", size = 89222 }, - { url = "https://files.pythonhosted.org/packages/09/28/2e45a4f4771fcfb109e244d5dbe54259e970362a311b67a965555ba65026/wrapt-1.17.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6d9187b01bebc3875bac9b087948a2bccefe464a7d8f627cf6e48b1bbae30f82", size = 86707 }, - { url = "https://files.pythonhosted.org/packages/c6/d2/dcb56bf5f32fcd4bd9aacc77b50a539abdd5b6536872413fd3f428b21bed/wrapt-1.17.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:9e8659775f1adf02eb1e6f109751268e493c73716ca5761f8acb695e52a756ae", size = 79685 }, - { url = "https://files.pythonhosted.org/packages/80/4e/eb8b353e36711347893f502ce91c770b0b0929f8f0bed2670a6856e667a9/wrapt-1.17.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e8b2816ebef96d83657b56306152a93909a83f23994f4b30ad4573b00bd11bb9", size = 87567 }, - { url = "https://files.pythonhosted.org/packages/17/27/4fe749a54e7fae6e7146f1c7d914d28ef599dacd4416566c055564080fe2/wrapt-1.17.2-cp312-cp312-win32.whl", hash = "sha256:468090021f391fe0056ad3e807e3d9034e0fd01adcd3bdfba977b6fdf4213ea9", size = 36672 }, - { url = "https://files.pythonhosted.org/packages/15/06/1dbf478ea45c03e78a6a8c4be4fdc3c3bddea5c8de8a93bc971415e47f0f/wrapt-1.17.2-cp312-cp312-win_amd64.whl", hash = "sha256:ec89ed91f2fa8e3f52ae53cd3cf640d6feff92ba90d62236a81e4e563ac0e991", size = 38865 }, - { url = "https://files.pythonhosted.org/packages/2d/82/f56956041adef78f849db6b289b282e72b55ab8045a75abad81898c28d19/wrapt-1.17.2-py3-none-any.whl", hash = "sha256:b18f2d1533a71f069c7f82d524a52599053d4c7166e9dd374ae2136b7f40f7c8", size = 23594 }, +sdist = { url = "https://files.pythonhosted.org/packages/c3/fc/e91cc220803d7bc4db93fb02facd8461c37364151b8494762cc88b0fbcef/wrapt-1.17.2.tar.gz", hash = "sha256:41388e9d4d1522446fe79d3213196bd9e3b301a336965b9e27ca2788ebd122f3", size = 55531, upload-time = "2025-01-14T10:35:45.465Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cd/f7/a2aab2cbc7a665efab072344a8949a71081eed1d2f451f7f7d2b966594a2/wrapt-1.17.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ff04ef6eec3eee8a5efef2401495967a916feaa353643defcc03fc74fe213b58", size = 53308, upload-time = "2025-01-14T10:33:33.992Z" }, + { url = "https://files.pythonhosted.org/packages/50/ff/149aba8365fdacef52b31a258c4dc1c57c79759c335eff0b3316a2664a64/wrapt-1.17.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4db983e7bca53819efdbd64590ee96c9213894272c776966ca6306b73e4affda", size = 38488, upload-time = "2025-01-14T10:33:35.264Z" }, + { url = "https://files.pythonhosted.org/packages/65/46/5a917ce85b5c3b490d35c02bf71aedaa9f2f63f2d15d9949cc4ba56e8ba9/wrapt-1.17.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9abc77a4ce4c6f2a3168ff34b1da9b0f311a8f1cfd694ec96b0603dff1c79438", size = 38776, upload-time = "2025-01-14T10:33:38.28Z" }, + { url = "https://files.pythonhosted.org/packages/ca/74/336c918d2915a4943501c77566db41d1bd6e9f4dbc317f356b9a244dfe83/wrapt-1.17.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0b929ac182f5ace000d459c59c2c9c33047e20e935f8e39371fa6e3b85d56f4a", size = 83776, upload-time = "2025-01-14T10:33:40.678Z" }, + { url = "https://files.pythonhosted.org/packages/09/99/c0c844a5ccde0fe5761d4305485297f91d67cf2a1a824c5f282e661ec7ff/wrapt-1.17.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f09b286faeff3c750a879d336fb6d8713206fc97af3adc14def0cdd349df6000", size = 75420, upload-time = "2025-01-14T10:33:41.868Z" }, + { url = "https://files.pythonhosted.org/packages/b4/b0/9fc566b0fe08b282c850063591a756057c3247b2362b9286429ec5bf1721/wrapt-1.17.2-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1a7ed2d9d039bd41e889f6fb9364554052ca21ce823580f6a07c4ec245c1f5d6", size = 83199, upload-time = "2025-01-14T10:33:43.598Z" }, + { url = "https://files.pythonhosted.org/packages/9d/4b/71996e62d543b0a0bd95dda485219856def3347e3e9380cc0d6cf10cfb2f/wrapt-1.17.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:129a150f5c445165ff941fc02ee27df65940fcb8a22a61828b1853c98763a64b", size = 82307, upload-time = "2025-01-14T10:33:48.499Z" }, + { url = "https://files.pythonhosted.org/packages/39/35/0282c0d8789c0dc9bcc738911776c762a701f95cfe113fb8f0b40e45c2b9/wrapt-1.17.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:1fb5699e4464afe5c7e65fa51d4f99e0b2eadcc176e4aa33600a3df7801d6662", size = 75025, upload-time = "2025-01-14T10:33:51.191Z" }, + { url = "https://files.pythonhosted.org/packages/4f/6d/90c9fd2c3c6fee181feecb620d95105370198b6b98a0770cba090441a828/wrapt-1.17.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9a2bce789a5ea90e51a02dfcc39e31b7f1e662bc3317979aa7e5538e3a034f72", size = 81879, upload-time = "2025-01-14T10:33:52.328Z" }, + { url = "https://files.pythonhosted.org/packages/8f/fa/9fb6e594f2ce03ef03eddbdb5f4f90acb1452221a5351116c7c4708ac865/wrapt-1.17.2-cp311-cp311-win32.whl", hash = "sha256:4afd5814270fdf6380616b321fd31435a462019d834f83c8611a0ce7484c7317", size = 36419, upload-time = "2025-01-14T10:33:53.551Z" }, + { url = "https://files.pythonhosted.org/packages/47/f8/fb1773491a253cbc123c5d5dc15c86041f746ed30416535f2a8df1f4a392/wrapt-1.17.2-cp311-cp311-win_amd64.whl", hash = "sha256:acc130bc0375999da18e3d19e5a86403667ac0c4042a094fefb7eec8ebac7cf3", size = 38773, upload-time = "2025-01-14T10:33:56.323Z" }, + { url = "https://files.pythonhosted.org/packages/a1/bd/ab55f849fd1f9a58ed7ea47f5559ff09741b25f00c191231f9f059c83949/wrapt-1.17.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:d5e2439eecc762cd85e7bd37161d4714aa03a33c5ba884e26c81559817ca0925", size = 53799, upload-time = "2025-01-14T10:33:57.4Z" }, + { url = "https://files.pythonhosted.org/packages/53/18/75ddc64c3f63988f5a1d7e10fb204ffe5762bc663f8023f18ecaf31a332e/wrapt-1.17.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:3fc7cb4c1c744f8c05cd5f9438a3caa6ab94ce8344e952d7c45a8ed59dd88392", size = 38821, upload-time = "2025-01-14T10:33:59.334Z" }, + { url = "https://files.pythonhosted.org/packages/48/2a/97928387d6ed1c1ebbfd4efc4133a0633546bec8481a2dd5ec961313a1c7/wrapt-1.17.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8fdbdb757d5390f7c675e558fd3186d590973244fab0c5fe63d373ade3e99d40", size = 38919, upload-time = "2025-01-14T10:34:04.093Z" }, + { url = "https://files.pythonhosted.org/packages/73/54/3bfe5a1febbbccb7a2f77de47b989c0b85ed3a6a41614b104204a788c20e/wrapt-1.17.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5bb1d0dbf99411f3d871deb6faa9aabb9d4e744d67dcaaa05399af89d847a91d", size = 88721, upload-time = "2025-01-14T10:34:07.163Z" }, + { url = "https://files.pythonhosted.org/packages/25/cb/7262bc1b0300b4b64af50c2720ef958c2c1917525238d661c3e9a2b71b7b/wrapt-1.17.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d18a4865f46b8579d44e4fe1e2bcbc6472ad83d98e22a26c963d46e4c125ef0b", size = 80899, upload-time = "2025-01-14T10:34:09.82Z" }, + { url = "https://files.pythonhosted.org/packages/2a/5a/04cde32b07a7431d4ed0553a76fdb7a61270e78c5fd5a603e190ac389f14/wrapt-1.17.2-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc570b5f14a79734437cb7b0500376b6b791153314986074486e0b0fa8d71d98", size = 89222, upload-time = "2025-01-14T10:34:11.258Z" }, + { url = "https://files.pythonhosted.org/packages/09/28/2e45a4f4771fcfb109e244d5dbe54259e970362a311b67a965555ba65026/wrapt-1.17.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6d9187b01bebc3875bac9b087948a2bccefe464a7d8f627cf6e48b1bbae30f82", size = 86707, upload-time = "2025-01-14T10:34:12.49Z" }, + { url = "https://files.pythonhosted.org/packages/c6/d2/dcb56bf5f32fcd4bd9aacc77b50a539abdd5b6536872413fd3f428b21bed/wrapt-1.17.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:9e8659775f1adf02eb1e6f109751268e493c73716ca5761f8acb695e52a756ae", size = 79685, upload-time = "2025-01-14T10:34:15.043Z" }, + { url = "https://files.pythonhosted.org/packages/80/4e/eb8b353e36711347893f502ce91c770b0b0929f8f0bed2670a6856e667a9/wrapt-1.17.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e8b2816ebef96d83657b56306152a93909a83f23994f4b30ad4573b00bd11bb9", size = 87567, upload-time = "2025-01-14T10:34:16.563Z" }, + { url = "https://files.pythonhosted.org/packages/17/27/4fe749a54e7fae6e7146f1c7d914d28ef599dacd4416566c055564080fe2/wrapt-1.17.2-cp312-cp312-win32.whl", hash = "sha256:468090021f391fe0056ad3e807e3d9034e0fd01adcd3bdfba977b6fdf4213ea9", size = 36672, upload-time = "2025-01-14T10:34:17.727Z" }, + { url = "https://files.pythonhosted.org/packages/15/06/1dbf478ea45c03e78a6a8c4be4fdc3c3bddea5c8de8a93bc971415e47f0f/wrapt-1.17.2-cp312-cp312-win_amd64.whl", hash = "sha256:ec89ed91f2fa8e3f52ae53cd3cf640d6feff92ba90d62236a81e4e563ac0e991", size = 38865, upload-time = "2025-01-14T10:34:19.577Z" }, + { url = "https://files.pythonhosted.org/packages/2d/82/f56956041adef78f849db6b289b282e72b55ab8045a75abad81898c28d19/wrapt-1.17.2-py3-none-any.whl", hash = "sha256:b18f2d1533a71f069c7f82d524a52599053d4c7166e9dd374ae2136b7f40f7c8", size = 23594, upload-time = "2025-01-14T10:35:44.018Z" }, ] [[package]] @@ -6127,36 +6541,36 @@ dependencies = [ { name = "requests" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/4b/cf/7f825a311b11d1e0f7947a94f88adcf1d31e707c54a6d76d61a5d98604ed/xinference-client-1.2.2.tar.gz", hash = "sha256:85d2ba0fcbaae616b06719c422364123cbac97f3e3c82e614095fe6d0e630ed0", size = 44824 } +sdist = { url = "https://files.pythonhosted.org/packages/4b/cf/7f825a311b11d1e0f7947a94f88adcf1d31e707c54a6d76d61a5d98604ed/xinference-client-1.2.2.tar.gz", hash = "sha256:85d2ba0fcbaae616b06719c422364123cbac97f3e3c82e614095fe6d0e630ed0", size = 44824, upload-time = "2025-02-08T09:28:56.692Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/77/0f/fc58e062cf2f7506a33d2fe5446a1e88eb7f64914addffd7ed8b12749712/xinference_client-1.2.2-py3-none-any.whl", hash = "sha256:6941d87cf61283a9d6e81cee6cb2609a183d34c6b7d808c6ba0c33437520518f", size = 25723 }, + { url = "https://files.pythonhosted.org/packages/77/0f/fc58e062cf2f7506a33d2fe5446a1e88eb7f64914addffd7ed8b12749712/xinference_client-1.2.2-py3-none-any.whl", hash = "sha256:6941d87cf61283a9d6e81cee6cb2609a183d34c6b7d808c6ba0c33437520518f", size = 25723, upload-time = "2025-02-08T09:28:54.046Z" }, ] [[package]] name = "xlrd" version = "2.0.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a6/b3/19a2540d21dea5f908304375bd43f5ed7a4c28a370dc9122c565423e6b44/xlrd-2.0.1.tar.gz", hash = "sha256:f72f148f54442c6b056bf931dbc34f986fd0c3b0b6b5a58d013c9aef274d0c88", size = 100259 } +sdist = { url = "https://files.pythonhosted.org/packages/a6/b3/19a2540d21dea5f908304375bd43f5ed7a4c28a370dc9122c565423e6b44/xlrd-2.0.1.tar.gz", hash = "sha256:f72f148f54442c6b056bf931dbc34f986fd0c3b0b6b5a58d013c9aef274d0c88", size = 100259, upload-time = "2020-12-11T10:14:22.201Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a6/0c/c2a72d51fe56e08a08acc85d13013558a2d793028ae7385448a6ccdfae64/xlrd-2.0.1-py2.py3-none-any.whl", hash = "sha256:6a33ee89877bd9abc1158129f6e94be74e2679636b8a205b43b85206c3f0bbdd", size = 96531 }, + { url = "https://files.pythonhosted.org/packages/a6/0c/c2a72d51fe56e08a08acc85d13013558a2d793028ae7385448a6ccdfae64/xlrd-2.0.1-py2.py3-none-any.whl", hash = "sha256:6a33ee89877bd9abc1158129f6e94be74e2679636b8a205b43b85206c3f0bbdd", size = 96531, upload-time = "2020-12-11T10:14:20.877Z" }, ] [[package]] name = "xlsxwriter" -version = "3.2.2" +version = "3.2.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a1/08/26f69d1e9264e8107253018de9fc6b96f9219817d01c5f021e927384a8d1/xlsxwriter-3.2.2.tar.gz", hash = "sha256:befc7f92578a85fed261639fb6cde1fd51b79c5e854040847dde59d4317077dc", size = 205202 } +sdist = { url = "https://files.pythonhosted.org/packages/a7/d1/e026d33dd5d552e5bf3a873dee54dad66b550230df8290d79394f09b2315/xlsxwriter-3.2.3.tar.gz", hash = "sha256:ad6fd41bdcf1b885876b1f6b7087560aecc9ae5a9cc2ba97dcac7ab2e210d3d5", size = 209135, upload-time = "2025-04-17T10:11:23.481Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/9b/07/df054f7413bdfff5e98f75056e4ed0977d0c8716424011fac2587864d1d3/XlsxWriter-3.2.2-py3-none-any.whl", hash = "sha256:272ce861e7fa5e82a4a6ebc24511f2cb952fde3461f6c6e1a1e81d3272db1471", size = 165121 }, + { url = "https://files.pythonhosted.org/packages/37/b1/a252d499f2760b314fcf264d2b36fcc4343a1ecdb25492b210cb0db70a68/XlsxWriter-3.2.3-py3-none-any.whl", hash = "sha256:593f8296e8a91790c6d0378ab08b064f34a642b3feb787cf6738236bd0a4860d", size = 169433, upload-time = "2025-04-17T10:11:21.329Z" }, ] [[package]] name = "xmltodict" version = "0.14.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/50/05/51dcca9a9bf5e1bce52582683ce50980bcadbc4fa5143b9f2b19ab99958f/xmltodict-0.14.2.tar.gz", hash = "sha256:201e7c28bb210e374999d1dde6382923ab0ed1a8a5faeece48ab525b7810a553", size = 51942 } +sdist = { url = "https://files.pythonhosted.org/packages/50/05/51dcca9a9bf5e1bce52582683ce50980bcadbc4fa5143b9f2b19ab99958f/xmltodict-0.14.2.tar.gz", hash = "sha256:201e7c28bb210e374999d1dde6382923ab0ed1a8a5faeece48ab525b7810a553", size = 51942, upload-time = "2024-10-16T06:10:29.683Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d6/45/fc303eb433e8a2a271739c98e953728422fa61a3c1f36077a49e395c972e/xmltodict-0.14.2-py2.py3-none-any.whl", hash = "sha256:20cc7d723ed729276e808f26fb6b3599f786cbc37e06c65e192ba77c40f20aac", size = 9981 }, + { url = "https://files.pythonhosted.org/packages/d6/45/fc303eb433e8a2a271739c98e953728422fa61a3c1f36077a49e395c972e/xmltodict-0.14.2-py2.py3-none-any.whl", hash = "sha256:20cc7d723ed729276e808f26fb6b3599f786cbc37e06c65e192ba77c40f20aac", size = 9981, upload-time = "2024-10-16T06:10:27.649Z" }, ] [[package]] @@ -6168,50 +6582,50 @@ dependencies = [ { name = "multidict" }, { name = "propcache" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/b7/9d/4b94a8e6d2b51b599516a5cb88e5bc99b4d8d4583e468057eaa29d5f0918/yarl-1.18.3.tar.gz", hash = "sha256:ac1801c45cbf77b6c99242eeff4fffb5e4e73a800b5c4ad4fc0be5def634d2e1", size = 181062 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/40/93/282b5f4898d8e8efaf0790ba6d10e2245d2c9f30e199d1a85cae9356098c/yarl-1.18.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8503ad47387b8ebd39cbbbdf0bf113e17330ffd339ba1144074da24c545f0069", size = 141555 }, - { url = "https://files.pythonhosted.org/packages/6d/9c/0a49af78df099c283ca3444560f10718fadb8a18dc8b3edf8c7bd9fd7d89/yarl-1.18.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:02ddb6756f8f4517a2d5e99d8b2f272488e18dd0bfbc802f31c16c6c20f22193", size = 94351 }, - { url = "https://files.pythonhosted.org/packages/5a/a1/205ab51e148fdcedad189ca8dd587794c6f119882437d04c33c01a75dece/yarl-1.18.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:67a283dd2882ac98cc6318384f565bffc751ab564605959df4752d42483ad889", size = 92286 }, - { url = "https://files.pythonhosted.org/packages/ed/fe/88b690b30f3f59275fb674f5f93ddd4a3ae796c2b62e5bb9ece8a4914b83/yarl-1.18.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d980e0325b6eddc81331d3f4551e2a333999fb176fd153e075c6d1c2530aa8a8", size = 340649 }, - { url = "https://files.pythonhosted.org/packages/07/eb/3b65499b568e01f36e847cebdc8d7ccb51fff716dbda1ae83c3cbb8ca1c9/yarl-1.18.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b643562c12680b01e17239be267bc306bbc6aac1f34f6444d1bded0c5ce438ca", size = 356623 }, - { url = "https://files.pythonhosted.org/packages/33/46/f559dc184280b745fc76ec6b1954de2c55595f0ec0a7614238b9ebf69618/yarl-1.18.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c017a3b6df3a1bd45b9fa49a0f54005e53fbcad16633870104b66fa1a30a29d8", size = 354007 }, - { url = "https://files.pythonhosted.org/packages/af/ba/1865d85212351ad160f19fb99808acf23aab9a0f8ff31c8c9f1b4d671fc9/yarl-1.18.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:75674776d96d7b851b6498f17824ba17849d790a44d282929c42dbb77d4f17ae", size = 344145 }, - { url = "https://files.pythonhosted.org/packages/94/cb/5c3e975d77755d7b3d5193e92056b19d83752ea2da7ab394e22260a7b824/yarl-1.18.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ccaa3a4b521b780a7e771cc336a2dba389a0861592bbce09a476190bb0c8b4b3", size = 336133 }, - { url = "https://files.pythonhosted.org/packages/19/89/b77d3fd249ab52a5c40859815765d35c91425b6bb82e7427ab2f78f5ff55/yarl-1.18.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:2d06d3005e668744e11ed80812e61efd77d70bb7f03e33c1598c301eea20efbb", size = 347967 }, - { url = "https://files.pythonhosted.org/packages/35/bd/f6b7630ba2cc06c319c3235634c582a6ab014d52311e7d7c22f9518189b5/yarl-1.18.3-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:9d41beda9dc97ca9ab0b9888cb71f7539124bc05df02c0cff6e5acc5a19dcc6e", size = 346397 }, - { url = "https://files.pythonhosted.org/packages/18/1a/0b4e367d5a72d1f095318344848e93ea70da728118221f84f1bf6c1e39e7/yarl-1.18.3-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:ba23302c0c61a9999784e73809427c9dbedd79f66a13d84ad1b1943802eaaf59", size = 350206 }, - { url = "https://files.pythonhosted.org/packages/b5/cf/320fff4367341fb77809a2d8d7fe75b5d323a8e1b35710aafe41fdbf327b/yarl-1.18.3-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:6748dbf9bfa5ba1afcc7556b71cda0d7ce5f24768043a02a58846e4a443d808d", size = 362089 }, - { url = "https://files.pythonhosted.org/packages/57/cf/aadba261d8b920253204085268bad5e8cdd86b50162fcb1b10c10834885a/yarl-1.18.3-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:0b0cad37311123211dc91eadcb322ef4d4a66008d3e1bdc404808992260e1a0e", size = 366267 }, - { url = "https://files.pythonhosted.org/packages/54/58/fb4cadd81acdee6dafe14abeb258f876e4dd410518099ae9a35c88d8097c/yarl-1.18.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0fb2171a4486bb075316ee754c6d8382ea6eb8b399d4ec62fde2b591f879778a", size = 359141 }, - { url = "https://files.pythonhosted.org/packages/9a/7a/4c571597589da4cd5c14ed2a0b17ac56ec9ee7ee615013f74653169e702d/yarl-1.18.3-cp311-cp311-win32.whl", hash = "sha256:61b1a825a13bef4a5f10b1885245377d3cd0bf87cba068e1d9a88c2ae36880e1", size = 84402 }, - { url = "https://files.pythonhosted.org/packages/ae/7b/8600250b3d89b625f1121d897062f629883c2f45339623b69b1747ec65fa/yarl-1.18.3-cp311-cp311-win_amd64.whl", hash = "sha256:b9d60031cf568c627d028239693fd718025719c02c9f55df0a53e587aab951b5", size = 91030 }, - { url = "https://files.pythonhosted.org/packages/33/85/bd2e2729752ff4c77338e0102914897512e92496375e079ce0150a6dc306/yarl-1.18.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:1dd4bdd05407ced96fed3d7f25dbbf88d2ffb045a0db60dbc247f5b3c5c25d50", size = 142644 }, - { url = "https://files.pythonhosted.org/packages/ff/74/1178322cc0f10288d7eefa6e4a85d8d2e28187ccab13d5b844e8b5d7c88d/yarl-1.18.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7c33dd1931a95e5d9a772d0ac5e44cac8957eaf58e3c8da8c1414de7dd27c576", size = 94962 }, - { url = "https://files.pythonhosted.org/packages/be/75/79c6acc0261e2c2ae8a1c41cf12265e91628c8c58ae91f5ff59e29c0787f/yarl-1.18.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:25b411eddcfd56a2f0cd6a384e9f4f7aa3efee14b188de13048c25b5e91f1640", size = 92795 }, - { url = "https://files.pythonhosted.org/packages/6b/32/927b2d67a412c31199e83fefdce6e645247b4fb164aa1ecb35a0f9eb2058/yarl-1.18.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:436c4fc0a4d66b2badc6c5fc5ef4e47bb10e4fd9bf0c79524ac719a01f3607c2", size = 332368 }, - { url = "https://files.pythonhosted.org/packages/19/e5/859fca07169d6eceeaa4fde1997c91d8abde4e9a7c018e371640c2da2b71/yarl-1.18.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e35ef8683211db69ffe129a25d5634319a677570ab6b2eba4afa860f54eeaf75", size = 342314 }, - { url = "https://files.pythonhosted.org/packages/08/75/76b63ccd91c9e03ab213ef27ae6add2e3400e77e5cdddf8ed2dbc36e3f21/yarl-1.18.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:84b2deecba4a3f1a398df819151eb72d29bfeb3b69abb145a00ddc8d30094512", size = 341987 }, - { url = "https://files.pythonhosted.org/packages/1a/e1/a097d5755d3ea8479a42856f51d97eeff7a3a7160593332d98f2709b3580/yarl-1.18.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:00e5a1fea0fd4f5bfa7440a47eff01d9822a65b4488f7cff83155a0f31a2ecba", size = 336914 }, - { url = "https://files.pythonhosted.org/packages/0b/42/e1b4d0e396b7987feceebe565286c27bc085bf07d61a59508cdaf2d45e63/yarl-1.18.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d0e883008013c0e4aef84dcfe2a0b172c4d23c2669412cf5b3371003941f72bb", size = 325765 }, - { url = "https://files.pythonhosted.org/packages/7e/18/03a5834ccc9177f97ca1bbb245b93c13e58e8225276f01eedc4cc98ab820/yarl-1.18.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5a3f356548e34a70b0172d8890006c37be92995f62d95a07b4a42e90fba54272", size = 344444 }, - { url = "https://files.pythonhosted.org/packages/c8/03/a713633bdde0640b0472aa197b5b86e90fbc4c5bc05b727b714cd8a40e6d/yarl-1.18.3-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:ccd17349166b1bee6e529b4add61727d3f55edb7babbe4069b5764c9587a8cc6", size = 340760 }, - { url = "https://files.pythonhosted.org/packages/eb/99/f6567e3f3bbad8fd101886ea0276c68ecb86a2b58be0f64077396cd4b95e/yarl-1.18.3-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:b958ddd075ddba5b09bb0be8a6d9906d2ce933aee81100db289badbeb966f54e", size = 346484 }, - { url = "https://files.pythonhosted.org/packages/8e/a9/84717c896b2fc6cb15bd4eecd64e34a2f0a9fd6669e69170c73a8b46795a/yarl-1.18.3-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c7d79f7d9aabd6011004e33b22bc13056a3e3fb54794d138af57f5ee9d9032cb", size = 359864 }, - { url = "https://files.pythonhosted.org/packages/1e/2e/d0f5f1bef7ee93ed17e739ec8dbcb47794af891f7d165fa6014517b48169/yarl-1.18.3-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:4891ed92157e5430874dad17b15eb1fda57627710756c27422200c52d8a4e393", size = 364537 }, - { url = "https://files.pythonhosted.org/packages/97/8a/568d07c5d4964da5b02621a517532adb8ec5ba181ad1687191fffeda0ab6/yarl-1.18.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ce1af883b94304f493698b00d0f006d56aea98aeb49d75ec7d98cd4a777e9285", size = 357861 }, - { url = "https://files.pythonhosted.org/packages/7d/e3/924c3f64b6b3077889df9a1ece1ed8947e7b61b0a933f2ec93041990a677/yarl-1.18.3-cp312-cp312-win32.whl", hash = "sha256:f91c4803173928a25e1a55b943c81f55b8872f0018be83e3ad4938adffb77dd2", size = 84097 }, - { url = "https://files.pythonhosted.org/packages/34/45/0e055320daaabfc169b21ff6174567b2c910c45617b0d79c68d7ab349b02/yarl-1.18.3-cp312-cp312-win_amd64.whl", hash = "sha256:7e2ee16578af3b52ac2f334c3b1f92262f47e02cc6193c598502bd46f5cd1477", size = 90399 }, - { url = "https://files.pythonhosted.org/packages/f5/4b/a06e0ec3d155924f77835ed2d167ebd3b211a7b0853da1cf8d8414d784ef/yarl-1.18.3-py3-none-any.whl", hash = "sha256:b57f4f58099328dfb26c6a771d09fb20dbbae81d20cfb66141251ea063bd101b", size = 45109 }, +sdist = { url = "https://files.pythonhosted.org/packages/b7/9d/4b94a8e6d2b51b599516a5cb88e5bc99b4d8d4583e468057eaa29d5f0918/yarl-1.18.3.tar.gz", hash = "sha256:ac1801c45cbf77b6c99242eeff4fffb5e4e73a800b5c4ad4fc0be5def634d2e1", size = 181062, upload-time = "2024-12-01T20:35:23.292Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/40/93/282b5f4898d8e8efaf0790ba6d10e2245d2c9f30e199d1a85cae9356098c/yarl-1.18.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8503ad47387b8ebd39cbbbdf0bf113e17330ffd339ba1144074da24c545f0069", size = 141555, upload-time = "2024-12-01T20:33:08.819Z" }, + { url = "https://files.pythonhosted.org/packages/6d/9c/0a49af78df099c283ca3444560f10718fadb8a18dc8b3edf8c7bd9fd7d89/yarl-1.18.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:02ddb6756f8f4517a2d5e99d8b2f272488e18dd0bfbc802f31c16c6c20f22193", size = 94351, upload-time = "2024-12-01T20:33:10.609Z" }, + { url = "https://files.pythonhosted.org/packages/5a/a1/205ab51e148fdcedad189ca8dd587794c6f119882437d04c33c01a75dece/yarl-1.18.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:67a283dd2882ac98cc6318384f565bffc751ab564605959df4752d42483ad889", size = 92286, upload-time = "2024-12-01T20:33:12.322Z" }, + { url = "https://files.pythonhosted.org/packages/ed/fe/88b690b30f3f59275fb674f5f93ddd4a3ae796c2b62e5bb9ece8a4914b83/yarl-1.18.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d980e0325b6eddc81331d3f4551e2a333999fb176fd153e075c6d1c2530aa8a8", size = 340649, upload-time = "2024-12-01T20:33:13.842Z" }, + { url = "https://files.pythonhosted.org/packages/07/eb/3b65499b568e01f36e847cebdc8d7ccb51fff716dbda1ae83c3cbb8ca1c9/yarl-1.18.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b643562c12680b01e17239be267bc306bbc6aac1f34f6444d1bded0c5ce438ca", size = 356623, upload-time = "2024-12-01T20:33:15.535Z" }, + { url = "https://files.pythonhosted.org/packages/33/46/f559dc184280b745fc76ec6b1954de2c55595f0ec0a7614238b9ebf69618/yarl-1.18.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c017a3b6df3a1bd45b9fa49a0f54005e53fbcad16633870104b66fa1a30a29d8", size = 354007, upload-time = "2024-12-01T20:33:17.518Z" }, + { url = "https://files.pythonhosted.org/packages/af/ba/1865d85212351ad160f19fb99808acf23aab9a0f8ff31c8c9f1b4d671fc9/yarl-1.18.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:75674776d96d7b851b6498f17824ba17849d790a44d282929c42dbb77d4f17ae", size = 344145, upload-time = "2024-12-01T20:33:20.071Z" }, + { url = "https://files.pythonhosted.org/packages/94/cb/5c3e975d77755d7b3d5193e92056b19d83752ea2da7ab394e22260a7b824/yarl-1.18.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ccaa3a4b521b780a7e771cc336a2dba389a0861592bbce09a476190bb0c8b4b3", size = 336133, upload-time = "2024-12-01T20:33:22.515Z" }, + { url = "https://files.pythonhosted.org/packages/19/89/b77d3fd249ab52a5c40859815765d35c91425b6bb82e7427ab2f78f5ff55/yarl-1.18.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:2d06d3005e668744e11ed80812e61efd77d70bb7f03e33c1598c301eea20efbb", size = 347967, upload-time = "2024-12-01T20:33:24.139Z" }, + { url = "https://files.pythonhosted.org/packages/35/bd/f6b7630ba2cc06c319c3235634c582a6ab014d52311e7d7c22f9518189b5/yarl-1.18.3-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:9d41beda9dc97ca9ab0b9888cb71f7539124bc05df02c0cff6e5acc5a19dcc6e", size = 346397, upload-time = "2024-12-01T20:33:26.205Z" }, + { url = "https://files.pythonhosted.org/packages/18/1a/0b4e367d5a72d1f095318344848e93ea70da728118221f84f1bf6c1e39e7/yarl-1.18.3-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:ba23302c0c61a9999784e73809427c9dbedd79f66a13d84ad1b1943802eaaf59", size = 350206, upload-time = "2024-12-01T20:33:27.83Z" }, + { url = "https://files.pythonhosted.org/packages/b5/cf/320fff4367341fb77809a2d8d7fe75b5d323a8e1b35710aafe41fdbf327b/yarl-1.18.3-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:6748dbf9bfa5ba1afcc7556b71cda0d7ce5f24768043a02a58846e4a443d808d", size = 362089, upload-time = "2024-12-01T20:33:29.565Z" }, + { url = "https://files.pythonhosted.org/packages/57/cf/aadba261d8b920253204085268bad5e8cdd86b50162fcb1b10c10834885a/yarl-1.18.3-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:0b0cad37311123211dc91eadcb322ef4d4a66008d3e1bdc404808992260e1a0e", size = 366267, upload-time = "2024-12-01T20:33:31.449Z" }, + { url = "https://files.pythonhosted.org/packages/54/58/fb4cadd81acdee6dafe14abeb258f876e4dd410518099ae9a35c88d8097c/yarl-1.18.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0fb2171a4486bb075316ee754c6d8382ea6eb8b399d4ec62fde2b591f879778a", size = 359141, upload-time = "2024-12-01T20:33:33.79Z" }, + { url = "https://files.pythonhosted.org/packages/9a/7a/4c571597589da4cd5c14ed2a0b17ac56ec9ee7ee615013f74653169e702d/yarl-1.18.3-cp311-cp311-win32.whl", hash = "sha256:61b1a825a13bef4a5f10b1885245377d3cd0bf87cba068e1d9a88c2ae36880e1", size = 84402, upload-time = "2024-12-01T20:33:35.689Z" }, + { url = "https://files.pythonhosted.org/packages/ae/7b/8600250b3d89b625f1121d897062f629883c2f45339623b69b1747ec65fa/yarl-1.18.3-cp311-cp311-win_amd64.whl", hash = "sha256:b9d60031cf568c627d028239693fd718025719c02c9f55df0a53e587aab951b5", size = 91030, upload-time = "2024-12-01T20:33:37.511Z" }, + { url = "https://files.pythonhosted.org/packages/33/85/bd2e2729752ff4c77338e0102914897512e92496375e079ce0150a6dc306/yarl-1.18.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:1dd4bdd05407ced96fed3d7f25dbbf88d2ffb045a0db60dbc247f5b3c5c25d50", size = 142644, upload-time = "2024-12-01T20:33:39.204Z" }, + { url = "https://files.pythonhosted.org/packages/ff/74/1178322cc0f10288d7eefa6e4a85d8d2e28187ccab13d5b844e8b5d7c88d/yarl-1.18.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7c33dd1931a95e5d9a772d0ac5e44cac8957eaf58e3c8da8c1414de7dd27c576", size = 94962, upload-time = "2024-12-01T20:33:40.808Z" }, + { url = "https://files.pythonhosted.org/packages/be/75/79c6acc0261e2c2ae8a1c41cf12265e91628c8c58ae91f5ff59e29c0787f/yarl-1.18.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:25b411eddcfd56a2f0cd6a384e9f4f7aa3efee14b188de13048c25b5e91f1640", size = 92795, upload-time = "2024-12-01T20:33:42.322Z" }, + { url = "https://files.pythonhosted.org/packages/6b/32/927b2d67a412c31199e83fefdce6e645247b4fb164aa1ecb35a0f9eb2058/yarl-1.18.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:436c4fc0a4d66b2badc6c5fc5ef4e47bb10e4fd9bf0c79524ac719a01f3607c2", size = 332368, upload-time = "2024-12-01T20:33:43.956Z" }, + { url = "https://files.pythonhosted.org/packages/19/e5/859fca07169d6eceeaa4fde1997c91d8abde4e9a7c018e371640c2da2b71/yarl-1.18.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e35ef8683211db69ffe129a25d5634319a677570ab6b2eba4afa860f54eeaf75", size = 342314, upload-time = "2024-12-01T20:33:46.046Z" }, + { url = "https://files.pythonhosted.org/packages/08/75/76b63ccd91c9e03ab213ef27ae6add2e3400e77e5cdddf8ed2dbc36e3f21/yarl-1.18.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:84b2deecba4a3f1a398df819151eb72d29bfeb3b69abb145a00ddc8d30094512", size = 341987, upload-time = "2024-12-01T20:33:48.352Z" }, + { url = "https://files.pythonhosted.org/packages/1a/e1/a097d5755d3ea8479a42856f51d97eeff7a3a7160593332d98f2709b3580/yarl-1.18.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:00e5a1fea0fd4f5bfa7440a47eff01d9822a65b4488f7cff83155a0f31a2ecba", size = 336914, upload-time = "2024-12-01T20:33:50.875Z" }, + { url = "https://files.pythonhosted.org/packages/0b/42/e1b4d0e396b7987feceebe565286c27bc085bf07d61a59508cdaf2d45e63/yarl-1.18.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d0e883008013c0e4aef84dcfe2a0b172c4d23c2669412cf5b3371003941f72bb", size = 325765, upload-time = "2024-12-01T20:33:52.641Z" }, + { url = "https://files.pythonhosted.org/packages/7e/18/03a5834ccc9177f97ca1bbb245b93c13e58e8225276f01eedc4cc98ab820/yarl-1.18.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5a3f356548e34a70b0172d8890006c37be92995f62d95a07b4a42e90fba54272", size = 344444, upload-time = "2024-12-01T20:33:54.395Z" }, + { url = "https://files.pythonhosted.org/packages/c8/03/a713633bdde0640b0472aa197b5b86e90fbc4c5bc05b727b714cd8a40e6d/yarl-1.18.3-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:ccd17349166b1bee6e529b4add61727d3f55edb7babbe4069b5764c9587a8cc6", size = 340760, upload-time = "2024-12-01T20:33:56.286Z" }, + { url = "https://files.pythonhosted.org/packages/eb/99/f6567e3f3bbad8fd101886ea0276c68ecb86a2b58be0f64077396cd4b95e/yarl-1.18.3-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:b958ddd075ddba5b09bb0be8a6d9906d2ce933aee81100db289badbeb966f54e", size = 346484, upload-time = "2024-12-01T20:33:58.375Z" }, + { url = "https://files.pythonhosted.org/packages/8e/a9/84717c896b2fc6cb15bd4eecd64e34a2f0a9fd6669e69170c73a8b46795a/yarl-1.18.3-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c7d79f7d9aabd6011004e33b22bc13056a3e3fb54794d138af57f5ee9d9032cb", size = 359864, upload-time = "2024-12-01T20:34:00.22Z" }, + { url = "https://files.pythonhosted.org/packages/1e/2e/d0f5f1bef7ee93ed17e739ec8dbcb47794af891f7d165fa6014517b48169/yarl-1.18.3-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:4891ed92157e5430874dad17b15eb1fda57627710756c27422200c52d8a4e393", size = 364537, upload-time = "2024-12-01T20:34:03.54Z" }, + { url = "https://files.pythonhosted.org/packages/97/8a/568d07c5d4964da5b02621a517532adb8ec5ba181ad1687191fffeda0ab6/yarl-1.18.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ce1af883b94304f493698b00d0f006d56aea98aeb49d75ec7d98cd4a777e9285", size = 357861, upload-time = "2024-12-01T20:34:05.73Z" }, + { url = "https://files.pythonhosted.org/packages/7d/e3/924c3f64b6b3077889df9a1ece1ed8947e7b61b0a933f2ec93041990a677/yarl-1.18.3-cp312-cp312-win32.whl", hash = "sha256:f91c4803173928a25e1a55b943c81f55b8872f0018be83e3ad4938adffb77dd2", size = 84097, upload-time = "2024-12-01T20:34:07.664Z" }, + { url = "https://files.pythonhosted.org/packages/34/45/0e055320daaabfc169b21ff6174567b2c910c45617b0d79c68d7ab349b02/yarl-1.18.3-cp312-cp312-win_amd64.whl", hash = "sha256:7e2ee16578af3b52ac2f334c3b1f92262f47e02cc6193c598502bd46f5cd1477", size = 90399, upload-time = "2024-12-01T20:34:09.61Z" }, + { url = "https://files.pythonhosted.org/packages/f5/4b/a06e0ec3d155924f77835ed2d167ebd3b211a7b0853da1cf8d8414d784ef/yarl-1.18.3-py3-none-any.whl", hash = "sha256:b57f4f58099328dfb26c6a771d09fb20dbbae81d20cfb66141251ea063bd101b", size = 45109, upload-time = "2024-12-01T20:35:20.834Z" }, ] [[package]] name = "zipp" -version = "3.21.0" +version = "3.22.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/3f/50/bad581df71744867e9468ebd0bcd6505de3b275e06f202c2cb016e3ff56f/zipp-3.21.0.tar.gz", hash = "sha256:2c9958f6430a2040341a52eb608ed6dd93ef4392e02ffe219417c1b28b5dd1f4", size = 24545 } +sdist = { url = "https://files.pythonhosted.org/packages/12/b6/7b3d16792fdf94f146bed92be90b4eb4563569eca91513c8609aebf0c167/zipp-3.22.0.tar.gz", hash = "sha256:dd2f28c3ce4bc67507bfd3781d21b7bb2be31103b51a4553ad7d90b84e57ace5", size = 25257, upload-time = "2025-05-26T14:46:32.217Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b7/1a/7e4798e9339adc931158c9d69ecc34f5e6791489d469f5e50ec15e35f458/zipp-3.21.0-py3-none-any.whl", hash = "sha256:ac1bbe05fd2991f160ebce24ffbac5f6d11d83dc90891255885223d42b3cd931", size = 9630 }, + { url = "https://files.pythonhosted.org/packages/ad/da/f64669af4cae46f17b90798a827519ce3737d31dbafad65d391e49643dc4/zipp-3.22.0-py3-none-any.whl", hash = "sha256:fe208f65f2aca48b81f9e6fd8cf7b8b32c26375266b009b413d45306b6148343", size = 9796, upload-time = "2025-05-26T14:46:30.775Z" }, ] [[package]] @@ -6221,9 +6635,9 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "setuptools" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/46/c2/427f1867bb96555d1d34342f1dd97f8c420966ab564d58d18469a1db8736/zope.event-5.0.tar.gz", hash = "sha256:bac440d8d9891b4068e2b5a2c5e2c9765a9df762944bda6955f96bb9b91e67cd", size = 17350 } +sdist = { url = "https://files.pythonhosted.org/packages/46/c2/427f1867bb96555d1d34342f1dd97f8c420966ab564d58d18469a1db8736/zope.event-5.0.tar.gz", hash = "sha256:bac440d8d9891b4068e2b5a2c5e2c9765a9df762944bda6955f96bb9b91e67cd", size = 17350, upload-time = "2023-06-23T06:28:35.709Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/fe/42/f8dbc2b9ad59e927940325a22d6d3931d630c3644dae7e2369ef5d9ba230/zope.event-5.0-py3-none-any.whl", hash = "sha256:2832e95014f4db26c47a13fdaef84cef2f4df37e66b59d8f1f4a8f319a632c26", size = 6824 }, + { url = "https://files.pythonhosted.org/packages/fe/42/f8dbc2b9ad59e927940325a22d6d3931d630c3644dae7e2369ef5d9ba230/zope.event-5.0-py3-none-any.whl", hash = "sha256:2832e95014f4db26c47a13fdaef84cef2f4df37e66b59d8f1f4a8f319a632c26", size = 6824, upload-time = "2023-06-23T06:28:32.652Z" }, ] [[package]] @@ -6233,20 +6647,20 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "setuptools" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/30/93/9210e7606be57a2dfc6277ac97dcc864fd8d39f142ca194fdc186d596fda/zope.interface-7.2.tar.gz", hash = "sha256:8b49f1a3d1ee4cdaf5b32d2e738362c7f5e40ac8b46dd7d1a65e82a4872728fe", size = 252960 } +sdist = { url = "https://files.pythonhosted.org/packages/30/93/9210e7606be57a2dfc6277ac97dcc864fd8d39f142ca194fdc186d596fda/zope.interface-7.2.tar.gz", hash = "sha256:8b49f1a3d1ee4cdaf5b32d2e738362c7f5e40ac8b46dd7d1a65e82a4872728fe", size = 252960, upload-time = "2024-11-28T08:45:39.224Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/98/7d/2e8daf0abea7798d16a58f2f3a2bf7588872eee54ac119f99393fdd47b65/zope.interface-7.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1909f52a00c8c3dcab6c4fad5d13de2285a4b3c7be063b239b8dc15ddfb73bd2", size = 208776 }, - { url = "https://files.pythonhosted.org/packages/a0/2a/0c03c7170fe61d0d371e4c7ea5b62b8cb79b095b3d630ca16719bf8b7b18/zope.interface-7.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:80ecf2451596f19fd607bb09953f426588fc1e79e93f5968ecf3367550396b22", size = 209296 }, - { url = "https://files.pythonhosted.org/packages/49/b4/451f19448772b4a1159519033a5f72672221e623b0a1bd2b896b653943d8/zope.interface-7.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:033b3923b63474800b04cba480b70f6e6243a62208071fc148354f3f89cc01b7", size = 260997 }, - { url = "https://files.pythonhosted.org/packages/65/94/5aa4461c10718062c8f8711161faf3249d6d3679c24a0b81dd6fc8ba1dd3/zope.interface-7.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a102424e28c6b47c67923a1f337ede4a4c2bba3965b01cf707978a801fc7442c", size = 255038 }, - { url = "https://files.pythonhosted.org/packages/9f/aa/1a28c02815fe1ca282b54f6705b9ddba20328fabdc37b8cf73fc06b172f0/zope.interface-7.2-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:25e6a61dcb184453bb00eafa733169ab6d903e46f5c2ace4ad275386f9ab327a", size = 259806 }, - { url = "https://files.pythonhosted.org/packages/a7/2c/82028f121d27c7e68632347fe04f4a6e0466e77bb36e104c8b074f3d7d7b/zope.interface-7.2-cp311-cp311-win_amd64.whl", hash = "sha256:3f6771d1647b1fc543d37640b45c06b34832a943c80d1db214a37c31161a93f1", size = 212305 }, - { url = "https://files.pythonhosted.org/packages/68/0b/c7516bc3bad144c2496f355e35bd699443b82e9437aa02d9867653203b4a/zope.interface-7.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:086ee2f51eaef1e4a52bd7d3111a0404081dadae87f84c0ad4ce2649d4f708b7", size = 208959 }, - { url = "https://files.pythonhosted.org/packages/a2/e9/1463036df1f78ff8c45a02642a7bf6931ae4a38a4acd6a8e07c128e387a7/zope.interface-7.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:21328fcc9d5b80768bf051faa35ab98fb979080c18e6f84ab3f27ce703bce465", size = 209357 }, - { url = "https://files.pythonhosted.org/packages/07/a8/106ca4c2add440728e382f1b16c7d886563602487bdd90004788d45eb310/zope.interface-7.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f6dd02ec01f4468da0f234da9d9c8545c5412fef80bc590cc51d8dd084138a89", size = 264235 }, - { url = "https://files.pythonhosted.org/packages/fc/ca/57286866285f4b8a4634c12ca1957c24bdac06eae28fd4a3a578e30cf906/zope.interface-7.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8e7da17f53e25d1a3bde5da4601e026adc9e8071f9f6f936d0fe3fe84ace6d54", size = 259253 }, - { url = "https://files.pythonhosted.org/packages/96/08/2103587ebc989b455cf05e858e7fbdfeedfc3373358320e9c513428290b1/zope.interface-7.2-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cab15ff4832580aa440dc9790b8a6128abd0b88b7ee4dd56abacbc52f212209d", size = 264702 }, - { url = "https://files.pythonhosted.org/packages/5f/c7/3c67562e03b3752ba4ab6b23355f15a58ac2d023a6ef763caaca430f91f2/zope.interface-7.2-cp312-cp312-win_amd64.whl", hash = "sha256:29caad142a2355ce7cfea48725aa8bcf0067e2b5cc63fcf5cd9f97ad12d6afb5", size = 212466 }, + { url = "https://files.pythonhosted.org/packages/98/7d/2e8daf0abea7798d16a58f2f3a2bf7588872eee54ac119f99393fdd47b65/zope.interface-7.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1909f52a00c8c3dcab6c4fad5d13de2285a4b3c7be063b239b8dc15ddfb73bd2", size = 208776, upload-time = "2024-11-28T08:47:53.009Z" }, + { url = "https://files.pythonhosted.org/packages/a0/2a/0c03c7170fe61d0d371e4c7ea5b62b8cb79b095b3d630ca16719bf8b7b18/zope.interface-7.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:80ecf2451596f19fd607bb09953f426588fc1e79e93f5968ecf3367550396b22", size = 209296, upload-time = "2024-11-28T08:47:57.993Z" }, + { url = "https://files.pythonhosted.org/packages/49/b4/451f19448772b4a1159519033a5f72672221e623b0a1bd2b896b653943d8/zope.interface-7.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:033b3923b63474800b04cba480b70f6e6243a62208071fc148354f3f89cc01b7", size = 260997, upload-time = "2024-11-28T09:18:13.935Z" }, + { url = "https://files.pythonhosted.org/packages/65/94/5aa4461c10718062c8f8711161faf3249d6d3679c24a0b81dd6fc8ba1dd3/zope.interface-7.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a102424e28c6b47c67923a1f337ede4a4c2bba3965b01cf707978a801fc7442c", size = 255038, upload-time = "2024-11-28T08:48:26.381Z" }, + { url = "https://files.pythonhosted.org/packages/9f/aa/1a28c02815fe1ca282b54f6705b9ddba20328fabdc37b8cf73fc06b172f0/zope.interface-7.2-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:25e6a61dcb184453bb00eafa733169ab6d903e46f5c2ace4ad275386f9ab327a", size = 259806, upload-time = "2024-11-28T08:48:30.78Z" }, + { url = "https://files.pythonhosted.org/packages/a7/2c/82028f121d27c7e68632347fe04f4a6e0466e77bb36e104c8b074f3d7d7b/zope.interface-7.2-cp311-cp311-win_amd64.whl", hash = "sha256:3f6771d1647b1fc543d37640b45c06b34832a943c80d1db214a37c31161a93f1", size = 212305, upload-time = "2024-11-28T08:49:14.525Z" }, + { url = "https://files.pythonhosted.org/packages/68/0b/c7516bc3bad144c2496f355e35bd699443b82e9437aa02d9867653203b4a/zope.interface-7.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:086ee2f51eaef1e4a52bd7d3111a0404081dadae87f84c0ad4ce2649d4f708b7", size = 208959, upload-time = "2024-11-28T08:47:47.788Z" }, + { url = "https://files.pythonhosted.org/packages/a2/e9/1463036df1f78ff8c45a02642a7bf6931ae4a38a4acd6a8e07c128e387a7/zope.interface-7.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:21328fcc9d5b80768bf051faa35ab98fb979080c18e6f84ab3f27ce703bce465", size = 209357, upload-time = "2024-11-28T08:47:50.897Z" }, + { url = "https://files.pythonhosted.org/packages/07/a8/106ca4c2add440728e382f1b16c7d886563602487bdd90004788d45eb310/zope.interface-7.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f6dd02ec01f4468da0f234da9d9c8545c5412fef80bc590cc51d8dd084138a89", size = 264235, upload-time = "2024-11-28T09:18:15.56Z" }, + { url = "https://files.pythonhosted.org/packages/fc/ca/57286866285f4b8a4634c12ca1957c24bdac06eae28fd4a3a578e30cf906/zope.interface-7.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8e7da17f53e25d1a3bde5da4601e026adc9e8071f9f6f936d0fe3fe84ace6d54", size = 259253, upload-time = "2024-11-28T08:48:29.025Z" }, + { url = "https://files.pythonhosted.org/packages/96/08/2103587ebc989b455cf05e858e7fbdfeedfc3373358320e9c513428290b1/zope.interface-7.2-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cab15ff4832580aa440dc9790b8a6128abd0b88b7ee4dd56abacbc52f212209d", size = 264702, upload-time = "2024-11-28T08:48:37.363Z" }, + { url = "https://files.pythonhosted.org/packages/5f/c7/3c67562e03b3752ba4ab6b23355f15a58ac2d023a6ef763caaca430f91f2/zope.interface-7.2-cp312-cp312-win_amd64.whl", hash = "sha256:29caad142a2355ce7cfea48725aa8bcf0067e2b5cc63fcf5cd9f97ad12d6afb5", size = 212466, upload-time = "2024-11-28T08:49:14.397Z" }, ] [[package]] @@ -6256,40 +6670,40 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cffi", marker = "platform_python_implementation == 'PyPy'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ed/f6/2ac0287b442160a89d726b17a9184a4c615bb5237db763791a7fd16d9df1/zstandard-0.23.0.tar.gz", hash = "sha256:b2d8c62d08e7255f68f7a740bae85b3c9b8e5466baa9cbf7f57f1cde0ac6bc09", size = 681701 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/9e/40/f67e7d2c25a0e2dc1744dd781110b0b60306657f8696cafb7ad7579469bd/zstandard-0.23.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:34895a41273ad33347b2fc70e1bff4240556de3c46c6ea430a7ed91f9042aa4e", size = 788699 }, - { url = "https://files.pythonhosted.org/packages/e8/46/66d5b55f4d737dd6ab75851b224abf0afe5774976fe511a54d2eb9063a41/zstandard-0.23.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:77ea385f7dd5b5676d7fd943292ffa18fbf5c72ba98f7d09fc1fb9e819b34c23", size = 633681 }, - { url = "https://files.pythonhosted.org/packages/63/b6/677e65c095d8e12b66b8f862b069bcf1f1d781b9c9c6f12eb55000d57583/zstandard-0.23.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:983b6efd649723474f29ed42e1467f90a35a74793437d0bc64a5bf482bedfa0a", size = 4944328 }, - { url = "https://files.pythonhosted.org/packages/59/cc/e76acb4c42afa05a9d20827116d1f9287e9c32b7ad58cc3af0721ce2b481/zstandard-0.23.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:80a539906390591dd39ebb8d773771dc4db82ace6372c4d41e2d293f8e32b8db", size = 5311955 }, - { url = "https://files.pythonhosted.org/packages/78/e4/644b8075f18fc7f632130c32e8f36f6dc1b93065bf2dd87f03223b187f26/zstandard-0.23.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:445e4cb5048b04e90ce96a79b4b63140e3f4ab5f662321975679b5f6360b90e2", size = 5344944 }, - { url = "https://files.pythonhosted.org/packages/76/3f/dbafccf19cfeca25bbabf6f2dd81796b7218f768ec400f043edc767015a6/zstandard-0.23.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd30d9c67d13d891f2360b2a120186729c111238ac63b43dbd37a5a40670b8ca", size = 5442927 }, - { url = "https://files.pythonhosted.org/packages/0c/c3/d24a01a19b6733b9f218e94d1a87c477d523237e07f94899e1c10f6fd06c/zstandard-0.23.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d20fd853fbb5807c8e84c136c278827b6167ded66c72ec6f9a14b863d809211c", size = 4864910 }, - { url = "https://files.pythonhosted.org/packages/1c/a9/cf8f78ead4597264f7618d0875be01f9bc23c9d1d11afb6d225b867cb423/zstandard-0.23.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ed1708dbf4d2e3a1c5c69110ba2b4eb6678262028afd6c6fbcc5a8dac9cda68e", size = 4935544 }, - { url = "https://files.pythonhosted.org/packages/2c/96/8af1e3731b67965fb995a940c04a2c20997a7b3b14826b9d1301cf160879/zstandard-0.23.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:be9b5b8659dff1f913039c2feee1aca499cfbc19e98fa12bc85e037c17ec6ca5", size = 5467094 }, - { url = "https://files.pythonhosted.org/packages/ff/57/43ea9df642c636cb79f88a13ab07d92d88d3bfe3e550b55a25a07a26d878/zstandard-0.23.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:65308f4b4890aa12d9b6ad9f2844b7ee42c7f7a4fd3390425b242ffc57498f48", size = 4860440 }, - { url = "https://files.pythonhosted.org/packages/46/37/edb78f33c7f44f806525f27baa300341918fd4c4af9472fbc2c3094be2e8/zstandard-0.23.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:98da17ce9cbf3bfe4617e836d561e433f871129e3a7ac16d6ef4c680f13a839c", size = 4700091 }, - { url = "https://files.pythonhosted.org/packages/c1/f1/454ac3962671a754f3cb49242472df5c2cced4eb959ae203a377b45b1a3c/zstandard-0.23.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:8ed7d27cb56b3e058d3cf684d7200703bcae623e1dcc06ed1e18ecda39fee003", size = 5208682 }, - { url = "https://files.pythonhosted.org/packages/85/b2/1734b0fff1634390b1b887202d557d2dd542de84a4c155c258cf75da4773/zstandard-0.23.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:b69bb4f51daf461b15e7b3db033160937d3ff88303a7bc808c67bbc1eaf98c78", size = 5669707 }, - { url = "https://files.pythonhosted.org/packages/52/5a/87d6971f0997c4b9b09c495bf92189fb63de86a83cadc4977dc19735f652/zstandard-0.23.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:034b88913ecc1b097f528e42b539453fa82c3557e414b3de9d5632c80439a473", size = 5201792 }, - { url = "https://files.pythonhosted.org/packages/79/02/6f6a42cc84459d399bd1a4e1adfc78d4dfe45e56d05b072008d10040e13b/zstandard-0.23.0-cp311-cp311-win32.whl", hash = "sha256:f2d4380bf5f62daabd7b751ea2339c1a21d1c9463f1feb7fc2bdcea2c29c3160", size = 430586 }, - { url = "https://files.pythonhosted.org/packages/be/a2/4272175d47c623ff78196f3c10e9dc7045c1b9caf3735bf041e65271eca4/zstandard-0.23.0-cp311-cp311-win_amd64.whl", hash = "sha256:62136da96a973bd2557f06ddd4e8e807f9e13cbb0bfb9cc06cfe6d98ea90dfe0", size = 495420 }, - { url = "https://files.pythonhosted.org/packages/7b/83/f23338c963bd9de687d47bf32efe9fd30164e722ba27fb59df33e6b1719b/zstandard-0.23.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b4567955a6bc1b20e9c31612e615af6b53733491aeaa19a6b3b37f3b65477094", size = 788713 }, - { url = "https://files.pythonhosted.org/packages/5b/b3/1a028f6750fd9227ee0b937a278a434ab7f7fdc3066c3173f64366fe2466/zstandard-0.23.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1e172f57cd78c20f13a3415cc8dfe24bf388614324d25539146594c16d78fcc8", size = 633459 }, - { url = "https://files.pythonhosted.org/packages/26/af/36d89aae0c1f95a0a98e50711bc5d92c144939efc1f81a2fcd3e78d7f4c1/zstandard-0.23.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b0e166f698c5a3e914947388c162be2583e0c638a4703fc6a543e23a88dea3c1", size = 4945707 }, - { url = "https://files.pythonhosted.org/packages/cd/2e/2051f5c772f4dfc0aae3741d5fc72c3dcfe3aaeb461cc231668a4db1ce14/zstandard-0.23.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:12a289832e520c6bd4dcaad68e944b86da3bad0d339ef7989fb7e88f92e96072", size = 5306545 }, - { url = "https://files.pythonhosted.org/packages/0a/9e/a11c97b087f89cab030fa71206963090d2fecd8eb83e67bb8f3ffb84c024/zstandard-0.23.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d50d31bfedd53a928fed6707b15a8dbeef011bb6366297cc435accc888b27c20", size = 5337533 }, - { url = "https://files.pythonhosted.org/packages/fc/79/edeb217c57fe1bf16d890aa91a1c2c96b28c07b46afed54a5dcf310c3f6f/zstandard-0.23.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:72c68dda124a1a138340fb62fa21b9bf4848437d9ca60bd35db36f2d3345f373", size = 5436510 }, - { url = "https://files.pythonhosted.org/packages/81/4f/c21383d97cb7a422ddf1ae824b53ce4b51063d0eeb2afa757eb40804a8ef/zstandard-0.23.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:53dd9d5e3d29f95acd5de6802e909ada8d8d8cfa37a3ac64836f3bc4bc5512db", size = 4859973 }, - { url = "https://files.pythonhosted.org/packages/ab/15/08d22e87753304405ccac8be2493a495f529edd81d39a0870621462276ef/zstandard-0.23.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:6a41c120c3dbc0d81a8e8adc73312d668cd34acd7725f036992b1b72d22c1772", size = 4936968 }, - { url = "https://files.pythonhosted.org/packages/eb/fa/f3670a597949fe7dcf38119a39f7da49a8a84a6f0b1a2e46b2f71a0ab83f/zstandard-0.23.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:40b33d93c6eddf02d2c19f5773196068d875c41ca25730e8288e9b672897c105", size = 5467179 }, - { url = "https://files.pythonhosted.org/packages/4e/a9/dad2ab22020211e380adc477a1dbf9f109b1f8d94c614944843e20dc2a99/zstandard-0.23.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:9206649ec587e6b02bd124fb7799b86cddec350f6f6c14bc82a2b70183e708ba", size = 4848577 }, - { url = "https://files.pythonhosted.org/packages/08/03/dd28b4484b0770f1e23478413e01bee476ae8227bbc81561f9c329e12564/zstandard-0.23.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:76e79bc28a65f467e0409098fa2c4376931fd3207fbeb6b956c7c476d53746dd", size = 4693899 }, - { url = "https://files.pythonhosted.org/packages/2b/64/3da7497eb635d025841e958bcd66a86117ae320c3b14b0ae86e9e8627518/zstandard-0.23.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:66b689c107857eceabf2cf3d3fc699c3c0fe8ccd18df2219d978c0283e4c508a", size = 5199964 }, - { url = "https://files.pythonhosted.org/packages/43/a4/d82decbab158a0e8a6ebb7fc98bc4d903266bce85b6e9aaedea1d288338c/zstandard-0.23.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:9c236e635582742fee16603042553d276cca506e824fa2e6489db04039521e90", size = 5655398 }, - { url = "https://files.pythonhosted.org/packages/f2/61/ac78a1263bc83a5cf29e7458b77a568eda5a8f81980691bbc6eb6a0d45cc/zstandard-0.23.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a8fffdbd9d1408006baaf02f1068d7dd1f016c6bcb7538682622c556e7b68e35", size = 5191313 }, - { url = "https://files.pythonhosted.org/packages/e7/54/967c478314e16af5baf849b6ee9d6ea724ae5b100eb506011f045d3d4e16/zstandard-0.23.0-cp312-cp312-win32.whl", hash = "sha256:dc1d33abb8a0d754ea4763bad944fd965d3d95b5baef6b121c0c9013eaf1907d", size = 430877 }, - { url = "https://files.pythonhosted.org/packages/75/37/872d74bd7739639c4553bf94c84af7d54d8211b626b352bc57f0fd8d1e3f/zstandard-0.23.0-cp312-cp312-win_amd64.whl", hash = "sha256:64585e1dba664dc67c7cdabd56c1e5685233fbb1fc1966cfba2a340ec0dfff7b", size = 495595 }, +sdist = { url = "https://files.pythonhosted.org/packages/ed/f6/2ac0287b442160a89d726b17a9184a4c615bb5237db763791a7fd16d9df1/zstandard-0.23.0.tar.gz", hash = "sha256:b2d8c62d08e7255f68f7a740bae85b3c9b8e5466baa9cbf7f57f1cde0ac6bc09", size = 681701, upload-time = "2024-07-15T00:18:06.141Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9e/40/f67e7d2c25a0e2dc1744dd781110b0b60306657f8696cafb7ad7579469bd/zstandard-0.23.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:34895a41273ad33347b2fc70e1bff4240556de3c46c6ea430a7ed91f9042aa4e", size = 788699, upload-time = "2024-07-15T00:14:04.909Z" }, + { url = "https://files.pythonhosted.org/packages/e8/46/66d5b55f4d737dd6ab75851b224abf0afe5774976fe511a54d2eb9063a41/zstandard-0.23.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:77ea385f7dd5b5676d7fd943292ffa18fbf5c72ba98f7d09fc1fb9e819b34c23", size = 633681, upload-time = "2024-07-15T00:14:13.99Z" }, + { url = "https://files.pythonhosted.org/packages/63/b6/677e65c095d8e12b66b8f862b069bcf1f1d781b9c9c6f12eb55000d57583/zstandard-0.23.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:983b6efd649723474f29ed42e1467f90a35a74793437d0bc64a5bf482bedfa0a", size = 4944328, upload-time = "2024-07-15T00:14:16.588Z" }, + { url = "https://files.pythonhosted.org/packages/59/cc/e76acb4c42afa05a9d20827116d1f9287e9c32b7ad58cc3af0721ce2b481/zstandard-0.23.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:80a539906390591dd39ebb8d773771dc4db82ace6372c4d41e2d293f8e32b8db", size = 5311955, upload-time = "2024-07-15T00:14:19.389Z" }, + { url = "https://files.pythonhosted.org/packages/78/e4/644b8075f18fc7f632130c32e8f36f6dc1b93065bf2dd87f03223b187f26/zstandard-0.23.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:445e4cb5048b04e90ce96a79b4b63140e3f4ab5f662321975679b5f6360b90e2", size = 5344944, upload-time = "2024-07-15T00:14:22.173Z" }, + { url = "https://files.pythonhosted.org/packages/76/3f/dbafccf19cfeca25bbabf6f2dd81796b7218f768ec400f043edc767015a6/zstandard-0.23.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd30d9c67d13d891f2360b2a120186729c111238ac63b43dbd37a5a40670b8ca", size = 5442927, upload-time = "2024-07-15T00:14:24.825Z" }, + { url = "https://files.pythonhosted.org/packages/0c/c3/d24a01a19b6733b9f218e94d1a87c477d523237e07f94899e1c10f6fd06c/zstandard-0.23.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d20fd853fbb5807c8e84c136c278827b6167ded66c72ec6f9a14b863d809211c", size = 4864910, upload-time = "2024-07-15T00:14:26.982Z" }, + { url = "https://files.pythonhosted.org/packages/1c/a9/cf8f78ead4597264f7618d0875be01f9bc23c9d1d11afb6d225b867cb423/zstandard-0.23.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ed1708dbf4d2e3a1c5c69110ba2b4eb6678262028afd6c6fbcc5a8dac9cda68e", size = 4935544, upload-time = "2024-07-15T00:14:29.582Z" }, + { url = "https://files.pythonhosted.org/packages/2c/96/8af1e3731b67965fb995a940c04a2c20997a7b3b14826b9d1301cf160879/zstandard-0.23.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:be9b5b8659dff1f913039c2feee1aca499cfbc19e98fa12bc85e037c17ec6ca5", size = 5467094, upload-time = "2024-07-15T00:14:40.126Z" }, + { url = "https://files.pythonhosted.org/packages/ff/57/43ea9df642c636cb79f88a13ab07d92d88d3bfe3e550b55a25a07a26d878/zstandard-0.23.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:65308f4b4890aa12d9b6ad9f2844b7ee42c7f7a4fd3390425b242ffc57498f48", size = 4860440, upload-time = "2024-07-15T00:14:42.786Z" }, + { url = "https://files.pythonhosted.org/packages/46/37/edb78f33c7f44f806525f27baa300341918fd4c4af9472fbc2c3094be2e8/zstandard-0.23.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:98da17ce9cbf3bfe4617e836d561e433f871129e3a7ac16d6ef4c680f13a839c", size = 4700091, upload-time = "2024-07-15T00:14:45.184Z" }, + { url = "https://files.pythonhosted.org/packages/c1/f1/454ac3962671a754f3cb49242472df5c2cced4eb959ae203a377b45b1a3c/zstandard-0.23.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:8ed7d27cb56b3e058d3cf684d7200703bcae623e1dcc06ed1e18ecda39fee003", size = 5208682, upload-time = "2024-07-15T00:14:47.407Z" }, + { url = "https://files.pythonhosted.org/packages/85/b2/1734b0fff1634390b1b887202d557d2dd542de84a4c155c258cf75da4773/zstandard-0.23.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:b69bb4f51daf461b15e7b3db033160937d3ff88303a7bc808c67bbc1eaf98c78", size = 5669707, upload-time = "2024-07-15T00:15:03.529Z" }, + { url = "https://files.pythonhosted.org/packages/52/5a/87d6971f0997c4b9b09c495bf92189fb63de86a83cadc4977dc19735f652/zstandard-0.23.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:034b88913ecc1b097f528e42b539453fa82c3557e414b3de9d5632c80439a473", size = 5201792, upload-time = "2024-07-15T00:15:28.372Z" }, + { url = "https://files.pythonhosted.org/packages/79/02/6f6a42cc84459d399bd1a4e1adfc78d4dfe45e56d05b072008d10040e13b/zstandard-0.23.0-cp311-cp311-win32.whl", hash = "sha256:f2d4380bf5f62daabd7b751ea2339c1a21d1c9463f1feb7fc2bdcea2c29c3160", size = 430586, upload-time = "2024-07-15T00:15:32.26Z" }, + { url = "https://files.pythonhosted.org/packages/be/a2/4272175d47c623ff78196f3c10e9dc7045c1b9caf3735bf041e65271eca4/zstandard-0.23.0-cp311-cp311-win_amd64.whl", hash = "sha256:62136da96a973bd2557f06ddd4e8e807f9e13cbb0bfb9cc06cfe6d98ea90dfe0", size = 495420, upload-time = "2024-07-15T00:15:34.004Z" }, + { url = "https://files.pythonhosted.org/packages/7b/83/f23338c963bd9de687d47bf32efe9fd30164e722ba27fb59df33e6b1719b/zstandard-0.23.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b4567955a6bc1b20e9c31612e615af6b53733491aeaa19a6b3b37f3b65477094", size = 788713, upload-time = "2024-07-15T00:15:35.815Z" }, + { url = "https://files.pythonhosted.org/packages/5b/b3/1a028f6750fd9227ee0b937a278a434ab7f7fdc3066c3173f64366fe2466/zstandard-0.23.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1e172f57cd78c20f13a3415cc8dfe24bf388614324d25539146594c16d78fcc8", size = 633459, upload-time = "2024-07-15T00:15:37.995Z" }, + { url = "https://files.pythonhosted.org/packages/26/af/36d89aae0c1f95a0a98e50711bc5d92c144939efc1f81a2fcd3e78d7f4c1/zstandard-0.23.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b0e166f698c5a3e914947388c162be2583e0c638a4703fc6a543e23a88dea3c1", size = 4945707, upload-time = "2024-07-15T00:15:39.872Z" }, + { url = "https://files.pythonhosted.org/packages/cd/2e/2051f5c772f4dfc0aae3741d5fc72c3dcfe3aaeb461cc231668a4db1ce14/zstandard-0.23.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:12a289832e520c6bd4dcaad68e944b86da3bad0d339ef7989fb7e88f92e96072", size = 5306545, upload-time = "2024-07-15T00:15:41.75Z" }, + { url = "https://files.pythonhosted.org/packages/0a/9e/a11c97b087f89cab030fa71206963090d2fecd8eb83e67bb8f3ffb84c024/zstandard-0.23.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d50d31bfedd53a928fed6707b15a8dbeef011bb6366297cc435accc888b27c20", size = 5337533, upload-time = "2024-07-15T00:15:44.114Z" }, + { url = "https://files.pythonhosted.org/packages/fc/79/edeb217c57fe1bf16d890aa91a1c2c96b28c07b46afed54a5dcf310c3f6f/zstandard-0.23.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:72c68dda124a1a138340fb62fa21b9bf4848437d9ca60bd35db36f2d3345f373", size = 5436510, upload-time = "2024-07-15T00:15:46.509Z" }, + { url = "https://files.pythonhosted.org/packages/81/4f/c21383d97cb7a422ddf1ae824b53ce4b51063d0eeb2afa757eb40804a8ef/zstandard-0.23.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:53dd9d5e3d29f95acd5de6802e909ada8d8d8cfa37a3ac64836f3bc4bc5512db", size = 4859973, upload-time = "2024-07-15T00:15:49.939Z" }, + { url = "https://files.pythonhosted.org/packages/ab/15/08d22e87753304405ccac8be2493a495f529edd81d39a0870621462276ef/zstandard-0.23.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:6a41c120c3dbc0d81a8e8adc73312d668cd34acd7725f036992b1b72d22c1772", size = 4936968, upload-time = "2024-07-15T00:15:52.025Z" }, + { url = "https://files.pythonhosted.org/packages/eb/fa/f3670a597949fe7dcf38119a39f7da49a8a84a6f0b1a2e46b2f71a0ab83f/zstandard-0.23.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:40b33d93c6eddf02d2c19f5773196068d875c41ca25730e8288e9b672897c105", size = 5467179, upload-time = "2024-07-15T00:15:54.971Z" }, + { url = "https://files.pythonhosted.org/packages/4e/a9/dad2ab22020211e380adc477a1dbf9f109b1f8d94c614944843e20dc2a99/zstandard-0.23.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:9206649ec587e6b02bd124fb7799b86cddec350f6f6c14bc82a2b70183e708ba", size = 4848577, upload-time = "2024-07-15T00:15:57.634Z" }, + { url = "https://files.pythonhosted.org/packages/08/03/dd28b4484b0770f1e23478413e01bee476ae8227bbc81561f9c329e12564/zstandard-0.23.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:76e79bc28a65f467e0409098fa2c4376931fd3207fbeb6b956c7c476d53746dd", size = 4693899, upload-time = "2024-07-15T00:16:00.811Z" }, + { url = "https://files.pythonhosted.org/packages/2b/64/3da7497eb635d025841e958bcd66a86117ae320c3b14b0ae86e9e8627518/zstandard-0.23.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:66b689c107857eceabf2cf3d3fc699c3c0fe8ccd18df2219d978c0283e4c508a", size = 5199964, upload-time = "2024-07-15T00:16:03.669Z" }, + { url = "https://files.pythonhosted.org/packages/43/a4/d82decbab158a0e8a6ebb7fc98bc4d903266bce85b6e9aaedea1d288338c/zstandard-0.23.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:9c236e635582742fee16603042553d276cca506e824fa2e6489db04039521e90", size = 5655398, upload-time = "2024-07-15T00:16:06.694Z" }, + { url = "https://files.pythonhosted.org/packages/f2/61/ac78a1263bc83a5cf29e7458b77a568eda5a8f81980691bbc6eb6a0d45cc/zstandard-0.23.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a8fffdbd9d1408006baaf02f1068d7dd1f016c6bcb7538682622c556e7b68e35", size = 5191313, upload-time = "2024-07-15T00:16:09.758Z" }, + { url = "https://files.pythonhosted.org/packages/e7/54/967c478314e16af5baf849b6ee9d6ea724ae5b100eb506011f045d3d4e16/zstandard-0.23.0-cp312-cp312-win32.whl", hash = "sha256:dc1d33abb8a0d754ea4763bad944fd965d3d95b5baef6b121c0c9013eaf1907d", size = 430877, upload-time = "2024-07-15T00:16:11.758Z" }, + { url = "https://files.pythonhosted.org/packages/75/37/872d74bd7739639c4553bf94c84af7d54d8211b626b352bc57f0fd8d1e3f/zstandard-0.23.0-cp312-cp312-win_amd64.whl", hash = "sha256:64585e1dba664dc67c7cdabd56c1e5685233fbb1fc1966cfba2a340ec0dfff7b", size = 495595, upload-time = "2024-07-15T00:16:13.731Z" }, ] [package.optional-dependencies] diff --git a/dev/mypy-check b/dev/mypy-check index 23e3776b1a..b1c2c969a8 100755 --- a/dev/mypy-check +++ b/dev/mypy-check @@ -2,6 +2,9 @@ set -x +SCRIPT_DIR="$(dirname "$(realpath "$0")")" +cd "$SCRIPT_DIR/.." + # run mypy checks -uv run --directory api --dev \ - python -m mypy --install-types --non-interactive . +uv run --directory api --dev --with pip \ + python -m mypy --install-types --non-interactive ./ diff --git a/dev/pytest/pytest_all_tests.sh b/dev/pytest/pytest_all_tests.sh index c4318fb922..30898b4fcf 100755 --- a/dev/pytest/pytest_all_tests.sh +++ b/dev/pytest/pytest_all_tests.sh @@ -1,6 +1,9 @@ #!/bin/bash set -x +SCRIPT_DIR="$(dirname "$(realpath "$0")")" +cd "$SCRIPT_DIR/../.." + # ModelRuntime dev/pytest/pytest_model_runtime.sh @@ -11,4 +14,4 @@ dev/pytest/pytest_tools.sh dev/pytest/pytest_workflow.sh # Unit tests -dev/pytest/pytest_unit_tests.sh \ No newline at end of file +dev/pytest/pytest_unit_tests.sh diff --git a/dev/pytest/pytest_artifacts.sh b/dev/pytest/pytest_artifacts.sh index d52acb2273..3086ef5cc4 100755 --- a/dev/pytest/pytest_artifacts.sh +++ b/dev/pytest/pytest_artifacts.sh @@ -1,4 +1,7 @@ #!/bin/bash set -x +SCRIPT_DIR="$(dirname "$(realpath "$0")")" +cd "$SCRIPT_DIR/../.." + pytest api/tests/artifact_tests/ diff --git a/dev/pytest/pytest_model_runtime.sh b/dev/pytest/pytest_model_runtime.sh index 63891eb9f8..2cbbbbfd81 100755 --- a/dev/pytest/pytest_model_runtime.sh +++ b/dev/pytest/pytest_model_runtime.sh @@ -1,6 +1,9 @@ #!/bin/bash set -x +SCRIPT_DIR="$(dirname "$(realpath "$0")")" +cd "$SCRIPT_DIR/../.." + pytest api/tests/integration_tests/model_runtime/anthropic \ api/tests/integration_tests/model_runtime/azure_openai \ api/tests/integration_tests/model_runtime/openai api/tests/integration_tests/model_runtime/chatglm \ @@ -10,4 +13,4 @@ pytest api/tests/integration_tests/model_runtime/anthropic \ api/tests/integration_tests/model_runtime/fireworks \ api/tests/integration_tests/model_runtime/nomic \ api/tests/integration_tests/model_runtime/mixedbread \ - api/tests/integration_tests/model_runtime/voyage \ No newline at end of file + api/tests/integration_tests/model_runtime/voyage diff --git a/dev/pytest/pytest_tools.sh b/dev/pytest/pytest_tools.sh index 5b1de8b6dd..d10934626f 100755 --- a/dev/pytest/pytest_tools.sh +++ b/dev/pytest/pytest_tools.sh @@ -1,4 +1,7 @@ #!/bin/bash set -x -pytest api/tests/integration_tests/tools/test_all_provider.py +SCRIPT_DIR="$(dirname "$(realpath "$0")")" +cd "$SCRIPT_DIR/../.." + +pytest api/tests/integration_tests/tools diff --git a/dev/pytest/pytest_unit_tests.sh b/dev/pytest/pytest_unit_tests.sh index 2075596b7f..1a1819ca28 100755 --- a/dev/pytest/pytest_unit_tests.sh +++ b/dev/pytest/pytest_unit_tests.sh @@ -1,5 +1,8 @@ #!/bin/bash set -x +SCRIPT_DIR="$(dirname "$(realpath "$0")")" +cd "$SCRIPT_DIR/../.." + # libs pytest api/tests/unit_tests diff --git a/dev/pytest/pytest_vdb.sh b/dev/pytest/pytest_vdb.sh index dd03ca3514..7f617a9c05 100755 --- a/dev/pytest/pytest_vdb.sh +++ b/dev/pytest/pytest_vdb.sh @@ -1,6 +1,9 @@ #!/bin/bash set -x +SCRIPT_DIR="$(dirname "$(realpath "$0")")" +cd "$SCRIPT_DIR/../.." + pytest api/tests/integration_tests/vdb/chroma \ api/tests/integration_tests/vdb/milvus \ api/tests/integration_tests/vdb/pgvecto_rs \ diff --git a/dev/pytest/pytest_workflow.sh b/dev/pytest/pytest_workflow.sh index db8fdb2fb9..b63d49069f 100755 --- a/dev/pytest/pytest_workflow.sh +++ b/dev/pytest/pytest_workflow.sh @@ -1,4 +1,7 @@ #!/bin/bash set -x +SCRIPT_DIR="$(dirname "$(realpath "$0")")" +cd "$SCRIPT_DIR/../.." + pytest api/tests/integration_tests/workflow diff --git a/dev/reformat b/dev/reformat index 53d7703fce..71cb6abb1e 100755 --- a/dev/reformat +++ b/dev/reformat @@ -2,6 +2,9 @@ set -x +SCRIPT_DIR="$(dirname "$(realpath "$0")")" +cd "$SCRIPT_DIR/.." + # run ruff linter uv run --directory api --dev ruff check --fix ./ diff --git a/dev/start-api b/dev/start-api new file mode 100755 index 0000000000..0b50ad0d0a --- /dev/null +++ b/dev/start-api @@ -0,0 +1,10 @@ +#!/bin/bash + +set -x + +SCRIPT_DIR="$(dirname "$(realpath "$0")")" +cd "$SCRIPT_DIR/.." + + +uv --directory api run \ + flask run --host 0.0.0.0 --port=5001 --debug diff --git a/dev/start-worker b/dev/start-worker new file mode 100755 index 0000000000..7007b265e0 --- /dev/null +++ b/dev/start-worker @@ -0,0 +1,11 @@ +#!/bin/bash + +set -x + +SCRIPT_DIR="$(dirname "$(realpath "$0")")" +cd "$SCRIPT_DIR/.." + + +uv --directory api run \ + celery -A app.celery worker \ + -P gevent -c 1 --loglevel INFO -Q dataset,generation,mail,ops_trace,app_deletion diff --git a/dev/sync-uv b/dev/sync-uv index 7bc3bb22be..67a8133f5a 100755 --- a/dev/sync-uv +++ b/dev/sync-uv @@ -6,5 +6,8 @@ if ! command -v uv &> /dev/null; then pip install uv fi +SCRIPT_DIR="$(dirname "$(realpath "$0")")" +cd "$SCRIPT_DIR/.." + # check uv.lock in sync with pyproject.toml uv lock --project api diff --git a/docker/.env.example b/docker/.env.example index 0b80dccb37..d4d59936eb 100644 --- a/docker/.env.example +++ b/docker/.env.example @@ -39,6 +39,12 @@ APP_WEB_URL= # File preview or download Url prefix. # used to display File preview or download Url to the front-end or as Multi-model inputs; # Url is signed and has expiration time. +# Setting FILES_URL is required for file processing plugins. +# - For https://example.com, use FILES_URL=https://example.com +# - For http://example.com, use FILES_URL=http://example.com +# Recommendation: use a dedicated domain (e.g., https://upload.example.com). +# Alternatively, use http://:5001 or http://api:5001, +# ensuring port 5001 is externally accessible (see docker-compose.yaml). FILES_URL= # ------------------------------ @@ -68,6 +74,10 @@ DEBUG=false # which is convenient for debugging. FLASK_DEBUG=false +# Enable request logging, which will log the request and response information. +# And the log level is DEBUG +ENABLE_REQUEST_LOGGING=False + # A secret key that is used for securely signing the session cookie # and encrypting sensitive information on the database. # You can generate a strong key using `openssl rand -base64 42`. @@ -389,7 +399,7 @@ SUPABASE_URL=your-server-url # ------------------------------ # The type of vector store to use. -# Supported values are `weaviate`, `qdrant`, `milvus`, `myscale`, `relyt`, `pgvector`, `pgvecto-rs`, `chroma`, `opensearch`, `tidb_vector`, `oracle`, `tencent`, `elasticsearch`, `elasticsearch-ja`, `analyticdb`, `couchbase`, `vikingdb`, `oceanbase`, `opengauss`, `tablestore`. +# Supported values are `weaviate`, `qdrant`, `milvus`, `myscale`, `relyt`, `pgvector`, `pgvecto-rs`, `chroma`, `opensearch`, `oracle`, `tencent`, `elasticsearch`, `elasticsearch-ja`, `analyticdb`, `couchbase`, `vikingdb`, `oceanbase`, `opengauss`, `tablestore`,`vastbase`,`tidb`,`tidb_on_qdrant`,`baidu`,`lindorm`,`huawei_cloud`,`upstash`. VECTOR_STORE=weaviate # The Weaviate endpoint URL. Only available when VECTOR_STORE is `weaviate`. @@ -402,10 +412,12 @@ QDRANT_API_KEY=difyai123456 QDRANT_CLIENT_TIMEOUT=20 QDRANT_GRPC_ENABLED=false QDRANT_GRPC_PORT=6334 +QDRANT_REPLICATION_FACTOR=1 # Milvus configuration. Only available when VECTOR_STORE is `milvus`. # The milvus uri. MILVUS_URI=http://host.docker.internal:19530 +MILVUS_DATABASE= MILVUS_TOKEN= MILVUS_USER= MILVUS_PASSWORD= @@ -441,6 +453,15 @@ PGVECTOR_MAX_CONNECTION=5 PGVECTOR_PG_BIGM=false PGVECTOR_PG_BIGM_VERSION=1.2-20240606 +# vastbase configurations, only available when VECTOR_STORE is `vastbase` +VASTBASE_HOST=vastbase +VASTBASE_PORT=5432 +VASTBASE_USER=dify +VASTBASE_PASSWORD=Difyai123456 +VASTBASE_DATABASE=dify +VASTBASE_MIN_CONNECTION=1 +VASTBASE_MAX_CONNECTION=5 + # pgvecto-rs configurations, only available when VECTOR_STORE is `pgvecto-rs` PGVECTO_RS_HOST=pgvecto-rs PGVECTO_RS_PORT=5432 @@ -462,7 +483,7 @@ ANALYTICDB_PORT=5432 ANALYTICDB_MIN_CONNECTION=1 ANALYTICDB_MAX_CONNECTION=5 -# TiDB vector configurations, only available when VECTOR_STORE is `tidb` +# TiDB vector configurations, only available when VECTOR_STORE is `tidb_vector` TIDB_VECTOR_HOST=tidb TIDB_VECTOR_PORT=4000 TIDB_VECTOR_USER= @@ -510,9 +531,14 @@ RELYT_DATABASE=postgres # open search configuration, only available when VECTOR_STORE is `opensearch` OPENSEARCH_HOST=opensearch OPENSEARCH_PORT=9200 +OPENSEARCH_SECURE=true +OPENSEARCH_VERIFY_CERTS=true +OPENSEARCH_AUTH_METHOD=basic OPENSEARCH_USER=admin OPENSEARCH_PASSWORD=admin -OPENSEARCH_SECURE=true +# If using AWS managed IAM, e.g. Managed Cluster or OpenSearch Serverless +OPENSEARCH_AWS_REGION=ap-southeast-1 +OPENSEARCH_AWS_SERVICE=aoss # tencent vector configurations, only available when VECTOR_STORE is `tencent` TENCENT_VECTOR_DB_URL=http://127.0.0.1 @@ -553,6 +579,7 @@ VIKINGDB_SOCKET_TIMEOUT=30 LINDORM_URL=http://lindorm:30070 LINDORM_USERNAME=lindorm LINDORM_PASSWORD=lindorm +LINDORM_QUERY_TIMEOUT=1 # OceanBase Vector configuration, only available when VECTOR_STORE is `oceanbase` OCEANBASE_VECTOR_HOST=oceanbase @@ -775,7 +802,7 @@ MAX_TOOLS_NUM=10 MAX_PARALLEL_LIMIT=10 # The maximum number of iterations for agent setting -MAX_ITERATIONS_NUM=5 +MAX_ITERATIONS_NUM=99 # ------------------------------ # Environment Variables for web Service @@ -844,7 +871,7 @@ CHROMA_IS_PERSISTENT=TRUE # ------------------------------ # Environment Variables for Oracle Service -# (only used when VECTOR_STORE is Oracle) +# (only used when VECTOR_STORE is oracle) # ------------------------------ ORACLE_PWD=Dify123456 ORACLE_CHARACTERSET=AL32UTF8 @@ -1030,7 +1057,7 @@ PLUGIN_MAX_EXECUTION_TIMEOUT=600 PIP_MIRROR_URL= # https://github.com/langgenius/dify-plugin-daemon/blob/main/.env.example -# Plugin storage type, local aws_s3 tencent_cos azure_blob +# Plugin storage type, local aws_s3 tencent_cos azure_blob aliyun_oss volcengine_tos PLUGIN_STORAGE_TYPE=local PLUGIN_STORAGE_LOCAL_ROOT=/app/storage PLUGIN_WORKING_PATH=/app/storage/cwd @@ -1040,6 +1067,7 @@ PLUGIN_MEDIA_CACHE_PATH=assets # Plugin oss bucket PLUGIN_STORAGE_OSS_BUCKET= # Plugin oss s3 credentials +PLUGIN_S3_USE_AWS= PLUGIN_S3_USE_AWS_MANAGED_IAM=false PLUGIN_S3_ENDPOINT= PLUGIN_S3_USE_PATH_STYLE=false @@ -1053,6 +1081,18 @@ PLUGIN_AZURE_BLOB_STORAGE_CONNECTION_STRING= PLUGIN_TENCENT_COS_SECRET_KEY= PLUGIN_TENCENT_COS_SECRET_ID= PLUGIN_TENCENT_COS_REGION= +# Plugin oss aliyun oss +PLUGIN_ALIYUN_OSS_REGION= +PLUGIN_ALIYUN_OSS_ENDPOINT= +PLUGIN_ALIYUN_OSS_ACCESS_KEY_ID= +PLUGIN_ALIYUN_OSS_ACCESS_KEY_SECRET= +PLUGIN_ALIYUN_OSS_AUTH_VERSION=v4 +PLUGIN_ALIYUN_OSS_PATH= +# Plugin oss volcengine tos +PLUGIN_VOLCENGINE_TOS_ENDPOINT= +PLUGIN_VOLCENGINE_TOS_ACCESS_KEY= +PLUGIN_VOLCENGINE_TOS_SECRET_KEY= +PLUGIN_VOLCENGINE_TOS_REGION= # ------------------------------ # OTLP Collector Configuration @@ -1060,6 +1100,7 @@ PLUGIN_TENCENT_COS_REGION= ENABLE_OTEL=false OTLP_BASE_ENDPOINT=http://localhost:4318 OTLP_API_KEY= +OTEL_EXPORTER_OTLP_PROTOCOL= OTEL_EXPORTER_TYPE=otlp OTEL_SAMPLING_RATE=0.1 OTEL_BATCH_EXPORT_SCHEDULE_DELAY=5000 @@ -1071,3 +1112,10 @@ OTEL_METRIC_EXPORT_TIMEOUT=30000 # Prevent Clickjacking ALLOW_EMBED=false + +# Dataset queue monitor configuration +QUEUE_MONITOR_THRESHOLD=200 +# You can configure multiple ones, separated by commas. eg: test1@dify.ai,test2@dify.ai +QUEUE_MONITOR_ALERT_EMAILS= +# Monitor interval in minutes, default is 30 minutes +QUEUE_MONITOR_INTERVAL=30 diff --git a/docker/README.md b/docker/README.md index 38b11a677f..22dfe2c91c 100644 --- a/docker/README.md +++ b/docker/README.md @@ -14,7 +14,6 @@ Welcome to the new `docker` directory for deploying Dify using Docker Compose. T - **Unified Vector Database Services**: All vector database services are now managed from a single Docker Compose file `docker-compose.yaml`. You can switch between different vector databases by setting the `VECTOR_STORE` environment variable in your `.env` file. - **Mandatory .env File**: A `.env` file is now required to run `docker compose up`. This file is crucial for configuring your deployment and for any custom settings to persist through upgrades. -- **Legacy Support**: Previous deployment files are now located in the `docker-legacy` directory and will no longer be maintained. ### How to Deploy Dify with `docker-compose.yaml` diff --git a/docker/couchbase-server/Dockerfile b/docker/couchbase-server/Dockerfile index bd8af64150..23e487e4ed 100644 --- a/docker/couchbase-server/Dockerfile +++ b/docker/couchbase-server/Dockerfile @@ -1,4 +1,4 @@ FROM couchbase/server:latest AS stage_base -# FROM couchbase:latest AS stage_base +# FROM couchbase:latest AS stage_base COPY init-cbserver.sh /opt/couchbase/init/ -RUN chmod +x /opt/couchbase/init/init-cbserver.sh \ No newline at end of file +RUN chmod +x /opt/couchbase/init/init-cbserver.sh diff --git a/docker/couchbase-server/init-cbserver.sh b/docker/couchbase-server/init-cbserver.sh index e66bc18530..e19a650f23 100755 --- a/docker/couchbase-server/init-cbserver.sh +++ b/docker/couchbase-server/init-cbserver.sh @@ -1,8 +1,8 @@ #!/bin/bash -# used to start couchbase server - can't get around this as docker compose only allows you to start one command - so we have to start couchbase like the standard couchbase Dockerfile would +# used to start couchbase server - can't get around this as docker compose only allows you to start one command - so we have to start couchbase like the standard couchbase Dockerfile would # https://github.com/couchbase/docker/blob/master/enterprise/couchbase-server/7.2.0/Dockerfile#L88 -/entrypoint.sh couchbase-server & +/entrypoint.sh couchbase-server & # track if setup is complete so we don't try to setup again FILE=/opt/couchbase/init/setupComplete.txt @@ -36,9 +36,9 @@ if ! [ -f "$FILE" ]; then --bucket-ramsize $COUCHBASE_BUCKET_RAMSIZE \ --bucket-type couchbase - # create file so we know that the cluster is setup and don't run the setup again + # create file so we know that the cluster is setup and don't run the setup again touch $FILE -fi +fi # docker compose will stop the container from running unless we do this # known issue and workaround tail -f /dev/null diff --git a/docker/docker-compose-template.yaml b/docker/docker-compose-template.yaml index 8c57a7c4c2..55e1b55599 100644 --- a/docker/docker-compose-template.yaml +++ b/docker/docker-compose-template.yaml @@ -2,7 +2,7 @@ x-shared-env: &shared-api-worker-env services: # API service api: - image: langgenius/dify-api:1.3.0 + image: langgenius/dify-api:1.4.3 restart: always environment: # Use the shared environment variables. @@ -31,7 +31,7 @@ services: # worker service # The Celery worker for processing the queue. worker: - image: langgenius/dify-api:1.3.0 + image: langgenius/dify-api:1.4.3 restart: always environment: # Use the shared environment variables. @@ -57,7 +57,7 @@ services: # Frontend web application. web: - image: langgenius/dify-web:1.3.0 + image: langgenius/dify-web:1.4.3 restart: always environment: CONSOLE_API_URL: ${CONSOLE_API_URL:-} @@ -75,7 +75,7 @@ services: LOOP_NODE_MAX_COUNT: ${LOOP_NODE_MAX_COUNT:-100} MAX_TOOLS_NUM: ${MAX_TOOLS_NUM:-10} MAX_PARALLEL_LIMIT: ${MAX_PARALLEL_LIMIT:-10} - MAX_ITERATIONS_NUM: ${MAX_ITERATIONS_NUM:-5} + MAX_ITERATIONS_NUM: ${MAX_ITERATIONS_NUM:-99} ENABLE_WEBSITE_JINAREADER: ${ENABLE_WEBSITE_JINAREADER:-true} ENABLE_WEBSITE_FIRECRAWL: ${ENABLE_WEBSITE_FIRECRAWL:-true} ENABLE_WEBSITE_WATERCRAWL: ${ENABLE_WEBSITE_WATERCRAWL:-true} @@ -118,7 +118,7 @@ services: # The DifySandbox sandbox: - image: langgenius/dify-sandbox:0.2.11 + image: langgenius/dify-sandbox:0.2.12 restart: always environment: # The DifySandbox configurations @@ -142,7 +142,7 @@ services: # plugin daemon plugin_daemon: - image: langgenius/dify-plugin-daemon:0.0.8-local + image: langgenius/dify-plugin-daemon:0.1.2-local restart: always environment: # Use the shared environment variables. @@ -168,6 +168,7 @@ services: PLUGIN_MEDIA_CACHE_PATH: ${PLUGIN_MEDIA_CACHE_PATH:-assets} PLUGIN_STORAGE_OSS_BUCKET: ${PLUGIN_STORAGE_OSS_BUCKET:-} S3_USE_AWS_MANAGED_IAM: ${PLUGIN_S3_USE_AWS_MANAGED_IAM:-false} + S3_USE_AWS: ${PLUGIN_S3_USE_AWS:-} S3_ENDPOINT: ${PLUGIN_S3_ENDPOINT:-} S3_USE_PATH_STYLE: ${PLUGIN_S3_USE_PATH_STYLE:-false} AWS_ACCESS_KEY: ${PLUGIN_AWS_ACCESS_KEY:-} @@ -178,6 +179,16 @@ services: TENCENT_COS_SECRET_KEY: ${PLUGIN_TENCENT_COS_SECRET_KEY:-} TENCENT_COS_SECRET_ID: ${PLUGIN_TENCENT_COS_SECRET_ID:-} TENCENT_COS_REGION: ${PLUGIN_TENCENT_COS_REGION:-} + ALIYUN_OSS_REGION: ${PLUGIN_ALIYUN_OSS_REGION:-} + ALIYUN_OSS_ENDPOINT: ${PLUGIN_ALIYUN_OSS_ENDPOINT:-} + ALIYUN_OSS_ACCESS_KEY_ID: ${PLUGIN_ALIYUN_OSS_ACCESS_KEY_ID:-} + ALIYUN_OSS_ACCESS_KEY_SECRET: ${PLUGIN_ALIYUN_OSS_ACCESS_KEY_SECRET:-} + ALIYUN_OSS_AUTH_VERSION: ${PLUGIN_ALIYUN_OSS_AUTH_VERSION:-v4} + ALIYUN_OSS_PATH: ${PLUGIN_ALIYUN_OSS_PATH:-} + VOLCENGINE_TOS_ENDPOINT: ${PLUGIN_VOLCENGINE_TOS_ENDPOINT:-} + VOLCENGINE_TOS_ACCESS_KEY: ${PLUGIN_VOLCENGINE_TOS_ACCESS_KEY:-} + VOLCENGINE_TOS_SECRET_KEY: ${PLUGIN_VOLCENGINE_TOS_SECRET_KEY:-} + VOLCENGINE_TOS_REGION: ${PLUGIN_VOLCENGINE_TOS_REGION:-} ports: - "${EXPOSE_PLUGIN_DEBUGGING_PORT:-5003}:${PLUGIN_DEBUGGING_PORT:-5003}" volumes: @@ -188,7 +199,7 @@ services: # ssrf_proxy server # for more information, please refer to - # https://docs.dify.ai/learn-more/faq/install-faq#id-18.-why-is-ssrf_proxy-needed + # https://docs.dify.ai/learn-more/faq/install-faq#18-why-is-ssrf-proxy-needed%3F ssrf_proxy: image: ubuntu/squid:latest restart: always @@ -363,6 +374,30 @@ services: timeout: 3s retries: 30 + # get image from https://www.vastdata.com.cn/ + vastbase: + image: vastdata/vastbase-vector + profiles: + - vastbase + restart: always + environment: + - VB_DBCOMPATIBILITY=PG + - VB_DB=dify + - VB_USERNAME=dify + - VB_PASSWORD=Difyai123456 + ports: + - '5434:5432' + volumes: + - ./vastbase/lic:/home/vastbase/vastbase/lic + - ./vastbase/data:/home/vastbase/data + - ./vastbase/backup:/home/vastbase/backup + - ./vastbase/backup_log:/home/vastbase/backup_log + healthcheck: + test: [ 'CMD', 'pg_isready' ] + interval: 1s + timeout: 3s + retries: 30 + # pgvecto-rs vector store pgvecto-rs: image: tensorchord/pgvecto-rs:pg16-v0.3.0 @@ -400,7 +435,7 @@ services: # OceanBase vector database oceanbase: - image: oceanbase/oceanbase-ce:4.3.5.1-101000042025031818 + image: oceanbase/oceanbase-ce:4.3.5-lts container_name: oceanbase profiles: - oceanbase @@ -414,9 +449,8 @@ services: OB_SYS_PASSWORD: ${OCEANBASE_VECTOR_PASSWORD:-difyai123456} OB_TENANT_PASSWORD: ${OCEANBASE_VECTOR_PASSWORD:-difyai123456} OB_CLUSTER_NAME: ${OCEANBASE_CLUSTER_NAME:-difyai} - MODE: MINI - ports: - - "${OCEANBASE_VECTOR_PORT:-2881}:2881" + OB_SERVER_IP: 127.0.0.1 + MODE: mini # Oracle vector database oracle: diff --git a/docker/docker-compose.middleware.yaml b/docker/docker-compose.middleware.yaml index fc08edd264..4081bfd818 100644 --- a/docker/docker-compose.middleware.yaml +++ b/docker/docker-compose.middleware.yaml @@ -45,7 +45,7 @@ services: # The DifySandbox sandbox: - image: langgenius/dify-sandbox:0.2.11 + image: langgenius/dify-sandbox:0.2.12 restart: always env_file: - ./middleware.env @@ -71,7 +71,7 @@ services: # plugin daemon plugin_daemon: - image: langgenius/dify-plugin-daemon:0.0.8-local + image: langgenius/dify-plugin-daemon:0.1.2-local restart: always env_file: - ./middleware.env @@ -104,6 +104,7 @@ services: PLUGIN_PACKAGE_CACHE_PATH: ${PLUGIN_PACKAGE_CACHE_PATH:-plugin_packages} PLUGIN_MEDIA_CACHE_PATH: ${PLUGIN_MEDIA_CACHE_PATH:-assets} PLUGIN_STORAGE_OSS_BUCKET: ${PLUGIN_STORAGE_OSS_BUCKET:-} + S3_USE_AWS: ${PLUGIN_S3_USE_AWS:-} S3_USE_AWS_MANAGED_IAM: ${PLUGIN_S3_USE_AWS_MANAGED_IAM:-false} S3_ENDPOINT: ${PLUGIN_S3_ENDPOINT:-} S3_USE_PATH_STYLE: ${PLUGIN_S3_USE_PATH_STYLE:-false} @@ -115,6 +116,16 @@ services: TENCENT_COS_SECRET_KEY: ${PLUGIN_TENCENT_COS_SECRET_KEY:-} TENCENT_COS_SECRET_ID: ${PLUGIN_TENCENT_COS_SECRET_ID:-} TENCENT_COS_REGION: ${PLUGIN_TENCENT_COS_REGION:-} + ALIYUN_OSS_REGION: ${PLUGIN_ALIYUN_OSS_REGION:-} + ALIYUN_OSS_ENDPOINT: ${PLUGIN_ALIYUN_OSS_ENDPOINT:-} + ALIYUN_OSS_ACCESS_KEY_ID: ${PLUGIN_ALIYUN_OSS_ACCESS_KEY_ID:-} + ALIYUN_OSS_ACCESS_KEY_SECRET: ${PLUGIN_ALIYUN_OSS_ACCESS_KEY_SECRET:-} + ALIYUN_OSS_AUTH_VERSION: ${PLUGIN_ALIYUN_OSS_AUTH_VERSION:-v4} + ALIYUN_OSS_PATH: ${PLUGIN_ALIYUN_OSS_PATH:-} + VOLCENGINE_TOS_ENDPOINT: ${PLUGIN_VOLCENGINE_TOS_ENDPOINT:-} + VOLCENGINE_TOS_ACCESS_KEY: ${PLUGIN_VOLCENGINE_TOS_ACCESS_KEY:-} + VOLCENGINE_TOS_SECRET_KEY: ${PLUGIN_VOLCENGINE_TOS_SECRET_KEY:-} + VOLCENGINE_TOS_REGION: ${PLUGIN_VOLCENGINE_TOS_REGION:-} ports: - "${EXPOSE_PLUGIN_DAEMON_PORT:-5002}:${PLUGIN_DAEMON_PORT:-5002}" - "${EXPOSE_PLUGIN_DEBUGGING_PORT:-5003}:${PLUGIN_DEBUGGING_PORT:-5003}" @@ -123,7 +134,7 @@ services: # ssrf_proxy server # for more information, please refer to - # https://docs.dify.ai/learn-more/faq/install-faq#id-18.-why-is-ssrf_proxy-needed + # https://docs.dify.ai/learn-more/faq/install-faq#18-why-is-ssrf-proxy-needed%3F ssrf_proxy: image: ubuntu/squid:latest restart: always diff --git a/docker/docker-compose.png b/docker/docker-compose.png index bdac113086..015d450236 100644 Binary files a/docker/docker-compose.png and b/docker/docker-compose.png differ diff --git a/docker/docker-compose.yaml b/docker/docker-compose.yaml index 3d3e3a901f..dddce106b9 100644 --- a/docker/docker-compose.yaml +++ b/docker/docker-compose.yaml @@ -19,6 +19,7 @@ x-shared-env: &shared-api-worker-env LOG_TZ: ${LOG_TZ:-UTC} DEBUG: ${DEBUG:-false} FLASK_DEBUG: ${FLASK_DEBUG:-false} + ENABLE_REQUEST_LOGGING: ${ENABLE_REQUEST_LOGGING:-False} SECRET_KEY: ${SECRET_KEY:-sk-9f73s3ljTXVcMT3Blb3ljTqtsKiGHXVcMT3BlbkFJLK7U} INIT_PASSWORD: ${INIT_PASSWORD:-} DEPLOY_ENV: ${DEPLOY_ENV:-PRODUCTION} @@ -137,7 +138,9 @@ x-shared-env: &shared-api-worker-env QDRANT_CLIENT_TIMEOUT: ${QDRANT_CLIENT_TIMEOUT:-20} QDRANT_GRPC_ENABLED: ${QDRANT_GRPC_ENABLED:-false} QDRANT_GRPC_PORT: ${QDRANT_GRPC_PORT:-6334} + QDRANT_REPLICATION_FACTOR: ${QDRANT_REPLICATION_FACTOR:-1} MILVUS_URI: ${MILVUS_URI:-http://host.docker.internal:19530} + MILVUS_DATABASE: ${MILVUS_DATABASE:-} MILVUS_TOKEN: ${MILVUS_TOKEN:-} MILVUS_USER: ${MILVUS_USER:-} MILVUS_PASSWORD: ${MILVUS_PASSWORD:-} @@ -163,6 +166,13 @@ x-shared-env: &shared-api-worker-env PGVECTOR_MAX_CONNECTION: ${PGVECTOR_MAX_CONNECTION:-5} PGVECTOR_PG_BIGM: ${PGVECTOR_PG_BIGM:-false} PGVECTOR_PG_BIGM_VERSION: ${PGVECTOR_PG_BIGM_VERSION:-1.2-20240606} + VASTBASE_HOST: ${VASTBASE_HOST:-vastbase} + VASTBASE_PORT: ${VASTBASE_PORT:-5432} + VASTBASE_USER: ${VASTBASE_USER:-dify} + VASTBASE_PASSWORD: ${VASTBASE_PASSWORD:-Difyai123456} + VASTBASE_DATABASE: ${VASTBASE_DATABASE:-dify} + VASTBASE_MIN_CONNECTION: ${VASTBASE_MIN_CONNECTION:-1} + VASTBASE_MAX_CONNECTION: ${VASTBASE_MAX_CONNECTION:-5} PGVECTO_RS_HOST: ${PGVECTO_RS_HOST:-pgvecto-rs} PGVECTO_RS_PORT: ${PGVECTO_RS_PORT:-5432} PGVECTO_RS_USER: ${PGVECTO_RS_USER:-postgres} @@ -217,9 +227,13 @@ x-shared-env: &shared-api-worker-env RELYT_DATABASE: ${RELYT_DATABASE:-postgres} OPENSEARCH_HOST: ${OPENSEARCH_HOST:-opensearch} OPENSEARCH_PORT: ${OPENSEARCH_PORT:-9200} + OPENSEARCH_SECURE: ${OPENSEARCH_SECURE:-true} + OPENSEARCH_VERIFY_CERTS: ${OPENSEARCH_VERIFY_CERTS:-true} + OPENSEARCH_AUTH_METHOD: ${OPENSEARCH_AUTH_METHOD:-basic} OPENSEARCH_USER: ${OPENSEARCH_USER:-admin} OPENSEARCH_PASSWORD: ${OPENSEARCH_PASSWORD:-admin} - OPENSEARCH_SECURE: ${OPENSEARCH_SECURE:-true} + OPENSEARCH_AWS_REGION: ${OPENSEARCH_AWS_REGION:-ap-southeast-1} + OPENSEARCH_AWS_SERVICE: ${OPENSEARCH_AWS_SERVICE:-aoss} TENCENT_VECTOR_DB_URL: ${TENCENT_VECTOR_DB_URL:-http://127.0.0.1} TENCENT_VECTOR_DB_API_KEY: ${TENCENT_VECTOR_DB_API_KEY:-dify} TENCENT_VECTOR_DB_TIMEOUT: ${TENCENT_VECTOR_DB_TIMEOUT:-30} @@ -250,6 +264,7 @@ x-shared-env: &shared-api-worker-env LINDORM_URL: ${LINDORM_URL:-http://lindorm:30070} LINDORM_USERNAME: ${LINDORM_USERNAME:-lindorm} LINDORM_PASSWORD: ${LINDORM_PASSWORD:-lindorm} + LINDORM_QUERY_TIMEOUT: ${LINDORM_QUERY_TIMEOUT:-1} OCEANBASE_VECTOR_HOST: ${OCEANBASE_VECTOR_HOST:-oceanbase} OCEANBASE_VECTOR_PORT: ${OCEANBASE_VECTOR_PORT:-2881} OCEANBASE_VECTOR_USER: ${OCEANBASE_VECTOR_USER:-root@test} @@ -339,7 +354,7 @@ x-shared-env: &shared-api-worker-env LOOP_NODE_MAX_COUNT: ${LOOP_NODE_MAX_COUNT:-100} MAX_TOOLS_NUM: ${MAX_TOOLS_NUM:-10} MAX_PARALLEL_LIMIT: ${MAX_PARALLEL_LIMIT:-10} - MAX_ITERATIONS_NUM: ${MAX_ITERATIONS_NUM:-5} + MAX_ITERATIONS_NUM: ${MAX_ITERATIONS_NUM:-99} TEXT_GENERATION_TIMEOUT_MS: ${TEXT_GENERATION_TIMEOUT_MS:-60000} PGUSER: ${PGUSER:-${DB_USERNAME}} POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-${DB_PASSWORD}} @@ -452,6 +467,7 @@ x-shared-env: &shared-api-worker-env PLUGIN_PACKAGE_CACHE_PATH: ${PLUGIN_PACKAGE_CACHE_PATH:-plugin_packages} PLUGIN_MEDIA_CACHE_PATH: ${PLUGIN_MEDIA_CACHE_PATH:-assets} PLUGIN_STORAGE_OSS_BUCKET: ${PLUGIN_STORAGE_OSS_BUCKET:-} + PLUGIN_S3_USE_AWS: ${PLUGIN_S3_USE_AWS:-} PLUGIN_S3_USE_AWS_MANAGED_IAM: ${PLUGIN_S3_USE_AWS_MANAGED_IAM:-false} PLUGIN_S3_ENDPOINT: ${PLUGIN_S3_ENDPOINT:-} PLUGIN_S3_USE_PATH_STYLE: ${PLUGIN_S3_USE_PATH_STYLE:-false} @@ -463,9 +479,20 @@ x-shared-env: &shared-api-worker-env PLUGIN_TENCENT_COS_SECRET_KEY: ${PLUGIN_TENCENT_COS_SECRET_KEY:-} PLUGIN_TENCENT_COS_SECRET_ID: ${PLUGIN_TENCENT_COS_SECRET_ID:-} PLUGIN_TENCENT_COS_REGION: ${PLUGIN_TENCENT_COS_REGION:-} + PLUGIN_ALIYUN_OSS_REGION: ${PLUGIN_ALIYUN_OSS_REGION:-} + PLUGIN_ALIYUN_OSS_ENDPOINT: ${PLUGIN_ALIYUN_OSS_ENDPOINT:-} + PLUGIN_ALIYUN_OSS_ACCESS_KEY_ID: ${PLUGIN_ALIYUN_OSS_ACCESS_KEY_ID:-} + PLUGIN_ALIYUN_OSS_ACCESS_KEY_SECRET: ${PLUGIN_ALIYUN_OSS_ACCESS_KEY_SECRET:-} + PLUGIN_ALIYUN_OSS_AUTH_VERSION: ${PLUGIN_ALIYUN_OSS_AUTH_VERSION:-v4} + PLUGIN_ALIYUN_OSS_PATH: ${PLUGIN_ALIYUN_OSS_PATH:-} + PLUGIN_VOLCENGINE_TOS_ENDPOINT: ${PLUGIN_VOLCENGINE_TOS_ENDPOINT:-} + PLUGIN_VOLCENGINE_TOS_ACCESS_KEY: ${PLUGIN_VOLCENGINE_TOS_ACCESS_KEY:-} + PLUGIN_VOLCENGINE_TOS_SECRET_KEY: ${PLUGIN_VOLCENGINE_TOS_SECRET_KEY:-} + PLUGIN_VOLCENGINE_TOS_REGION: ${PLUGIN_VOLCENGINE_TOS_REGION:-} ENABLE_OTEL: ${ENABLE_OTEL:-false} OTLP_BASE_ENDPOINT: ${OTLP_BASE_ENDPOINT:-http://localhost:4318} OTLP_API_KEY: ${OTLP_API_KEY:-} + OTEL_EXPORTER_OTLP_PROTOCOL: ${OTEL_EXPORTER_OTLP_PROTOCOL:-} OTEL_EXPORTER_TYPE: ${OTEL_EXPORTER_TYPE:-otlp} OTEL_SAMPLING_RATE: ${OTEL_SAMPLING_RATE:-0.1} OTEL_BATCH_EXPORT_SCHEDULE_DELAY: ${OTEL_BATCH_EXPORT_SCHEDULE_DELAY:-5000} @@ -475,11 +502,14 @@ x-shared-env: &shared-api-worker-env OTEL_BATCH_EXPORT_TIMEOUT: ${OTEL_BATCH_EXPORT_TIMEOUT:-10000} OTEL_METRIC_EXPORT_TIMEOUT: ${OTEL_METRIC_EXPORT_TIMEOUT:-30000} ALLOW_EMBED: ${ALLOW_EMBED:-false} + QUEUE_MONITOR_THRESHOLD: ${QUEUE_MONITOR_THRESHOLD:-200} + QUEUE_MONITOR_ALERT_EMAILS: ${QUEUE_MONITOR_ALERT_EMAILS:-} + QUEUE_MONITOR_INTERVAL: ${QUEUE_MONITOR_INTERVAL:-30} services: # API service api: - image: langgenius/dify-api:1.3.0 + image: langgenius/dify-api:1.4.3 restart: always environment: # Use the shared environment variables. @@ -508,7 +538,7 @@ services: # worker service # The Celery worker for processing the queue. worker: - image: langgenius/dify-api:1.3.0 + image: langgenius/dify-api:1.4.3 restart: always environment: # Use the shared environment variables. @@ -534,7 +564,7 @@ services: # Frontend web application. web: - image: langgenius/dify-web:1.3.0 + image: langgenius/dify-web:1.4.3 restart: always environment: CONSOLE_API_URL: ${CONSOLE_API_URL:-} @@ -552,7 +582,7 @@ services: LOOP_NODE_MAX_COUNT: ${LOOP_NODE_MAX_COUNT:-100} MAX_TOOLS_NUM: ${MAX_TOOLS_NUM:-10} MAX_PARALLEL_LIMIT: ${MAX_PARALLEL_LIMIT:-10} - MAX_ITERATIONS_NUM: ${MAX_ITERATIONS_NUM:-5} + MAX_ITERATIONS_NUM: ${MAX_ITERATIONS_NUM:-99} ENABLE_WEBSITE_JINAREADER: ${ENABLE_WEBSITE_JINAREADER:-true} ENABLE_WEBSITE_FIRECRAWL: ${ENABLE_WEBSITE_FIRECRAWL:-true} ENABLE_WEBSITE_WATERCRAWL: ${ENABLE_WEBSITE_WATERCRAWL:-true} @@ -595,7 +625,7 @@ services: # The DifySandbox sandbox: - image: langgenius/dify-sandbox:0.2.11 + image: langgenius/dify-sandbox:0.2.12 restart: always environment: # The DifySandbox configurations @@ -619,7 +649,7 @@ services: # plugin daemon plugin_daemon: - image: langgenius/dify-plugin-daemon:0.0.8-local + image: langgenius/dify-plugin-daemon:0.1.2-local restart: always environment: # Use the shared environment variables. @@ -645,6 +675,7 @@ services: PLUGIN_MEDIA_CACHE_PATH: ${PLUGIN_MEDIA_CACHE_PATH:-assets} PLUGIN_STORAGE_OSS_BUCKET: ${PLUGIN_STORAGE_OSS_BUCKET:-} S3_USE_AWS_MANAGED_IAM: ${PLUGIN_S3_USE_AWS_MANAGED_IAM:-false} + S3_USE_AWS: ${PLUGIN_S3_USE_AWS:-} S3_ENDPOINT: ${PLUGIN_S3_ENDPOINT:-} S3_USE_PATH_STYLE: ${PLUGIN_S3_USE_PATH_STYLE:-false} AWS_ACCESS_KEY: ${PLUGIN_AWS_ACCESS_KEY:-} @@ -655,6 +686,16 @@ services: TENCENT_COS_SECRET_KEY: ${PLUGIN_TENCENT_COS_SECRET_KEY:-} TENCENT_COS_SECRET_ID: ${PLUGIN_TENCENT_COS_SECRET_ID:-} TENCENT_COS_REGION: ${PLUGIN_TENCENT_COS_REGION:-} + ALIYUN_OSS_REGION: ${PLUGIN_ALIYUN_OSS_REGION:-} + ALIYUN_OSS_ENDPOINT: ${PLUGIN_ALIYUN_OSS_ENDPOINT:-} + ALIYUN_OSS_ACCESS_KEY_ID: ${PLUGIN_ALIYUN_OSS_ACCESS_KEY_ID:-} + ALIYUN_OSS_ACCESS_KEY_SECRET: ${PLUGIN_ALIYUN_OSS_ACCESS_KEY_SECRET:-} + ALIYUN_OSS_AUTH_VERSION: ${PLUGIN_ALIYUN_OSS_AUTH_VERSION:-v4} + ALIYUN_OSS_PATH: ${PLUGIN_ALIYUN_OSS_PATH:-} + VOLCENGINE_TOS_ENDPOINT: ${PLUGIN_VOLCENGINE_TOS_ENDPOINT:-} + VOLCENGINE_TOS_ACCESS_KEY: ${PLUGIN_VOLCENGINE_TOS_ACCESS_KEY:-} + VOLCENGINE_TOS_SECRET_KEY: ${PLUGIN_VOLCENGINE_TOS_SECRET_KEY:-} + VOLCENGINE_TOS_REGION: ${PLUGIN_VOLCENGINE_TOS_REGION:-} ports: - "${EXPOSE_PLUGIN_DEBUGGING_PORT:-5003}:${PLUGIN_DEBUGGING_PORT:-5003}" volumes: @@ -665,7 +706,7 @@ services: # ssrf_proxy server # for more information, please refer to - # https://docs.dify.ai/learn-more/faq/install-faq#id-18.-why-is-ssrf_proxy-needed + # https://docs.dify.ai/learn-more/faq/install-faq#18-why-is-ssrf-proxy-needed%3F ssrf_proxy: image: ubuntu/squid:latest restart: always @@ -840,6 +881,30 @@ services: timeout: 3s retries: 30 + # get image from https://www.vastdata.com.cn/ + vastbase: + image: vastdata/vastbase-vector + profiles: + - vastbase + restart: always + environment: + - VB_DBCOMPATIBILITY=PG + - VB_DB=dify + - VB_USERNAME=dify + - VB_PASSWORD=Difyai123456 + ports: + - '5434:5432' + volumes: + - ./vastbase/lic:/home/vastbase/vastbase/lic + - ./vastbase/data:/home/vastbase/data + - ./vastbase/backup:/home/vastbase/backup + - ./vastbase/backup_log:/home/vastbase/backup_log + healthcheck: + test: [ 'CMD', 'pg_isready' ] + interval: 1s + timeout: 3s + retries: 30 + # pgvecto-rs vector store pgvecto-rs: image: tensorchord/pgvecto-rs:pg16-v0.3.0 @@ -877,7 +942,7 @@ services: # OceanBase vector database oceanbase: - image: oceanbase/oceanbase-ce:4.3.5.1-101000042025031818 + image: oceanbase/oceanbase-ce:4.3.5-lts container_name: oceanbase profiles: - oceanbase @@ -891,9 +956,8 @@ services: OB_SYS_PASSWORD: ${OCEANBASE_VECTOR_PASSWORD:-difyai123456} OB_TENANT_PASSWORD: ${OCEANBASE_VECTOR_PASSWORD:-difyai123456} OB_CLUSTER_NAME: ${OCEANBASE_CLUSTER_NAME:-difyai} - MODE: MINI - ports: - - "${OCEANBASE_VECTOR_PORT:-2881}:2881" + OB_SERVER_IP: 127.0.0.1 + MODE: mini # Oracle vector database oracle: diff --git a/docker/middleware.env.example b/docker/middleware.env.example index 1a4484a9b5..338b057ae8 100644 --- a/docker/middleware.env.example +++ b/docker/middleware.env.example @@ -109,7 +109,7 @@ EXPOSE_PLUGIN_DEBUGGING_HOST=localhost EXPOSE_PLUGIN_DEBUGGING_PORT=5003 PLUGIN_DIFY_INNER_API_KEY=QaHbTe77CtuXmsfyhR7+vRjI/+XbV1AaFy691iy+kGDv2Jvy0/eAh8Y1 -PLUGIN_DIFY_INNER_API_URL=http://api:5001 +PLUGIN_DIFY_INNER_API_URL=http://host.docker.internal:5001 MARKETPLACE_ENABLED=true MARKETPLACE_API_URL=https://marketplace.dify.ai @@ -133,6 +133,7 @@ PLUGIN_MEDIA_CACHE_PATH=assets PLUGIN_STORAGE_OSS_BUCKET= # Plugin oss s3 credentials PLUGIN_S3_USE_AWS_MANAGED_IAM=false +PLUGIN_S3_USE_AWS= PLUGIN_S3_ENDPOINT= PLUGIN_S3_USE_PATH_STYLE=false PLUGIN_AWS_ACCESS_KEY= @@ -144,4 +145,16 @@ PLUGIN_AZURE_BLOB_STORAGE_CONNECTION_STRING= # Plugin oss tencent cos PLUGIN_TENCENT_COS_SECRET_KEY= PLUGIN_TENCENT_COS_SECRET_ID= -PLUGIN_TENCENT_COS_REGION= \ No newline at end of file +PLUGIN_TENCENT_COS_REGION= +# Plugin oss aliyun oss +PLUGIN_ALIYUN_OSS_REGION= +PLUGIN_ALIYUN_OSS_ENDPOINT= +PLUGIN_ALIYUN_OSS_ACCESS_KEY_ID= +PLUGIN_ALIYUN_OSS_ACCESS_KEY_SECRET= +PLUGIN_ALIYUN_OSS_AUTH_VERSION=v4 +PLUGIN_ALIYUN_OSS_PATH= +# Plugin oss volcengine tos +PLUGIN_VOLCENGINE_TOS_ENDPOINT= +PLUGIN_VOLCENGINE_TOS_ACCESS_KEY= +PLUGIN_VOLCENGINE_TOS_SECRET_KEY= +PLUGIN_VOLCENGINE_TOS_REGION= diff --git a/docker/nginx/docker-entrypoint.sh b/docker/nginx/docker-entrypoint.sh index 8e1110ffa9..763254e37b 100755 --- a/docker/nginx/docker-entrypoint.sh +++ b/docker/nginx/docker-entrypoint.sh @@ -39,4 +39,4 @@ envsubst "$env_vars" < /etc/nginx/proxy.conf.template > /etc/nginx/proxy.conf envsubst "$env_vars" < /etc/nginx/conf.d/default.conf.template > /etc/nginx/conf.d/default.conf # Start Nginx using the default entrypoint -exec nginx -g 'daemon off;' \ No newline at end of file +exec nginx -g 'daemon off;' diff --git a/docker/nginx/https.conf.template b/docker/nginx/https.conf.template index 95ea36f463..296908d8be 100644 --- a/docker/nginx/https.conf.template +++ b/docker/nginx/https.conf.template @@ -6,4 +6,4 @@ ssl_certificate_key ${SSL_CERTIFICATE_KEY_PATH}; ssl_protocols ${NGINX_SSL_PROTOCOLS}; ssl_prefer_server_ciphers on; ssl_session_cache shared:SSL:10m; -ssl_session_timeout 10m; \ No newline at end of file +ssl_session_timeout 10m; diff --git a/docker/nginx/nginx.conf.template b/docker/nginx/nginx.conf.template index 32a571653e..20446fae2e 100644 --- a/docker/nginx/nginx.conf.template +++ b/docker/nginx/nginx.conf.template @@ -31,4 +31,4 @@ http { client_max_body_size ${NGINX_CLIENT_MAX_BODY_SIZE}; include /etc/nginx/conf.d/*.conf; -} \ No newline at end of file +} diff --git a/docker/ssrf_proxy/squid.conf.template b/docker/ssrf_proxy/squid.conf.template index c74c1fb67b..1775a1fff9 100644 --- a/docker/ssrf_proxy/squid.conf.template +++ b/docker/ssrf_proxy/squid.conf.template @@ -44,7 +44,7 @@ refresh_pattern . 0 20% 4320 # cache_dir ufs /var/spool/squid 100 16 256 # upstream proxy, set to your own upstream proxy IP to avoid SSRF attacks -# cache_peer 172.1.1.1 parent 3128 0 no-query no-digest no-netdb-exchange default +# cache_peer 172.1.1.1 parent 3128 0 no-query no-digest no-netdb-exchange default ################################## Reverse Proxy To Sandbox ################################ http_port ${REVERSE_PROXY_PORT} accel vhost @@ -53,4 +53,4 @@ acl src_all src all http_access allow src_all # Unless the option's size is increased, an error will occur when uploading more than two files. -client_request_buffer_max_size 100 MB \ No newline at end of file +client_request_buffer_max_size 100 MB diff --git a/docker/startupscripts/init.sh b/docker/startupscripts/init.sh index c6e6e1966f..dcee1e1978 100755 --- a/docker/startupscripts/init.sh +++ b/docker/startupscripts/init.sh @@ -8,6 +8,6 @@ if [ -f ${DB_INITIALIZED} ]; then exit else echo 'File does not exist. Standards for first time Start up this DB' - "$ORACLE_HOME"/bin/sqlplus -s "/ as sysdba" @"/opt/oracle/scripts/startup/init_user.script"; + "$ORACLE_HOME"/bin/sqlplus -s "/ as sysdba" @"/opt/oracle/scripts/startup/init_user.script"; touch ${DB_INITIALIZED} fi diff --git a/docker/startupscripts/init_user.script b/docker/startupscripts/init_user.script index 0c5bff1ef6..e710d827e8 100755 --- a/docker/startupscripts/init_user.script +++ b/docker/startupscripts/init_user.script @@ -1,5 +1,5 @@ show pdbs; -ALTER SYSTEM SET PROCESSES=500 SCOPE=SPFILE; +ALTER SYSTEM SET PROCESSES=500 SCOPE=SPFILE; alter session set container= freepdb1; create user dify identified by dify DEFAULT TABLESPACE users quota unlimited on users; grant DB_DEVELOPER_ROLE to dify; diff --git a/docker/tidb/config/pd.toml b/docker/tidb/config/pd.toml index 042b251e46..01e352a86a 100644 --- a/docker/tidb/config/pd.toml +++ b/docker/tidb/config/pd.toml @@ -1,4 +1,4 @@ # PD Configuration File reference: # https://docs.pingcap.com/tidb/stable/pd-configuration-file#pd-configuration-file [replication] -max-replicas = 1 \ No newline at end of file +max-replicas = 1 diff --git a/docker/volumes/myscale/config/users.d/custom_users_config.xml b/docker/volumes/myscale/config/users.d/custom_users_config.xml index 67f24b69ee..b46e73a0e9 100644 --- a/docker/volumes/myscale/config/users.d/custom_users_config.xml +++ b/docker/volumes/myscale/config/users.d/custom_users_config.xml @@ -14,4 +14,4 @@ 1 - \ No newline at end of file + diff --git a/docker/volumes/oceanbase/init.d/vec_memory.sql b/docker/volumes/oceanbase/init.d/vec_memory.sql index f4c283fdf4..0d859e5f7c 100644 --- a/docker/volumes/oceanbase/init.d/vec_memory.sql +++ b/docker/volumes/oceanbase/init.d/vec_memory.sql @@ -1 +1 @@ -ALTER SYSTEM SET ob_vector_memory_limit_percentage = 30; \ No newline at end of file +ALTER SYSTEM SET ob_vector_memory_limit_percentage = 30; diff --git a/images/GitHub_README_cover.png b/images/GitHub_README_cover.png deleted file mode 100644 index 0e21be2177..0000000000 Binary files a/images/GitHub_README_cover.png and /dev/null differ diff --git a/images/GitHub_README_if.png b/images/GitHub_README_if.png new file mode 100644 index 0000000000..2a4e67264e Binary files /dev/null and b/images/GitHub_README_if.png differ diff --git a/images/demo.png b/images/demo.png deleted file mode 100644 index ad0cbe0fb5..0000000000 Binary files a/images/demo.png and /dev/null differ diff --git a/images/describe.png b/images/describe.png new file mode 100644 index 0000000000..747739dc2c Binary files /dev/null and b/images/describe.png differ diff --git a/sdks/nodejs-client/.gitignore b/sdks/nodejs-client/.gitignore index 35d1a1461b..1d40ff2ece 100644 --- a/sdks/nodejs-client/.gitignore +++ b/sdks/nodejs-client/.gitignore @@ -45,4 +45,4 @@ package-lock.json .yarnrc.yml # pmpm -pnpm-lock.yaml \ No newline at end of file +pnpm-lock.yaml diff --git a/sdks/nodejs-client/index.d.ts b/sdks/nodejs-client/index.d.ts index a2e6e50aef..a8b7497f4f 100644 --- a/sdks/nodejs-client/index.d.ts +++ b/sdks/nodejs-client/index.d.ts @@ -26,7 +26,7 @@ export declare class DifyClient { params?: Params, stream?: boolean, headerParams?: HeaderParams - ): Promise; + ): Promise; messageFeedback(message_id: string, rating: number, user: User): Promise; @@ -64,9 +64,9 @@ export declare class ChatClient extends DifyClient { getConversations( - user: User, - first_id?: string | null, - limit?: number | null, + user: User, + first_id?: string | null, + limit?: number | null, pinned?: boolean | null ): Promise; @@ -80,7 +80,7 @@ export declare class ChatClient extends DifyClient { renameConversation(conversation_id: string, name: string, user: User,auto_generate:boolean): Promise; deleteConversation(conversation_id: string, user: User): Promise; - + audioToText(data: FormData): Promise; } @@ -88,4 +88,4 @@ export declare class WorkflowClient extends DifyClient { run(inputs: any, user: User, stream?: boolean,): Promise; stop(task_id: string, user: User): Promise; -} \ No newline at end of file +} diff --git a/sdks/nodejs-client/index.js b/sdks/nodejs-client/index.js index 858241ce5a..0ba7bba8bb 100644 --- a/sdks/nodejs-client/index.js +++ b/sdks/nodejs-client/index.js @@ -334,12 +334,12 @@ export class ChatClient extends DifyClient { export class WorkflowClient extends DifyClient { run(inputs,user,stream) { - const data = { - inputs, + const data = { + inputs, response_mode: stream ? "streaming" : "blocking", - user + user }; - + return this.sendRequest( routes.runWorkflow.method, routes.runWorkflow.url(), @@ -357,4 +357,4 @@ export class WorkflowClient extends DifyClient { data ); } -} \ No newline at end of file +} diff --git a/sdks/nodejs-client/index.test.js b/sdks/nodejs-client/index.test.js index f300b16fc9..1f5d6edb06 100644 --- a/sdks/nodejs-client/index.test.js +++ b/sdks/nodejs-client/index.test.js @@ -62,4 +62,4 @@ describe('Send Requests', () => { errorMessage ) }) -}) \ No newline at end of file +}) diff --git a/sdks/nodejs-client/package.json b/sdks/nodejs-client/package.json index cc27c5e0c0..cd3bcc4bce 100644 --- a/sdks/nodejs-client/package.json +++ b/sdks/nodejs-client/package.json @@ -32,4 +32,4 @@ "babel-jest": "^29.5.0", "jest": "^29.5.0" } -} \ No newline at end of file +} diff --git a/sdks/php-client/README.md b/sdks/php-client/README.md index 812980d834..91e77ad9ff 100644 --- a/sdks/php-client/README.md +++ b/sdks/php-client/README.md @@ -92,4 +92,4 @@ Replace 'your-api-key-here' with your actual Dify API key. ## License -This SDK is released under the MIT License. \ No newline at end of file +This SDK is released under the MIT License. diff --git a/sdks/php-client/dify-client.php b/sdks/php-client/dify-client.php index acb862093a..b6cf261b66 100644 --- a/sdks/php-client/dify-client.php +++ b/sdks/php-client/dify-client.php @@ -119,14 +119,14 @@ class ChatClient extends DifyClient { return $this->send_request('POST', 'chat-messages', $data, null, $response_mode === 'streaming'); } - + public function get_suggestions($message_id, $user) { $params = [ 'user' => $user ]; return $this->send_request('GET', "messages/{$message_id}/suggested", null, $params); } - + public function stop_message($task_id, $user) { $data = ['user' => $user]; return $this->send_request('POST', "chat-messages/{$task_id}/stop", $data); @@ -157,7 +157,7 @@ class ChatClient extends DifyClient { return $this->send_request('GET', 'messages', null, $params); } - + public function rename_conversation($conversation_id, $name,$auto_generate, $user) { $data = [ 'name' => $name, @@ -202,5 +202,5 @@ class WorkflowClient extends DifyClient{ ]; return $this->send_request('POST', "workflows/tasks/{$task_id}/stop",$data); } - -} \ No newline at end of file + +} diff --git a/sdks/python-client/MANIFEST.in b/sdks/python-client/MANIFEST.in index da331d5e5c..12f44237a2 100644 --- a/sdks/python-client/MANIFEST.in +++ b/sdks/python-client/MANIFEST.in @@ -1 +1 @@ -recursive-include dify_client *.py \ No newline at end of file +recursive-include dify_client *.py diff --git a/sdks/python-client/build.sh b/sdks/python-client/build.sh index ca1a762c99..525f57c1ef 100755 --- a/sdks/python-client/build.sh +++ b/sdks/python-client/build.sh @@ -6,4 +6,4 @@ rm -rf build dist *.egg-info pip install setuptools wheel twine python setup.py sdist bdist_wheel -twine upload dist/* \ No newline at end of file +twine upload dist/* diff --git a/sdks/python-client/dify_client/client.py b/sdks/python-client/dify_client/client.py index ee1b5c57e1..d885dc6fb7 100644 --- a/sdks/python-client/dify_client/client.py +++ b/sdks/python-client/dify_client/client.py @@ -47,7 +47,7 @@ class DifyClient: def text_to_audio(self, text: str, user: str, streaming: bool = False): data = {"text": text, "user": user, "streaming": streaming} - return self._send_request("POST", "/text-to-audio", data=data) + return self._send_request("POST", "/text-to-audio", json=data) def get_meta(self, user): params = {"user": user} diff --git a/web/.dockerignore b/web/.dockerignore index 45a8922ce9..31eb66c210 100644 --- a/web/.dockerignore +++ b/web/.dockerignore @@ -21,4 +21,4 @@ node_modules # Jetbrains -.idea \ No newline at end of file +.idea diff --git a/web/.env.example b/web/.env.example index 51631c2437..78b4f33e8c 100644 --- a/web/.env.example +++ b/web/.env.example @@ -50,7 +50,7 @@ NEXT_PUBLIC_MAX_TOOLS_NUM=10 NEXT_PUBLIC_MAX_PARALLEL_LIMIT=10 # The maximum number of iterations for agent setting -NEXT_PUBLIC_MAX_ITERATIONS_NUM=5 +NEXT_PUBLIC_MAX_ITERATIONS_NUM=99 NEXT_PUBLIC_ENABLE_WEBSITE_JINAREADER=true NEXT_PUBLIC_ENABLE_WEBSITE_FIRECRAWL=true diff --git a/web/.vscode/extensions.json b/web/.vscode/extensions.json index a9afbcc640..e0e72ce11e 100644 --- a/web/.vscode/extensions.json +++ b/web/.vscode/extensions.json @@ -4,4 +4,4 @@ "firsttris.vscode-jest-runner", "kisstkondoros.vscode-codemetrics" ] -} \ No newline at end of file +} diff --git a/web/Dockerfile b/web/Dockerfile index dfc5ba8b46..93eef59815 100644 --- a/web/Dockerfile +++ b/web/Dockerfile @@ -6,7 +6,7 @@ LABEL maintainer="takatost@gmail.com" # RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories RUN apk add --no-cache tzdata -RUN npm install -g pnpm@10.8.0 +RUN npm install -g pnpm@10.11.1 ENV PNPM_HOME="/pnpm" ENV PATH="$PNPM_HOME:$PATH" diff --git a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/layout-main.tsx b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/layout-main.tsx index 1434a81b55..7d5d4cb52d 100644 --- a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/layout-main.tsx +++ b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/layout-main.tsx @@ -15,17 +15,17 @@ import { } from '@remixicon/react' import { useTranslation } from 'react-i18next' import { useShallow } from 'zustand/react/shallow' -import { useContextSelector } from 'use-context-selector' import s from './style.module.css' import cn from '@/utils/classnames' import { useStore } from '@/app/components/app/store' import AppSideBar from '@/app/components/app-sidebar' import type { NavIcon } from '@/app/components/app-sidebar/navLink' -import { fetchAppDetail, fetchAppSSO } from '@/service/apps' -import AppContext, { useAppContext } from '@/context/app-context' +import { fetchAppDetail } from '@/service/apps' +import { useAppContext } from '@/context/app-context' import Loading from '@/app/components/base/loading' import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints' import type { App } from '@/types/app' +import useDocumentTitle from '@/hooks/use-document-title' export type IAppDetailLayoutProps = { children: React.ReactNode @@ -56,7 +56,6 @@ const AppDetailLayout: FC = (props) => { icon: NavIcon selectedIcon: NavIcon }>>([]) - const systemFeatures = useContextSelector(AppContext, state => state.systemFeatures) const getNavigations = useCallback((appId: string, isCurrentWorkspaceEditor: boolean, mode: string) => { const navs = [ @@ -96,9 +95,10 @@ const AppDetailLayout: FC = (props) => { return navs }, []) + useDocumentTitle(appDetail?.name || t('common.menus.appDetail')) + useEffect(() => { if (appDetail) { - document.title = `${(appDetail.name || 'App')} - Dify` const localeMode = localStorage.getItem('app-detail-collapse-or-expand') || 'expand' const mode = isMobile ? 'collapse' : 'expand' setAppSiderbarExpand(isMobile ? mode : localeMode) @@ -142,14 +142,9 @@ const AppDetailLayout: FC = (props) => { else { setAppDetail({ ...res, enable_sso: false }) setNavigation(getNavigations(appId, isCurrentWorkspaceEditor, res.mode)) - if (systemFeatures.enable_web_sso_switch_component && canIEditApp) { - fetchAppSSO({ appId }).then((ssoRes) => { - setAppDetail({ ...res, enable_sso: ssoRes.enabled }) - }) - } } // eslint-disable-next-line react-hooks/exhaustive-deps - }, [appDetailRes, isCurrentWorkspaceEditor, isLoadingAppDetail, isLoadingCurrentWorkspace, systemFeatures.enable_web_sso_switch_component]) + }, [appDetailRes, isCurrentWorkspaceEditor, isLoadingAppDetail, isLoadingCurrentWorkspace]) useUnmount(() => { setAppDetail() diff --git a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/cardView.tsx b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/cardView.tsx index 79b45941f1..084adceef2 100644 --- a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/cardView.tsx +++ b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/cardView.tsx @@ -2,25 +2,22 @@ import type { FC } from 'react' import React from 'react' import { useTranslation } from 'react-i18next' -import { useContext, useContextSelector } from 'use-context-selector' +import { useContext } from 'use-context-selector' import AppCard from '@/app/components/app/overview/appCard' import Loading from '@/app/components/base/loading' import { ToastContext } from '@/app/components/base/toast' import { fetchAppDetail, - fetchAppSSO, - updateAppSSO, updateAppSiteAccessToken, updateAppSiteConfig, updateAppSiteStatus, } from '@/service/apps' -import type { App, AppSSO } from '@/types/app' +import type { App } from '@/types/app' import type { UpdateAppSiteCodeResponse } from '@/models/app' import { asyncRunSafe } from '@/utils' import { NEED_REFRESH_APP_LIST_KEY } from '@/config' import type { IAppCardProps } from '@/app/components/app/overview/appCard' import { useStore as useAppStore } from '@/app/components/app/store' -import AppContext from '@/context/app-context' export type ICardViewProps = { appId: string @@ -33,18 +30,11 @@ const CardView: FC = ({ appId, isInPanel, className }) => { const { notify } = useContext(ToastContext) const appDetail = useAppStore(state => state.appDetail) const setAppDetail = useAppStore(state => state.setAppDetail) - const systemFeatures = useContextSelector(AppContext, state => state.systemFeatures) const updateAppDetail = async () => { try { const res = await fetchAppDetail({ url: '/apps', id: appId }) - if (systemFeatures.enable_web_sso_switch_component) { - const ssoRes = await fetchAppSSO({ appId }) - setAppDetail({ ...res, enable_sso: ssoRes.enabled }) - } - else { - setAppDetail({ ...res }) - } + setAppDetail({ ...res }) } catch (error) { console.error(error) } } @@ -95,16 +85,6 @@ const CardView: FC = ({ appId, isInPanel, className }) => { if (!err) localStorage.setItem(NEED_REFRESH_APP_LIST_KEY, '1') - if (systemFeatures.enable_web_sso_switch_component) { - const [sso_err] = await asyncRunSafe( - updateAppSSO({ id: appId, enabled: Boolean(params.enable_sso) }) as Promise, - ) - if (sso_err) { - handleCallbackResult(sso_err) - return - } - } - handleCallbackResult(err) } diff --git a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/chartView.tsx b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/chartView.tsx index 4afba06eae..646c8bd93d 100644 --- a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/chartView.tsx +++ b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/chartView.tsx @@ -18,9 +18,10 @@ const queryDateFormat = 'YYYY-MM-DD HH:mm' export type IChartViewProps = { appId: string + headerRight: React.ReactNode } -export default function ChartView({ appId }: IChartViewProps) { +export default function ChartView({ appId, headerRight }: IChartViewProps) { const { t } = useTranslation() const appDetail = useAppStore(state => state.appDetail) const isChatApp = appDetail?.mode !== 'completion' && appDetail?.mode !== 'workflow' @@ -46,19 +47,24 @@ export default function ChartView({ appId }: IChartViewProps) { return (
-
- {t('appOverview.analysis.title')} - ({ value: k, name: t(`appLog.filter.period.${v.name}`) }))} - className='mt-0 !w-40' - onSelect={(item) => { - const id = item.value - const value = TIME_PERIOD_MAPPING[id]?.value ?? '-1' - const name = item.name || t('appLog.filter.period.allTime') - onSelect({ value, name }) - }} - defaultValue={'2'} - /> +
+
{t('common.appMenus.overview')}
+
+
+ ({ value: k, name: t(`appLog.filter.period.${v.name}`) }))} + className='mt-0 !w-40' + onSelect={(item) => { + const id = item.value + const value = TIME_PERIOD_MAPPING[id]?.value ?? '-1' + const name = item.name || t('appLog.filter.period.allTime') + onSelect({ value, name }) + }} + defaultValue={'2'} + /> +
+ {headerRight} +
{!isWorkflow && (
diff --git a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/page.tsx b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/page.tsx index 0f1bb7e18d..fc97f5e669 100644 --- a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/page.tsx +++ b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/page.tsx @@ -1,6 +1,5 @@ import React from 'react' import ChartView from './chartView' -import CardView from './cardView' import TracingPanel from './tracing/panel' import ApikeyInfoPanel from '@/app/components/app/overview/apikey-info-panel' @@ -18,9 +17,10 @@ const Overview = async (props: IDevelopProps) => { return (
- - - + } + />
) } diff --git a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/config-popup.tsx b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/config-popup.tsx index eb23da2ae0..0efc5082a4 100644 --- a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/config-popup.tsx +++ b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/config-popup.tsx @@ -5,7 +5,7 @@ import { useTranslation } from 'react-i18next' import { useBoolean } from 'ahooks' import TracingIcon from './tracing-icon' import ProviderPanel from './provider-panel' -import type { LangFuseConfig, LangSmithConfig, OpikConfig } from './type' +import type { LangFuseConfig, LangSmithConfig, OpikConfig, WeaveConfig } from './type' import { TracingProvider } from './type' import ProviderConfigModal from './provider-config-modal' import Indicator from '@/app/components/header/indicator' @@ -26,7 +26,8 @@ export type PopupProps = { langSmithConfig: LangSmithConfig | null langFuseConfig: LangFuseConfig | null opikConfig: OpikConfig | null - onConfigUpdated: (provider: TracingProvider, payload: LangSmithConfig | LangFuseConfig | OpikConfig) => void + weaveConfig: WeaveConfig | null + onConfigUpdated: (provider: TracingProvider, payload: LangSmithConfig | LangFuseConfig | OpikConfig | WeaveConfig) => void onConfigRemoved: (provider: TracingProvider) => void } @@ -40,6 +41,7 @@ const ConfigPopup: FC = ({ langSmithConfig, langFuseConfig, opikConfig, + weaveConfig, onConfigUpdated, onConfigRemoved, }) => { @@ -63,7 +65,7 @@ const ConfigPopup: FC = ({ } }, [onChooseProvider]) - const handleConfigUpdated = useCallback((payload: LangSmithConfig | LangFuseConfig | OpikConfig) => { + const handleConfigUpdated = useCallback((payload: LangSmithConfig | LangFuseConfig | OpikConfig | WeaveConfig) => { onConfigUpdated(currentProvider!, payload) hideConfigModal() }, [currentProvider, hideConfigModal, onConfigUpdated]) @@ -73,8 +75,8 @@ const ConfigPopup: FC = ({ hideConfigModal() }, [currentProvider, hideConfigModal, onConfigRemoved]) - const providerAllConfigured = langSmithConfig && langFuseConfig && opikConfig - const providerAllNotConfigured = !langSmithConfig && !langFuseConfig && !opikConfig + const providerAllConfigured = langSmithConfig && langFuseConfig && opikConfig && weaveConfig + const providerAllNotConfigured = !langSmithConfig && !langFuseConfig && !opikConfig && !weaveConfig const switchContent = ( = ({ /> ) + const weavePanel = ( + + ) const configuredProviderPanel = () => { const configuredPanels: JSX.Element[] = [] - if (langSmithConfig) - configuredPanels.push(langSmithPanel) - if (langFuseConfig) configuredPanels.push(langfusePanel) + if (langSmithConfig) + configuredPanels.push(langSmithPanel) + if (opikConfig) configuredPanels.push(opikPanel) + if (weaveConfig) + configuredPanels.push(weavePanel) + return configuredPanels } const moreProviderPanel = () => { const notConfiguredPanels: JSX.Element[] = [] - if (!langSmithConfig) - notConfiguredPanels.push(langSmithPanel) - if (!langFuseConfig) notConfiguredPanels.push(langfusePanel) + if (!langSmithConfig) + notConfiguredPanels.push(langSmithPanel) + if (!opikConfig) notConfiguredPanels.push(opikPanel) + if (!weaveConfig) + notConfiguredPanels.push(weavePanel) + return notConfiguredPanels } @@ -158,7 +178,9 @@ const ConfigPopup: FC = ({ return langSmithConfig if (currentProvider === TracingProvider.langfuse) return langFuseConfig - return opikConfig + if (currentProvider === TracingProvider.opik) + return opikConfig + return weaveConfig } return ( @@ -199,9 +221,10 @@ const ConfigPopup: FC = ({ <>
{t(`${I18N_PREFIX}.configProviderTitle.${providerAllConfigured ? 'configured' : 'notConfigured'}`)}
- {langSmithPanel} {langfusePanel} + {langSmithPanel} {opikPanel} + {weavePanel}
) diff --git a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/config.ts b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/config.ts index 0f3f280b30..5d3c4076bd 100644 --- a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/config.ts +++ b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/config.ts @@ -4,4 +4,5 @@ export const docURL = { [TracingProvider.langSmith]: 'https://docs.smith.langchain.com/', [TracingProvider.langfuse]: 'https://docs.langfuse.com', [TracingProvider.opik]: 'https://www.comet.com/docs/opik/tracing/integrations/dify#setup-instructions', + [TracingProvider.weave]: 'https://weave-docs.wandb.ai/', } diff --git a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/panel.tsx b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/panel.tsx index 160110f7ed..76e90ecf19 100644 --- a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/panel.tsx +++ b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/panel.tsx @@ -7,12 +7,12 @@ import { import { useTranslation } from 'react-i18next' import { usePathname } from 'next/navigation' import { useBoolean } from 'ahooks' -import type { LangFuseConfig, LangSmithConfig, OpikConfig } from './type' +import type { LangFuseConfig, LangSmithConfig, OpikConfig, WeaveConfig } from './type' import { TracingProvider } from './type' import TracingIcon from './tracing-icon' import ConfigButton from './config-button' import cn from '@/utils/classnames' -import { LangfuseIcon, LangsmithIcon, OpikIcon } from '@/app/components/base/icons/src/public/tracing' +import { LangfuseIcon, LangsmithIcon, OpikIcon, WeaveIcon } from '@/app/components/base/icons/src/public/tracing' import Indicator from '@/app/components/header/indicator' import { fetchTracingConfig as doFetchTracingConfig, fetchTracingStatus, updateTracingStatus } from '@/service/apps' import type { TracingStatus } from '@/models/app' @@ -23,19 +23,6 @@ import Divider from '@/app/components/base/divider' const I18N_PREFIX = 'app.tracing' -const Title = ({ - className, -}: { - className?: string -}) => { - const { t } = useTranslation() - - return ( -
- {t('common.appMenus.overview')} -
- ) -} const Panel: FC = () => { const { t } = useTranslation() const pathname = usePathname() @@ -82,12 +69,15 @@ const Panel: FC = () => { ? LangfuseIcon : inUseTracingProvider === TracingProvider.opik ? OpikIcon - : LangsmithIcon + : inUseTracingProvider === TracingProvider.weave + ? WeaveIcon + : LangsmithIcon const [langSmithConfig, setLangSmithConfig] = useState(null) const [langFuseConfig, setLangFuseConfig] = useState(null) const [opikConfig, setOpikConfig] = useState(null) - const hasConfiguredTracing = !!(langSmithConfig || langFuseConfig || opikConfig) + const [weaveConfig, setWeaveConfig] = useState(null) + const hasConfiguredTracing = !!(langSmithConfig || langFuseConfig || opikConfig || weaveConfig) const fetchTracingConfig = async () => { const { tracing_config: langSmithConfig, has_not_configured: langSmithHasNotConfig } = await doFetchTracingConfig({ appId, provider: TracingProvider.langSmith }) @@ -99,6 +89,9 @@ const Panel: FC = () => { const { tracing_config: opikConfig, has_not_configured: OpikHasNotConfig } = await doFetchTracingConfig({ appId, provider: TracingProvider.opik }) if (!OpikHasNotConfig) setOpikConfig(opikConfig as OpikConfig) + const { tracing_config: weaveConfig, has_not_configured: weaveHasNotConfig } = await doFetchTracingConfig({ appId, provider: TracingProvider.weave }) + if (!weaveHasNotConfig) + setWeaveConfig(weaveConfig as WeaveConfig) } const handleTracingConfigUpdated = async (provider: TracingProvider) => { @@ -110,6 +103,8 @@ const Panel: FC = () => { setLangFuseConfig(tracing_config as LangFuseConfig) else if (provider === TracingProvider.opik) setOpikConfig(tracing_config as OpikConfig) + else if (provider === TracingProvider.weave) + setWeaveConfig(tracing_config as WeaveConfig) } const handleTracingConfigRemoved = (provider: TracingProvider) => { @@ -119,6 +114,8 @@ const Panel: FC = () => { setLangFuseConfig(null) else if (provider === TracingProvider.opik) setOpikConfig(null) + else if (provider === TracingProvider.weave) + setWeaveConfig(null) if (provider === inUseTracingProvider) { handleTracingStatusChange({ enabled: false, @@ -144,7 +141,6 @@ const Panel: FC = () => { if (!isLoaded) { return (
- <div className='w-[200px]'> <Loading /> </div> @@ -153,8 +149,7 @@ const Panel: FC = () => { } return ( - <div className={cn('mb-3 flex items-center justify-between')}> - <Title className='h-[41px]' /> + <div className={cn('flex items-center justify-between')}> <div className={cn( 'flex cursor-pointer items-center rounded-xl border-l-[0.5px] border-t border-effects-highlight bg-background-default-dodge p-2 shadow-xs hover:border-effects-highlight-lightmode-off hover:bg-background-default-lighter', @@ -178,6 +173,7 @@ const Panel: FC = () => { langSmithConfig={langSmithConfig} langFuseConfig={langFuseConfig} opikConfig={opikConfig} + weaveConfig={weaveConfig} onConfigUpdated={handleTracingConfigUpdated} onConfigRemoved={handleTracingConfigRemoved} controlShowPopup={controlShowPopup} @@ -212,6 +208,7 @@ const Panel: FC = () => { langSmithConfig={langSmithConfig} langFuseConfig={langFuseConfig} opikConfig={opikConfig} + weaveConfig={weaveConfig} onConfigUpdated={handleTracingConfigUpdated} onConfigRemoved={handleTracingConfigRemoved} controlShowPopup={controlShowPopup} diff --git a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/provider-config-modal.tsx b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/provider-config-modal.tsx index a7675c4a66..b6c97add48 100644 --- a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/provider-config-modal.tsx +++ b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/provider-config-modal.tsx @@ -4,7 +4,7 @@ import React, { useCallback, useState } from 'react' import { useTranslation } from 'react-i18next' import { useBoolean } from 'ahooks' import Field from './field' -import type { LangFuseConfig, LangSmithConfig, OpikConfig } from './type' +import type { LangFuseConfig, LangSmithConfig, OpikConfig, WeaveConfig } from './type' import { TracingProvider } from './type' import { docURL } from './config' import { @@ -22,10 +22,10 @@ import Divider from '@/app/components/base/divider' type Props = { appId: string type: TracingProvider - payload?: LangSmithConfig | LangFuseConfig | OpikConfig | null + payload?: LangSmithConfig | LangFuseConfig | OpikConfig | WeaveConfig | null onRemoved: () => void onCancel: () => void - onSaved: (payload: LangSmithConfig | LangFuseConfig | OpikConfig) => void + onSaved: (payload: LangSmithConfig | LangFuseConfig | OpikConfig | WeaveConfig) => void onChosen: (provider: TracingProvider) => void } @@ -50,6 +50,14 @@ const opikConfigTemplate = { workspace: '', } +const weaveConfigTemplate = { + api_key: '', + entity: '', + project: '', + endpoint: '', + host: '', +} + const ProviderConfigModal: FC<Props> = ({ appId, type, @@ -63,7 +71,7 @@ const ProviderConfigModal: FC<Props> = ({ const isEdit = !!payload const isAdd = !isEdit const [isSaving, setIsSaving] = useState(false) - const [config, setConfig] = useState<LangSmithConfig | LangFuseConfig | OpikConfig>((() => { + const [config, setConfig] = useState<LangSmithConfig | LangFuseConfig | OpikConfig | WeaveConfig>((() => { if (isEdit) return payload @@ -73,7 +81,10 @@ const ProviderConfigModal: FC<Props> = ({ else if (type === TracingProvider.langfuse) return langFuseConfigTemplate - return opikConfigTemplate + else if (type === TracingProvider.opik) + return opikConfigTemplate + + return weaveConfigTemplate })()) const [isShowRemoveConfirm, { setTrue: showRemoveConfirm, @@ -127,6 +138,14 @@ const ProviderConfigModal: FC<Props> = ({ // const postData = config as OpikConfig } + if (type === TracingProvider.weave) { + const postData = config as WeaveConfig + if (!errorMessage && !postData.api_key) + errorMessage = t('common.errorMsg.fieldRequired', { field: 'API Key' }) + if (!errorMessage && !postData.project) + errorMessage = t('common.errorMsg.fieldRequired', { field: t(`${I18N_PREFIX}.project`) }) + } + return errorMessage }, [config, t, type]) const handleSave = useCallback(async () => { @@ -176,6 +195,47 @@ const ProviderConfigModal: FC<Props> = ({ </div> <div className='space-y-4'> + {type === TracingProvider.weave && ( + <> + <Field + label='API Key' + labelClassName='!text-sm' + isRequired + value={(config as WeaveConfig).api_key} + onChange={handleConfigChange('api_key')} + placeholder={t(`${I18N_PREFIX}.placeholder`, { key: 'API Key' })!} + /> + <Field + label={t(`${I18N_PREFIX}.project`)!} + labelClassName='!text-sm' + isRequired + value={(config as WeaveConfig).project} + onChange={handleConfigChange('project')} + placeholder={t(`${I18N_PREFIX}.placeholder`, { key: t(`${I18N_PREFIX}.project`) })!} + /> + <Field + label='Entity' + labelClassName='!text-sm' + value={(config as WeaveConfig).entity} + onChange={handleConfigChange('entity')} + placeholder={t(`${I18N_PREFIX}.placeholder`, { key: 'Entity' })!} + /> + <Field + label='Endpoint' + labelClassName='!text-sm' + value={(config as WeaveConfig).endpoint} + onChange={handleConfigChange('endpoint')} + placeholder={'https://trace.wandb.ai/'} + /> + <Field + label='Host' + labelClassName='!text-sm' + value={(config as WeaveConfig).host} + onChange={handleConfigChange('host')} + placeholder={'https://api.wandb.ai'} + /> + </> + )} {type === TracingProvider.langSmith && ( <> <Field @@ -263,7 +323,6 @@ const ProviderConfigModal: FC<Props> = ({ /> </> )} - </div> <div className='my-8 flex h-8 items-center justify-between'> <a diff --git a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/provider-panel.tsx b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/provider-panel.tsx index ec85ddd59c..bdccc502d6 100644 --- a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/provider-panel.tsx +++ b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/provider-panel.tsx @@ -7,7 +7,7 @@ import { import { useTranslation } from 'react-i18next' import { TracingProvider } from './type' import cn from '@/utils/classnames' -import { LangfuseIconBig, LangsmithIconBig, OpikIconBig } from '@/app/components/base/icons/src/public/tracing' +import { LangfuseIconBig, LangsmithIconBig, OpikIconBig, WeaveIconBig } from '@/app/components/base/icons/src/public/tracing' import { Eye as View } from '@/app/components/base/icons/src/vender/solid/general' const I18N_PREFIX = 'app.tracing' @@ -27,6 +27,7 @@ const getIcon = (type: TracingProvider) => { [TracingProvider.langSmith]: LangsmithIconBig, [TracingProvider.langfuse]: LangfuseIconBig, [TracingProvider.opik]: OpikIconBig, + [TracingProvider.weave]: WeaveIconBig, })[type] } diff --git a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/type.ts b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/type.ts index 982d01ffb3..ed468caf65 100644 --- a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/type.ts +++ b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/type.ts @@ -2,6 +2,7 @@ export enum TracingProvider { langSmith = 'langsmith', langfuse = 'langfuse', opik = 'opik', + weave = 'weave', } export type LangSmithConfig = { @@ -22,3 +23,11 @@ export type OpikConfig = { workspace: string url: string } + +export type WeaveConfig = { + api_key: string + entity: string + project: string + endpoint: string + host: string +} diff --git a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/style.module.css b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/style.module.css index 16392a5b4b..45c7d197b4 100644 --- a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/style.module.css +++ b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/style.module.css @@ -3,4 +3,4 @@ height: 0; border-radius: 16px 16px 0px 0px; box-shadow: 0px 0px 5px rgba(0, 0, 0, 0.05), 0px 0px 2px -1px rgba(0, 0, 0, 0.03); -} \ No newline at end of file +} diff --git a/web/app/(commonLayout)/app/(appDetailLayout)/layout.tsx b/web/app/(commonLayout)/app/(appDetailLayout)/layout.tsx index dda198fc89..126bf45842 100644 --- a/web/app/(commonLayout)/app/(appDetailLayout)/layout.tsx +++ b/web/app/(commonLayout)/app/(appDetailLayout)/layout.tsx @@ -2,7 +2,9 @@ import type { FC } from 'react' import React, { useEffect } from 'react' import { useRouter } from 'next/navigation' +import { useTranslation } from 'react-i18next' import { useAppContext } from '@/context/app-context' +import useDocumentTitle from '@/hooks/use-document-title' export type IAppDetail = { children: React.ReactNode @@ -11,12 +13,13 @@ export type IAppDetail = { const AppDetail: FC<IAppDetail> = ({ children }) => { const router = useRouter() const { isCurrentWorkspaceDatasetOperator } = useAppContext() + const { t } = useTranslation() + useDocumentTitle(t('common.menus.appDetail')) useEffect(() => { if (isCurrentWorkspaceDatasetOperator) return router.replace('/datasets') - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [isCurrentWorkspaceDatasetOperator]) + }, [isCurrentWorkspaceDatasetOperator, router]) return ( <> diff --git a/web/app/(commonLayout)/apps/AppCard.tsx b/web/app/(commonLayout)/apps/AppCard.tsx index 3f8c180c1a..31b9ed87c2 100644 --- a/web/app/(commonLayout)/apps/AppCard.tsx +++ b/web/app/(commonLayout)/apps/AppCard.tsx @@ -2,9 +2,10 @@ import { useContext, useContextSelector } from 'use-context-selector' import { useRouter } from 'next/navigation' -import { useCallback, useEffect, useState } from 'react' +import { useCallback, useEffect, useMemo, useState } from 'react' import { useTranslation } from 'react-i18next' -import { RiMoreFill } from '@remixicon/react' +import { RiBuildingLine, RiGlobalLine, RiLockLine, RiMoreFill, RiVerifiedBadgeLine } from '@remixicon/react' +import cn from '@/utils/classnames' import type { App } from '@/types/app' import Confirm from '@/app/components/base/confirm' import Toast, { ToastContext } from '@/app/components/base/toast' @@ -30,7 +31,11 @@ import DSLExportConfirmModal from '@/app/components/workflow/dsl-export-confirm- import { fetchWorkflowDraft } from '@/service/workflow' import { fetchInstalledAppList } from '@/service/explore' import { AppTypeIcon } from '@/app/components/app/type-selector' -import cn from '@/utils/classnames' +import Tooltip from '@/app/components/base/tooltip' +import AccessControl from '@/app/components/app/app-access-control' +import { AccessMode } from '@/models/access-control' +import { useGlobalPublicStore } from '@/context/global-public-context' +import { formatTime } from '@/utils/time' export type AppCardProps = { app: App @@ -40,6 +45,7 @@ export type AppCardProps = { const AppCard = ({ app, onRefresh }: AppCardProps) => { const { t } = useTranslation() const { notify } = useContext(ToastContext) + const systemFeatures = useGlobalPublicStore(s => s.systemFeatures) const { isCurrentWorkspaceEditor } = useAppContext() const { onPlanInfoChanged } = useProviderContext() const { push } = useRouter() @@ -53,6 +59,7 @@ const AppCard = ({ app, onRefresh }: AppCardProps) => { const [showDuplicateModal, setShowDuplicateModal] = useState(false) const [showSwitchModal, setShowSwitchModal] = useState<boolean>(false) const [showConfirmDelete, setShowConfirmDelete] = useState(false) + const [showAccessControl, setShowAccessControl] = useState(false) const [secretEnvList, setSecretEnvList] = useState<EnvironmentVariable[]>([]) const onConfirmDelete = useCallback(async () => { @@ -71,8 +78,7 @@ const AppCard = ({ app, onRefresh }: AppCardProps) => { }) } setShowConfirmDelete(false) - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [app.id]) + }, [app.id, mutateApps, notify, onPlanInfoChanged, onRefresh, t]) const onEdit: CreateAppModalProps['onConfirm'] = useCallback(async ({ name, @@ -176,6 +182,13 @@ const AppCard = ({ app, onRefresh }: AppCardProps) => { setShowSwitchModal(false) } + const onUpdateAccessControl = useCallback(() => { + if (onRefresh) + onRefresh() + mutateApps() + setShowAccessControl(false) + }, [onRefresh, mutateApps, setShowAccessControl]) + const Operations = (props: HtmlContentProps) => { const onMouseLeave = async () => { props.onClose?.() @@ -198,18 +211,24 @@ const AppCard = ({ app, onRefresh }: AppCardProps) => { e.preventDefault() exportCheck() } - const onClickSwitch = async (e: React.MouseEvent<HTMLDivElement>) => { + const onClickSwitch = async (e: React.MouseEvent<HTMLButtonElement>) => { e.stopPropagation() props.onClick?.() e.preventDefault() setShowSwitchModal(true) } - const onClickDelete = async (e: React.MouseEvent<HTMLDivElement>) => { + const onClickDelete = async (e: React.MouseEvent<HTMLButtonElement>) => { e.stopPropagation() props.onClick?.() e.preventDefault() setShowConfirmDelete(true) } + const onClickAccessControl = async (e: React.MouseEvent<HTMLButtonElement>) => { + e.stopPropagation() + props.onClick?.() + e.preventDefault() + setShowAccessControl(true) + } const onClickInstalledApp = async (e: React.MouseEvent<HTMLButtonElement>) => { e.stopPropagation() props.onClick?.() @@ -226,41 +245,49 @@ const AppCard = ({ app, onRefresh }: AppCardProps) => { } } return ( - <div className="relative w-full py-1" onMouseLeave={onMouseLeave}> - <button className='mx-1 flex h-8 w-[calc(100%_-_8px)] cursor-pointer items-center gap-2 rounded-lg px-3 py-[6px] hover:bg-state-base-hover' onClick={onClickSettings}> + <div className="relative flex w-full flex-col py-1" onMouseLeave={onMouseLeave}> + <button className='mx-1 flex h-8 cursor-pointer items-center gap-2 rounded-lg px-3 hover:bg-state-base-hover' onClick={onClickSettings}> <span className='system-sm-regular text-text-secondary'>{t('app.editApp')}</span> </button> - <Divider className="!my-1" /> - <button className='mx-1 flex h-8 w-[calc(100%_-_8px)] cursor-pointer items-center gap-2 rounded-lg px-3 py-[6px] hover:bg-state-base-hover' onClick={onClickDuplicate}> + <Divider className="my-1" /> + <button className='mx-1 flex h-8 cursor-pointer items-center gap-2 rounded-lg px-3 hover:bg-state-base-hover' onClick={onClickDuplicate}> <span className='system-sm-regular text-text-secondary'>{t('app.duplicate')}</span> </button> - <button className='mx-1 flex h-8 w-[calc(100%_-_8px)] cursor-pointer items-center gap-2 rounded-lg px-3 py-[6px] hover:bg-state-base-hover' onClick={onClickExport}> + <button className='mx-1 flex h-8 cursor-pointer items-center gap-2 rounded-lg px-3 hover:bg-state-base-hover' onClick={onClickExport}> <span className='system-sm-regular text-text-secondary'>{t('app.export')}</span> </button> {(app.mode === 'completion' || app.mode === 'chat') && ( <> - <Divider className="!my-1" /> - <div - className='mx-1 flex h-9 cursor-pointer items-center rounded-lg px-3 py-2 hover:bg-state-base-hover' + <Divider className="my-1" /> + <button + className='mx-1 flex h-8 cursor-pointer items-center rounded-lg px-3 hover:bg-state-base-hover' onClick={onClickSwitch} > <span className='text-sm leading-5 text-text-secondary'>{t('app.switch')}</span> - </div> + </button> </> )} - <Divider className="!my-1" /> - <button className='mx-1 flex h-8 w-[calc(100%_-_8px)] cursor-pointer items-center gap-2 rounded-lg px-3 py-[6px] hover:bg-state-base-hover' onClick={onClickInstalledApp}> + <Divider className="my-1" /> + <button className='mx-1 flex h-8 cursor-pointer items-center gap-2 rounded-lg px-3 hover:bg-state-base-hover' onClick={onClickInstalledApp}> <span className='system-sm-regular text-text-secondary'>{t('app.openInExplore')}</span> </button> - <Divider className="!my-1" /> - <div - className='group mx-1 flex h-8 w-[calc(100%_-_8px)] cursor-pointer items-center gap-2 rounded-lg px-3 py-[6px] hover:bg-state-destructive-hover' + <Divider className="my-1" /> + { + systemFeatures.webapp_auth.enabled && isCurrentWorkspaceEditor && <> + <button className='mx-1 flex h-8 cursor-pointer items-center rounded-lg px-3 hover:bg-state-base-hover' onClick={onClickAccessControl}> + <span className='text-sm leading-5 text-text-secondary'>{t('app.accessControl')}</span> + </button> + <Divider className='my-1' /> + </> + } + <button + className='group mx-1 flex h-8 cursor-pointer items-center gap-2 rounded-lg px-3 py-[6px] hover:bg-state-destructive-hover' onClick={onClickDelete} > <span className='system-sm-regular text-text-secondary group-hover:text-text-destructive'> {t('common.operation.delete')} </span> - </div> + </button> </div> ) } @@ -270,6 +297,15 @@ const AppCard = ({ app, onRefresh }: AppCardProps) => { setTags(app.tags) }, [app.tags]) + const EditTimeText = useMemo(() => { + const timeText = formatTime({ + date: (app.updated_at || app.created_at) * 1000, + dateFormat: 'MM/DD/YYYY h:mm', + }) + return `${t('datasetDocuments.segment.editedAt')} ${timeText}` + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [app.updated_at, app.created_at]) + return ( <> <div @@ -294,14 +330,26 @@ const AppCard = ({ app, onRefresh }: AppCardProps) => { <div className='flex items-center text-sm font-semibold leading-5 text-text-secondary'> <div className='truncate' title={app.name}>{app.name}</div> </div> - <div className='flex items-center text-[10px] font-medium leading-[18px] text-text-tertiary'> - {app.mode === 'advanced-chat' && <div className='truncate'>{t('app.types.advanced').toUpperCase()}</div>} - {app.mode === 'chat' && <div className='truncate'>{t('app.types.chatbot').toUpperCase()}</div>} - {app.mode === 'agent-chat' && <div className='truncate'>{t('app.types.agent').toUpperCase()}</div>} - {app.mode === 'workflow' && <div className='truncate'>{t('app.types.workflow').toUpperCase()}</div>} - {app.mode === 'completion' && <div className='truncate'>{t('app.types.completion').toUpperCase()}</div>} + <div className='flex items-center gap-1 text-[10px] font-medium leading-[18px] text-text-tertiary'> + <div className='truncate' title={app.author_name}>{app.author_name}</div> + <div>·</div> + <div className='truncate'>{EditTimeText}</div> </div> </div> + <div className='flex h-5 w-5 shrink-0 items-center justify-center'> + {app.access_mode === AccessMode.PUBLIC && <Tooltip asChild={false} popupContent={t('app.accessItemsDescription.anyone')}> + <RiGlobalLine className='h-4 w-4 text-text-quaternary' /> + </Tooltip>} + {app.access_mode === AccessMode.SPECIFIC_GROUPS_MEMBERS && <Tooltip asChild={false} popupContent={t('app.accessItemsDescription.specific')}> + <RiLockLine className='h-4 w-4 text-text-quaternary' /> + </Tooltip>} + {app.access_mode === AccessMode.ORGANIZATION && <Tooltip asChild={false} popupContent={t('app.accessItemsDescription.organization')}> + <RiBuildingLine className='h-4 w-4 text-text-quaternary' /> + </Tooltip>} + {app.access_mode === AccessMode.EXTERNAL_MEMBERS && <Tooltip asChild={false} popupContent={t('app.accessItemsDescription.external')}> + <RiVerifiedBadgeLine className='h-4 w-4 text-text-quaternary' /> + </Tooltip>} + </div> </div> <div className='title-wrapper h-[90px] px-[14px] text-xs leading-normal text-text-tertiary'> <div @@ -358,7 +406,7 @@ const AppCard = ({ app, onRefresh }: AppCardProps) => { popupClassName={ (app.mode === 'completion' || app.mode === 'chat') ? '!w-[256px] translate-x-[-224px]' - : '!w-[160px] translate-x-[-128px]' + : '!w-[216px] translate-x-[-128px]' } className={'!z-20 h-fit'} /> @@ -419,6 +467,9 @@ const AppCard = ({ app, onRefresh }: AppCardProps) => { onClose={() => setSecretEnvList([])} /> )} + {showAccessControl && ( + <AccessControl app={app} onConfirm={onUpdateAccessControl} onClose={() => setShowAccessControl(false)} /> + )} </> ) } diff --git a/web/app/(commonLayout)/apps/Apps.tsx b/web/app/(commonLayout)/apps/Apps.tsx index 1375f4dfd6..d0cc7ff91f 100644 --- a/web/app/(commonLayout)/apps/Apps.tsx +++ b/web/app/(commonLayout)/apps/Apps.tsx @@ -88,15 +88,14 @@ const Apps = () => { const anchorRef = useRef<HTMLDivElement>(null) const options = [ { value: 'all', text: t('app.types.all'), icon: <RiApps2Line className='mr-1 h-[14px] w-[14px]' /> }, + { value: 'workflow', text: t('app.types.workflow'), icon: <RiExchange2Line className='mr-1 h-[14px] w-[14px]' /> }, + { value: 'advanced-chat', text: t('app.types.advanced'), icon: <RiMessage3Line className='mr-1 h-[14px] w-[14px]' /> }, { value: 'chat', text: t('app.types.chatbot'), icon: <RiMessage3Line className='mr-1 h-[14px] w-[14px]' /> }, { value: 'agent-chat', text: t('app.types.agent'), icon: <RiRobot3Line className='mr-1 h-[14px] w-[14px]' /> }, { value: 'completion', text: t('app.types.completion'), icon: <RiFile4Line className='mr-1 h-[14px] w-[14px]' /> }, - { value: 'advanced-chat', text: t('app.types.advanced'), icon: <RiMessage3Line className='mr-1 h-[14px] w-[14px]' /> }, - { value: 'workflow', text: t('app.types.workflow'), icon: <RiExchange2Line className='mr-1 h-[14px] w-[14px]' /> }, ] useEffect(() => { - document.title = `${t('common.menus.apps')} - Dify` if (localStorage.getItem(NEED_REFRESH_APP_LIST_KEY) === '1') { localStorage.removeItem(NEED_REFRESH_APP_LIST_KEY) mutate() diff --git a/web/app/(commonLayout)/apps/layout.tsx b/web/app/(commonLayout)/apps/layout.tsx new file mode 100644 index 0000000000..10d04a4188 --- /dev/null +++ b/web/app/(commonLayout)/apps/layout.tsx @@ -0,0 +1,12 @@ +'use client' + +import useDocumentTitle from '@/hooks/use-document-title' +import { useTranslation } from 'react-i18next' + +export default function DatasetsLayout({ children }: { children: React.ReactNode }) { + const { t } = useTranslation() + useDocumentTitle(t('common.menus.apps')) + return (<> + {children} + </>) +} diff --git a/web/app/(commonLayout)/apps/page.tsx b/web/app/(commonLayout)/apps/page.tsx index 4a146d9b65..3f617d41c9 100644 --- a/web/app/(commonLayout)/apps/page.tsx +++ b/web/app/(commonLayout)/apps/page.tsx @@ -1,24 +1,20 @@ 'use client' -import { useContextSelector } from 'use-context-selector' import { useTranslation } from 'react-i18next' import { RiDiscordFill, RiGithubFill } from '@remixicon/react' import Link from 'next/link' import style from '../list.module.css' import Apps from './Apps' -import AppContext from '@/context/app-context' -import { LicenseStatus } from '@/types/feature' import { useEducationInit } from '@/app/education-apply/hooks' +import { useGlobalPublicStore } from '@/context/global-public-context' const AppList = () => { const { t } = useTranslation() useEducationInit() - - const systemFeatures = useContextSelector(AppContext, v => v.systemFeatures) - + const { systemFeatures } = useGlobalPublicStore() return ( <div className='relative flex h-0 shrink-0 grow flex-col overflow-y-auto bg-background-body'> <Apps /> - {systemFeatures.license.status === LicenseStatus.NONE && <footer className='shrink-0 grow-0 px-12 py-6'> + {!systemFeatures.branding.enabled && <footer className='shrink-0 grow-0 px-12 py-6'> <h3 className='text-gradient text-xl font-semibold leading-tight'>{t('app.join')}</h3> <p className='system-sm-regular mt-1 text-text-tertiary'>{t('app.communityIntro')}</p> <div className='mt-3 flex items-center gap-2'> diff --git a/web/app/(commonLayout)/datasets/(datasetDetailLayout)/[datasetId]/layout-main.tsx b/web/app/(commonLayout)/datasets/(datasetDetailLayout)/[datasetId]/layout-main.tsx index 078c7ebd8c..fb3a9087ca 100644 --- a/web/app/(commonLayout)/datasets/(datasetDetailLayout)/[datasetId]/layout-main.tsx +++ b/web/app/(commonLayout)/datasets/(datasetDetailLayout)/[datasetId]/layout-main.tsx @@ -25,12 +25,12 @@ import Loading from '@/app/components/base/loading' import DatasetDetailContext from '@/context/dataset-detail' import { DataSourceType } from '@/models/datasets' import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints' -import { LanguagesSupported } from '@/i18n/language' import { useStore } from '@/app/components/app/store' -import { getLocaleOnClient } from '@/i18n' +import { useDocLink } from '@/context/i18n' import { useAppContext } from '@/context/app-context' import Tooltip from '@/app/components/base/tooltip' import LinkedAppsPanel from '@/app/components/base/linked-apps-panel' +import useDocumentTitle from '@/hooks/use-document-title' export type IAppDetailLayoutProps = { children: React.ReactNode @@ -44,9 +44,9 @@ type IExtraInfoProps = { } const ExtraInfo = ({ isMobile, relatedApps, expand }: IExtraInfoProps) => { - const locale = getLocaleOnClient() const [isShowTips, { toggle: toggleTips, set: setShowTips }] = useBoolean(!isMobile) const { t } = useTranslation() + const docLink = useDocLink() const hasRelatedApps = relatedApps?.data && relatedApps?.data?.length > 0 const relatedAppsTotal = relatedApps?.data?.length || 0 @@ -96,11 +96,7 @@ const ExtraInfo = ({ isMobile, relatedApps, expand }: IExtraInfoProps) => { <div className='my-2 text-xs text-text-tertiary'>{t('common.datasetMenus.emptyTip')}</div> <a className='mt-2 inline-flex cursor-pointer items-center text-xs text-text-accent' - href={ - locale === LanguagesSupported[1] - ? 'https://docs.dify.ai/v/zh-hans/guides/knowledge-base/integrate-knowledge-within-application' - : 'https://docs.dify.ai/guides/knowledge-base/integrate-knowledge-within-application' - } + href={docLink('/guides/knowledge-base/integrate-knowledge-within-application')} target='_blank' rel='noopener noreferrer' > <RiBookOpenLine className='mr-1 text-text-accent' /> @@ -158,10 +154,7 @@ const DatasetDetailLayout: FC<IAppDetailLayoutProps> = (props) => { return baseNavigation }, [datasetRes?.provider, datasetId, t]) - useEffect(() => { - if (datasetRes) - document.title = `${datasetRes.name || 'Dataset'} - Dify` - }, [datasetRes]) + useDocumentTitle(datasetRes?.name || t('common.menus.datasets')) const setAppSiderbarExpand = useStore(state => state.setAppSiderbarExpand) diff --git a/web/app/(commonLayout)/datasets/Container.tsx b/web/app/(commonLayout)/datasets/Container.tsx index 771fa7c3f9..112b6a752e 100644 --- a/web/app/(commonLayout)/datasets/Container.tsx +++ b/web/app/(commonLayout)/datasets/Container.tsx @@ -29,16 +29,18 @@ import { useTabSearchParams } from '@/hooks/use-tab-searchparams' import { useStore as useTagStore } from '@/app/components/base/tag-management/store' import { useAppContext } from '@/context/app-context' import { useExternalApiPanel } from '@/context/external-api-panel-context' +import { useGlobalPublicStore } from '@/context/global-public-context' +import useDocumentTitle from '@/hooks/use-document-title' const Container = () => { const { t } = useTranslation() + const { systemFeatures } = useGlobalPublicStore() const router = useRouter() const { currentWorkspace, isCurrentWorkspaceOwner } = useAppContext() const showTagManagementModal = useTagStore(s => s.showTagManagementModal) const { showExternalApiPanel, setShowExternalApiPanel } = useExternalApiPanel() const [includeAll, { toggle: toggleIncludeAll }] = useBoolean(false) - - document.title = `${t('dataset.knowledge')} - Dify` + useDocumentTitle(t('dataset.knowledge')) const options = useMemo(() => { return [ @@ -85,7 +87,7 @@ const Container = () => { return ( <div ref={containerRef} className='scroll-container relative flex grow flex-col overflow-y-auto bg-background-body'> - <div className='sticky top-0 z-10 flex flex-wrap justify-between gap-y-2 bg-background-body px-12 pb-2 pt-4 leading-[56px]'> + <div className='sticky top-0 z-10 flex h-[80px] shrink-0 flex-wrap items-center justify-between gap-y-2 bg-background-body px-12 pb-2 pt-4 leading-[56px]'> <TabSliderNew value={activeTab} onChange={newActiveTab => setActiveTab(newActiveTab)} @@ -125,7 +127,7 @@ const Container = () => { {activeTab === 'dataset' && ( <> <Datasets containerRef={containerRef} tags={tagIDs} keywords={searchKeywords} includeAll={includeAll} /> - <DatasetFooter /> + {!systemFeatures.branding.enabled && <DatasetFooter />} {showTagManagementModal && ( <TagManagementModal type='knowledge' show={showTagManagementModal} /> )} diff --git a/web/app/(commonLayout)/datasets/DatasetCard.tsx b/web/app/(commonLayout)/datasets/DatasetCard.tsx index e0012b4956..4b40be2c7f 100644 --- a/web/app/(commonLayout)/datasets/DatasetCard.tsx +++ b/web/app/(commonLayout)/datasets/DatasetCard.tsx @@ -111,7 +111,7 @@ const DatasetCard = ({ return ( <> <div - className='group relative col-span-1 flex min-h-[160px] cursor-pointer flex-col rounded-xl border-[0.5px] border-solid border-components-card-border bg-components-card-bg shadow-sm transition-all duration-200 ease-in-out hover:shadow-lg' + className='group relative col-span-1 flex min-h-[171px] cursor-pointer flex-col rounded-xl border-[0.5px] border-solid border-components-card-border bg-components-card-bg shadow-sm transition-all duration-200 ease-in-out hover:shadow-lg' data-disable-nprogress={true} onClick={(e) => { e.preventDefault() diff --git a/web/app/(commonLayout)/datasets/Datasets.tsx b/web/app/(commonLayout)/datasets/Datasets.tsx index 6383513e9e..28461e8617 100644 --- a/web/app/(commonLayout)/datasets/Datasets.tsx +++ b/web/app/(commonLayout)/datasets/Datasets.tsx @@ -3,12 +3,12 @@ import { useCallback, useEffect, useRef } from 'react' import useSWRInfinite from 'swr/infinite' import { debounce } from 'lodash-es' -import { useTranslation } from 'react-i18next' import NewDatasetCard from './NewDatasetCard' import DatasetCard from './DatasetCard' import type { DataSetListResponse, FetchDatasetsParams } from '@/models/datasets' import { fetchDatasets } from '@/service/datasets' import { useAppContext } from '@/context/app-context' +import { useTranslation } from 'react-i18next' const getKey = ( pageIndex: number, @@ -48,6 +48,7 @@ const Datasets = ({ keywords, includeAll, }: Props) => { + const { t } = useTranslation() const { isCurrentWorkspaceEditor } = useAppContext() const { data, isLoading, setSize, mutate } = useSWRInfinite( (pageIndex: number, previousPageData: DataSetListResponse) => getKey(pageIndex, previousPageData, tags, keywords, includeAll), @@ -57,11 +58,8 @@ const Datasets = ({ const loadingStateRef = useRef(false) const anchorRef = useRef<HTMLAnchorElement>(null) - const { t } = useTranslation() - useEffect(() => { loadingStateRef.current = isLoading - document.title = `${t('dataset.knowledge')} - Dify` }, [isLoading, t]) const onScroll = useCallback( @@ -87,7 +85,7 @@ const Datasets = ({ return ( <nav className='grid shrink-0 grow grid-cols-1 content-start gap-4 px-12 pt-2 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4'> - { isCurrentWorkspaceEditor && <NewDatasetCard ref={anchorRef} /> } + {isCurrentWorkspaceEditor && <NewDatasetCard ref={anchorRef} />} {data?.map(({ data: datasets }) => datasets.map(dataset => ( <DatasetCard key={dataset.id} dataset={dataset} onSuccess={mutate} />), ))} diff --git a/web/app/(commonLayout)/datasets/Doc.tsx b/web/app/(commonLayout)/datasets/Doc.tsx index 609144962c..466db96246 100644 --- a/web/app/(commonLayout)/datasets/Doc.tsx +++ b/web/app/(commonLayout)/datasets/Doc.tsx @@ -214,9 +214,7 @@ const Doc = ({ apiBaseUrl }: DocProps) => { </button> )} </div> - <article - className={cn('prose-xl prose mx-1 rounded-t-xl bg-background-default px-4 pt-16 sm:mx-12', theme === Theme.dark && 'dark:prose-invert')} - > + <article className={cn('prose-xl prose mx-1 rounded-t-xl bg-background-default px-4 pt-16 sm:mx-12', theme === Theme.dark && 'prose-invert')}> {Template} </article> </div> diff --git a/web/app/(commonLayout)/datasets/layout.tsx b/web/app/(commonLayout)/datasets/layout.tsx index aecb537aa6..bb514fc348 100644 --- a/web/app/(commonLayout)/datasets/layout.tsx +++ b/web/app/(commonLayout)/datasets/layout.tsx @@ -1,9 +1,23 @@ 'use client' +import Loading from '@/app/components/base/loading' +import { useAppContext } from '@/context/app-context' import { ExternalApiPanelProvider } from '@/context/external-api-panel-context' import { ExternalKnowledgeApiProvider } from '@/context/external-knowledge-api-context' +import { useRouter } from 'next/navigation' +import { useEffect } from 'react' export default function DatasetsLayout({ children }: { children: React.ReactNode }) { + const { isCurrentWorkspaceEditor } = useAppContext() + const router = useRouter() + + useEffect(() => { + if (!isCurrentWorkspaceEditor) + router.replace('/apps') + }, [isCurrentWorkspaceEditor, router]) + + if (!isCurrentWorkspaceEditor) + return <Loading type='app' /> return ( <ExternalKnowledgeApiProvider> <ExternalApiPanelProvider> diff --git a/web/app/(commonLayout)/datasets/page.tsx b/web/app/(commonLayout)/datasets/page.tsx index 678de47c94..60a542f0a2 100644 --- a/web/app/(commonLayout)/datasets/page.tsx +++ b/web/app/(commonLayout)/datasets/page.tsx @@ -1,6 +1,11 @@ +'use client' +import { useTranslation } from 'react-i18next' import Container from './Container' +import useDocumentTitle from '@/hooks/use-document-title' -const AppList = async () => { +const AppList = () => { + const { t } = useTranslation() + useDocumentTitle(t('common.menus.datasets')) return <Container /> } diff --git a/web/app/(commonLayout)/datasets/template/template.en.mdx b/web/app/(commonLayout)/datasets/template/template.en.mdx index 54e08b45d8..4d00b7b2b5 100644 --- a/web/app/(commonLayout)/datasets/template/template.en.mdx +++ b/web/app/(commonLayout)/datasets/template/template.en.mdx @@ -11,7 +11,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty, PropertyInstructi <div> ### Authentication - Service API of Dify authenticates using an `API-Key`. + Service API authenticates using an `API-Key`. It is suggested that developers store the `API-Key` in the backend instead of sharing or storing it in the client side to avoid the leakage of the `API-Key`, which may lead to property loss. @@ -68,7 +68,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty, PropertyInstructi </Property> <Property name='process_rule' type='object' key='process_rule'> Processing rules - - <code>mode</code> (string) Cleaning, segmentation mode, automatic / custom + - <code>mode</code> (string) Cleaning, segmentation mode, automatic / custom / hierarchical - <code>rules</code> (object) Custom rules (in automatic mode, this field is empty) - <code>pre_processing_rules</code> (array[object]) Preprocessing rules - <code>id</code> (string) Unique identifier for the preprocessing rule @@ -203,7 +203,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty, PropertyInstructi - <code>doc_language</code> In Q&A mode, specify the language of the document, for example: <code>English</code>, <code>Chinese</code> - <code>process_rule</code> Processing rules - - <code>mode</code> (string) Cleaning, segmentation mode, automatic / custom + - <code>mode</code> (string) Cleaning, segmentation mode, automatic / custom / hierarchical - <code>rules</code> (object) Custom rules (in automatic mode, this field is empty) - <code>pre_processing_rules</code> (array[object]) Preprocessing rules - <code>id</code> (string) Unique identifier for the preprocessing rule @@ -314,7 +314,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty, PropertyInstructi </Property> <Property name='indexing_technique' type='string' key='indexing_technique'> Index technique (optional) - If this is not set, embedding_model, embedding_provider_name and retrieval_model will be set to null + If this is not set, embedding_model, embedding_model_provider and retrieval_model will be set to null - <code>high_quality</code> High quality - <code>economy</code> Economy </Property> @@ -338,7 +338,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty, PropertyInstructi <Property name='embedding_model' type='str' key='embedding_model'> Embedding model name (optional) </Property> - <Property name='embedding_provider_name' type='str' key='embedding_provider_name'> + <Property name='embedding_model_provider' type='str' key='embedding_model_provider'> Embedding model provider name (optional) </Property> <Property name='retrieval_model' type='object' key='retrieval_model'> @@ -783,7 +783,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty, PropertyInstructi </Property> <Property name='process_rule' type='object' key='process_rule'> Processing rules - - <code>mode</code> (string) Cleaning, segmentation mode, automatic / custom + - <code>mode</code> (string) Cleaning, segmentation mode, automatic / custom / hierarchical - <code>rules</code> (object) Custom rules (in automatic mode, this field is empty) - <code>pre_processing_rules</code> (array[object]) Preprocessing rules - <code>id</code> (string) Unique identifier for the preprocessing rule @@ -885,7 +885,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty, PropertyInstructi </Property> <Property name='process_rule' type='object' key='process_rule'> Processing rules - - <code>mode</code> (string) Cleaning, segmentation mode, automatic / custom + - <code>mode</code> (string) Cleaning, segmentation mode, automatic / custom / hierarchical - <code>rules</code> (object) Custom rules (in automatic mode, this field is empty) - <code>pre_processing_rules</code> (array[object]) Preprocessing rules - <code>id</code> (string) Unique identifier for the preprocessing rule @@ -1040,10 +1040,8 @@ import { Row, Col, Properties, Property, Heading, SubProperty, PropertyInstructi ``` </CodeGroup> <CodeGroup title="Response"> - ```json {{ title: 'Response' }} - { - "result": "success" - } + ```text {{ title: 'Response' }} + 204 No Content ``` </CodeGroup> </Col> @@ -1300,6 +1298,76 @@ import { Row, Col, Properties, Property, Heading, SubProperty, PropertyInstructi <hr className='ml-0 mr-0' /> +<Heading + url='/datasets/{dataset_id}/documents/{document_id}/segments/{segment_id}' + method='GET' + title='Get a Chunk Details in a Document' + name='#view_document_chunk' +/> +<Row> + <Col> + Get details of a specific document segment in the specified knowledge base + + ### Path + <Properties> + <Property name='dataset_id' type='string' key='dataset_id'> + Knowledge Base ID + </Property> + <Property name='document_id' type='string' key='document_id'> + Document ID + </Property> + <Property name='segment_id' type='string' key='segment_id'> + Segment ID + </Property> + </Properties> + </Col> + <Col sticky> + <CodeGroup + title="Request" + tag="GET" + label="/datasets/{dataset_id}/documents/{document_id}/segments/{segment_id}" + targetCode={`curl --location --request GET '${props.apiBaseUrl}/datasets/{dataset_id}/documents/{document_id}/segments/{segment_id}' \\\n--header 'Authorization: Bearer {api_key}'`} + > + ```bash {{ title: 'cURL' }} + curl --location --request GET '${props.apiBaseUrl}/datasets/{dataset_id}/documents/{document_id}/segments/{segment_id}' \ + --header 'Authorization: Bearer {api_key}' + ``` + </CodeGroup> + <CodeGroup title="Response"> + ```json {{ title: 'Response' }} + { + "data": { + "id": "chunk_id", + "position": 2, + "document_id": "document_id", + "content": "Segment content text", + "sign_content": "Signature content text", + "answer": "Answer content (if in Q&A mode)", + "word_count": 470, + "tokens": 382, + "keywords": ["keyword1", "keyword2"], + "index_node_id": "index_node_id", + "index_node_hash": "index_node_hash", + "hit_count": 0, + "enabled": true, + "status": "completed", + "created_by": "creator_id", + "created_at": creation_timestamp, + "updated_at": update_timestamp, + "indexing_at": indexing_timestamp, + "completed_at": completion_timestamp, + "error": null, + "child_chunks": [] + }, + "doc_form": "text_model" + } + ``` + </CodeGroup> + </Col> +</Row> + +<hr className='ml-0 mr-0' /> + <Heading url='/datasets/{dataset_id}/documents/{document_id}/segments/{segment_id}' method='DELETE' @@ -1335,10 +1403,8 @@ import { Row, Col, Properties, Property, Heading, SubProperty, PropertyInstructi ``` </CodeGroup> <CodeGroup title="Response"> - ```json {{ title: 'Response' }} - { - "result": "success" - } + ```text {{ title: 'Response' }} + 204 No Content ``` </CodeGroup> </Col> @@ -1620,10 +1686,8 @@ import { Row, Col, Properties, Property, Heading, SubProperty, PropertyInstructi ``` </CodeGroup> <CodeGroup title="Response"> - ```json {{ title: 'Response' }} - { - "result": "success" - } + ```text {{ title: 'Response' }} + 204 No Content ``` </CodeGroup> </Col> @@ -1777,20 +1841,45 @@ import { Row, Col, Properties, Property, Heading, SubProperty, PropertyInstructi Query keyword </Property> <Property name='retrieval_model' type='object' key='retrieval_model'> - Retrieval model (optional, if not filled, it will be recalled according to the default method) - - <code>search_method</code> (text) Search method: One of the following four keywords is required - - <code>keyword_search</code> Keyword search - - <code>semantic_search</code> Semantic search - - <code>full_text_search</code> Full-text search - - <code>hybrid_search</code> Hybrid search - - <code>reranking_enable</code> (bool) Whether to enable reranking, required if the search mode is semantic_search or hybrid_search (optional) - - <code>reranking_mode</code> (object) Rerank model configuration, required if reranking is enabled - - <code>reranking_provider_name</code> (string) Rerank model provider - - <code>reranking_model_name</code> (string) Rerank model name - - <code>weights</code> (float) Semantic search weight setting in hybrid search mode - - <code>top_k</code> (integer) Number of results to return (optional) - - <code>score_threshold_enabled</code> (bool) Whether to enable score threshold - - <code>score_threshold</code> (float) Score threshold + Retrieval parameters (optional, if not filled, it will be recalled according to the default method) + - <code>search_method</code> (text) Search method: One of the following four keywords is required + - <code>keyword_search</code> Keyword search + - <code>semantic_search</code> Semantic search + - <code>full_text_search</code> Full-text search + - <code>hybrid_search</code> Hybrid search + - <code>reranking_enable</code> (bool) Whether to enable reranking, required if the search mode is semantic_search or hybrid_search (optional) + - <code>reranking_mode</code> (object) Rerank model configuration, required if reranking is enabled + - <code>reranking_provider_name</code> (string) Rerank model provider + - <code>reranking_model_name</code> (string) Rerank model name + - <code>weights</code> (float) Semantic search weight setting in hybrid search mode + - <code>top_k</code> (integer) Number of results to return (optional) + - <code>score_threshold_enabled</code> (bool) Whether to enable score threshold + - <code>score_threshold</code> (float) Score threshold + - <code>metadata_filtering_conditions</code> (object) Metadata filtering conditions + - <code>logical_operator</code> (string) Logical operator: <code>and</code> | <code>or</code> + - <code>conditions</code> (array[object]) Conditions list + - <code>name</code> (string) Metadata field name + - <code>comparison_operator</code> (string) Comparison operator, allowed values: + - String comparison: + - <code>contains</code>: Contains + - <code>not contains</code>: Does not contain + - <code>start with</code>: Starts with + - <code>end with</code>: Ends with + - <code>is</code>: Equals + - <code>is not</code>: Does not equal + - <code>empty</code>: Is empty + - <code>not empty</code>: Is not empty + - Numeric comparison: + - <code>=</code>: Equals + - <code>≠</code>: Does not equal + - <code>></code>: Greater than + - <code>< </code>: Less than + - <code>≥</code>: Greater than or equal + - <code>≤</code>: Less than or equal + - Time comparison: + - <code>before</code>: Before + - <code>after</code>: After + - <code>value</code> (string|number|null) Comparison value </Property> <Property name='external_retrieval_model' type='object' key='external_retrieval_model'> Unused field @@ -1815,7 +1904,17 @@ import { Row, Col, Properties, Property, Heading, SubProperty, PropertyInstructi "weights": null, "top_k": 1, "score_threshold_enabled": false, - "score_threshold": null + "score_threshold": null, + "metadata_filtering_conditions": { + "logical_operator": "and", + "conditions": [ + { + "name": "document_name", + "comparison_operator": "contains", + "value": "test" + } + ] + } } }'`} > @@ -2095,9 +2194,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty, PropertyInstructi label="/datasets/{dataset_id}/documents/metadata" targetCode={`curl --location --request POST '${props.apiBaseUrl}/datasets/{dataset_id}/documents/metadata' \\\n--header 'Authorization: Bearer {api_key}' \\\n--header 'Content-Type: application/json'\\\n--data-raw '{"operation_data": [{"document_id": "document_id", "metadata_list": [{"id": "id", "value": "value", "name": "name"}]}]}'`} > - ```bash {{ title: 'cURL' }} - ``` - </CodeGroup> + ```bash {{ title: 'cURL' }} </CodeGroup> </Col> </Row> @@ -2252,6 +2349,316 @@ import { Row, Col, Properties, Property, Heading, SubProperty, PropertyInstructi </Row> <hr className='ml-0 mr-0' /> +Okay, I will translate the Chinese text in your document while keeping all formatting and code content unchanged. + +<Heading + url='/datasets/tags' + method='POST' + title='Create New Knowledge Base Type Tag' + name='#create_new_knowledge_tag' +/> +<Row> + <Col> + ### Request Body + <Properties> + <Property name='name' type='string'> + (text) New tag name, required, maximum length 50 + </Property> + </Properties> + </Col> + <Col sticky> + <CodeGroup + title="Request" + tag="POST" + label="/datasets/tags" + targetCode={`curl --location --request POST '${props.apiBaseUrl}/datasets/tags' \\\n--header 'Authorization: Bearer {api_key}' \\\n--header 'Content-Type: application/json' \\\n--data-raw '{"name": "testtag1"}'`} + > + ```bash {{ title: 'cURL' }} + curl --location --request POST '${props.apiBaseUrl}/datasets/tags' \ + --header 'Authorization: Bearer {api_key}' \ + --header 'Content-Type: application/json' \ + --data-raw '{"name": "testtag1"}' + ``` + </CodeGroup> + <CodeGroup title="Response"> + ```json {{ title: 'Response' }} + { + "id": "eddb66c2-04a1-4e3a-8cb2-75abd01e12a6", + "name": "testtag1", + "type": "knowledge", + "binding_count": 0 + } + ``` + </CodeGroup> + </Col> +</Row> + + +<hr className='ml-0 mr-0' /> + +<Heading + url='/datasets/tags' + method='GET' + title='Get Knowledge Base Type Tags' + name='#get_knowledge_type_tags' +/> +<Row> + <Col> + ### Request Body + </Col> + <Col sticky> + <CodeGroup + title="Request" + tag="GET" + label="/datasets/tags" + targetCode={`curl --location --request GET '${props.apiBaseUrl}/datasets/tags' \\\n--header 'Authorization: Bearer {api_key}' \\\n--header 'Content-Type: application/json'`} + > + ```bash {{ title: 'cURL' }} + curl --location --request GET '${props.apiBaseUrl}/datasets/tags' \ + --header 'Authorization: Bearer {api_key}' \ + --header 'Content-Type: application/json' + ``` + </CodeGroup> + <CodeGroup title="Response"> + ```json {{ title: 'Response' }} + [ + { + "id": "39d6934c-ed36-463d-b4a7-377fa1503dc0", + "name": "testtag1", + "type": "knowledge", + "binding_count": "0" + }, + ... + ] + ``` + </CodeGroup> + </Col> +</Row> + +<hr className='ml-0 mr-0' /> + +<Heading + url='/datasets/tags' + method='PATCH' + title='Modify Knowledge Base Type Tag Name' + name='#modify_knowledge_tag_name' +/> +<Row> + <Col> + ### Request Body + <Properties> + <Property name='name' type='string'> + (text) Modified tag name, required, maximum length 50 + </Property> + <Property name='tag_id' type='string'> + (text) Tag ID, required + </Property> + </Properties> + </Col> + <Col sticky> + <CodeGroup + title="Request" + tag="PATCH" + label="/datasets/tags" + targetCode={`curl --location --request PATCH '${props.apiBaseUrl}/datasets/tags' \\\n--header 'Authorization: Bearer {api_key}' \\\n--header 'Content-Type: application/json' \\\n--data-raw '{"name": "testtag2", "tag_id": "e1a0a3db-ee34-4e04-842a-81555d5316fd"}`} + > + ```bash {{ title: 'cURL' }} + curl --location --request PATCH '${props.apiBaseUrl}/datasets/tags' \ + --header 'Authorization: Bearer {api_key}' \ + --header 'Content-Type: application/json' \ + --data-raw '{"name": "testtag2", "tag_id": "e1a0a3db-ee34-4e04-842a-81555d5316fd"}' + ``` + </CodeGroup> + <CodeGroup title="Response"> + ```json {{ title: 'Response' }} + { + "id": "eddb66c2-04a1-4e3a-8cb2-75abd01e12a6", + "name": "tag-renamed", + "type": "knowledge", + "binding_count": 0 + } + ``` + </CodeGroup> + </Col> +</Row> + +<hr className='ml-0 mr-0' /> + + +<Heading + url='/datasets/tags' + method='DELETE' + title='Delete Knowledge Base Type Tag' + name='#delete_knowledge_tag' +/> +<Row> + <Col> + ### Request Body + <Properties> + <Property name='tag_id' type='string'> + (text) Tag ID, required + </Property> + </Properties> + </Col> + <Col sticky> + <CodeGroup + title="Request" + tag="DELETE" + label="/datasets/tags" + targetCode={`curl --location --request DELETE '${props.apiBaseUrl}/datasets/tags' \\\n--header 'Authorization: Bearer {api_key}' \\\n--header 'Content-Type: application/json' \\\n--data-raw '{ "tag_id": "e1a0a3db-ee34-4e04-842a-81555d5316fd"}`} + > + ```bash {{ title: 'cURL' }} + curl --location --request DELETE '${props.apiBaseUrl}/datasets/tags' \ + --header 'Authorization: Bearer {api_key}' \ + --header 'Content-Type: application/json' \ + --data-raw '{"tag_id": "e1a0a3db-ee34-4e04-842a-81555d5316fd"}' + ``` + </CodeGroup> + <CodeGroup title="Response"> + ```json {{ title: 'Response' }} + + {"result": "success"} + + ``` + </CodeGroup> + </Col> +</Row> + +<hr className='ml-0 mr-0' /> + +<Heading + url='/datasets/tags/binding' + method='POST' + title='Bind Dataset to Knowledge Base Type Tag' + name='#bind_dataset_to_knowledge_tag' +/> +<Row> + <Col> + ### Request Body + <Properties> + <Property name='tag_ids' type='list'> + (list) List of Tag IDs, required + </Property> + <Property name='target_id' type='string'> + (text) Dataset ID, required + </Property> + </Properties> + </Col> + <Col sticky> + <CodeGroup + title="Request" + tag="POST" + label="/datasets/tags/binding" + targetCode={`curl --location --request POST '${props.apiBaseUrl}/datasets/tags/binding' \\\n--header 'Authorization: Bearer {api_key}' \\\n--header 'Content-Type: application/json' \\\n--data-raw '{"tag_ids": ["65cc29be-d072-4e26-adf4-2f727644da29","1e5348f3-d3ff-42b8-a1b7-0a86d518001a"], "target_id": "a932ea9f-fae1-4b2c-9b65-71c56e2cacd6"}'`} + > + ```bash {{ title: 'cURL' }} + curl --location --request POST '${props.apiBaseUrl}/datasets/tags/binding' \ + --header 'Authorization: Bearer {api_key}' \ + --header 'Content-Type: application/json' \ + --data-raw '{"tag_ids": ["65cc29be-d072-4e26-adf4-2f727644da29","1e5348f3-d3ff-42b8-a1b7-0a86d518001a"], "target_id": "a932ea9f-fae1-4b2c-9b65-71c56e2cacd6"}' + ``` + </CodeGroup> + <CodeGroup title="Response"> + ```json {{ title: 'Response' }} + {"result": "success"} + ``` + </CodeGroup> + </Col> +</Row> + +<hr className='ml-0 mr-0' /> + +<Heading + url='/datasets/tags/unbinding' + method='POST' + title='Unbind Dataset and Knowledge Base Type Tag' + name='#unbind_dataset_and_knowledge_tag' +/> +<Row> + <Col> + ### Request Body + <Properties> + <Property name='tag_id' type='string'> + (text) Tag ID, required + </Property> + <Property name='target_id' type='string'> + (text) Dataset ID, required + </Property> + </Properties> + </Col> + <Col sticky> + <CodeGroup + title="Request" + tag="POST" + label="/datasets/tags/unbinding" + targetCode={`curl --location --request POST '${props.apiBaseUrl}/datasets/tags/unbinding' \\\n--header 'Authorization: Bearer {api_key}' \\\n--header 'Content-Type: application/json' \\\n--data-raw '{"tag_id": "1e5348f3-d3ff-42b8-a1b7-0a86d518001a", "target_id": "a932ea9f-fae1-4b2c-9b65-71c56e2cacd6"}'`} + > + ```bash {{ title: 'cURL' }} + curl --location --request POST '${props.apiBaseUrl}/datasets/tags/unbinding' \ + --header 'Authorization: Bearer {api_key}' \ + --header 'Content-Type: application/json' \ + --data-raw '{"tag_id": "1e5348f3-d3ff-42b8-a1b7-0a86d518001a", "target_id": "a932ea9f-fae1-4b2c-9b65-71c56e2cacd6"}' + ``` + </CodeGroup> + <CodeGroup title="Response"> + ```json {{ title: 'Response' }} + {"result": "success"} + ``` + </CodeGroup> + </Col> +</Row> + + +<hr className='ml-0 mr-0' /> + +<Heading + url='/datasets/<uuid:dataset_id>/tags' + method='POST' + title='Query Tags Bound to a Dataset' + name='#query_dataset_tags' +/> +<Row> + <Col> + ### Path + <Properties> + <Property name='dataset_id' type='string'> + (text) Dataset ID + </Property> + </Properties> + </Col> + <Col sticky> + <CodeGroup + title="Request" + tag="POST" + label="/datasets/<uuid:dataset_id>/tags" + targetCode={`curl --location --request POST '${props.apiBaseUrl}/datasets/<uuid:dataset_id>/tags' \\\n--header 'Authorization: Bearer {api_key}' \\\n--header 'Content-Type: application/json' \\\n`} + > + ```bash {{ title: 'cURL' }} + curl --location --request POST '${props.apiBaseUrl}/datasets/<uuid:dataset_id>/tags' \ + --header 'Authorization: Bearer {api_key}' \ + --header 'Content-Type: application/json' \ + ``` + </CodeGroup> + <CodeGroup title="Response"> + ```json {{ title: 'Response' }} + { + "data": + [ + {"id": "4a601f4f-f8a2-4166-ae7c-58c3b252a524", + "name": "123" + }, + ... + ], + "total": 3 + } + ``` + </CodeGroup> + </Col> +</Row> + + +<hr className='ml-0 mr-0' /> + <Row> <Col> diff --git a/web/app/(commonLayout)/datasets/template/template.ja.mdx b/web/app/(commonLayout)/datasets/template/template.ja.mdx index 6691d902a8..a796b65bae 100644 --- a/web/app/(commonLayout)/datasets/template/template.ja.mdx +++ b/web/app/(commonLayout)/datasets/template/template.ja.mdx @@ -192,15 +192,15 @@ import { Row, Col, Properties, Property, Heading, SubProperty, PropertyInstructi - original_document_id が渡されない場合、新しい操作が実行され、process_rule が必要です。 - <code>indexing_technique</code> インデックスモード - - <code>high_quality</code> 高品質: 埋め込みモデルを使用してベクトルデータベースインデックスを構築 - - <code>economy</code> 経済: キーワードテーブルインデックスの反転インデックスを構築 + - <code>high_quality</code> 高品質:埋め込みモデルを使用してベクトルデータベースインデックスを構築 + - <code>economy</code> 経済:キーワードテーブルインデックスの反転インデックスを構築 - <code>doc_form</code> インデックス化された内容の形式 - <code>text_model</code> テキストドキュメントは直接埋め込まれます; `economy` モードではこの形式がデフォルト - <code>hierarchical_model</code> 親子モード - - <code>qa_model</code> Q&A モード: 分割されたドキュメントの質問と回答ペアを生成し、質問を埋め込みます + - <code>qa_model</code> Q&A モード:分割されたドキュメントの質問と回答ペアを生成し、質問を埋め込みます - - <code>doc_language</code> Q&A モードでは、ドキュメントの言語を指定します。例: <code>English</code>, <code>Chinese</code> + - <code>doc_language</code> Q&A モードでは、ドキュメントの言語を指定します。例:<code>English</code>, <code>Chinese</code> - <code>process_rule</code> 処理ルール - <code>mode</code> (string) クリーニング、セグメンテーションモード、自動 / カスタム @@ -214,7 +214,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty, PropertyInstructi - <code>segmentation</code> (object) セグメンテーションルール - <code>separator</code> カスタムセグメント識別子。現在は 1 つの区切り文字のみ設定可能。デフォルトは \n - <code>max_tokens</code> 最大長 (トークン) デフォルトは 1000 - - <code>parent_mode</code> 親チャンクの検索モード: <code>full-doc</code> 全文検索 / <code>paragraph</code> 段落検索 + - <code>parent_mode</code> 親チャンクの検索モード:<code>full-doc</code> 全文検索 / <code>paragraph</code> 段落検索 - <code>subchunk_segmentation</code> (object) 子チャンクルール - <code>separator</code> セグメンテーション識別子。現在は 1 つの区切り文字のみ許可。デフォルトは <code>***</code> - <code>max_tokens</code> 最大長 (トークン) は親チャンクの長さより短いことを検証する必要があります @@ -324,7 +324,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty, PropertyInstructi - <code>partial_members</code> 一部のメンバー </Property> <Property name='provider' type='string' key='provider'> - プロバイダー (オプション、デフォルト: vendor) + プロバイダー (オプション、デフォルト:vendor) - <code>vendor</code> ベンダー - <code>external</code> 外部ナレッジ </Property> @@ -337,7 +337,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty, PropertyInstructi <Property name='embedding_model' type='str' key='embedding_model'> 埋め込みモデル名(任意) </Property> - <Property name='embedding_provider_name' type='str' key='embedding_provider_name'> + <Property name='embedding_model_provider' type='str' key='embedding_model_provider'> 埋め込みモデルのプロバイダ名(任意) </Property> <Property name='retrieval_model' type='object' key='retrieval_model'> @@ -415,16 +415,16 @@ import { Row, Col, Properties, Property, Heading, SubProperty, PropertyInstructi 検索キーワード、オプション </Property> <Property name='tag_ids' type='array[string]' key='tag_ids'> - タグIDリスト、オプション + タグ ID リスト、オプション </Property> <Property name='page' type='string' key='page'> - ページ番号、オプション、デフォルト1 + ページ番号、オプション、デフォルト 1 </Property> <Property name='limit' type='string' key='limit'> - 返されるアイテム数、オプション、デフォルト20、範囲1-100 + 返されるアイテム数、オプション、デフォルト 20、範囲 1-100 </Property> <Property name='include_all' type='boolean' key='include_all'> - すべてのデータセットを含めるかどうか(所有者のみ有効)、オプション、デフォルトはfalse + すべてのデータセットを含めるかどうか(所有者のみ有効)、オプション、デフォルトは false </Property> </Properties> </Col> @@ -501,7 +501,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty, PropertyInstructi ``` </CodeGroup> <CodeGroup title="レスポンス"> - ```text {{ title: 'Response' }} + ```text {{ title: 'レスポンス' }} 204 No Content ``` </CodeGroup> @@ -797,10 +797,8 @@ import { Row, Col, Properties, Property, Heading, SubProperty, PropertyInstructi ``` </CodeGroup> <CodeGroup title="レスポンス"> - ```json {{ title: 'Response' }} - { - "result": "success" - } + ```text {{ title: 'レスポンス' }} + 204 No Content ``` </CodeGroup> </Col> @@ -1059,6 +1057,75 @@ import { Row, Col, Properties, Property, Heading, SubProperty, PropertyInstructi <Heading url='/datasets/{dataset_id}/documents/{document_id}/segments/{segment_id}' + method='GET' + title='ドキュメントセグメントの詳細を表示' + name='#view_document_segment' +/> +<Row> + <Col> + 指定されたナレッジベース内の特定のドキュメントセグメントの詳細を表示します + + ### パス + <Properties> + <Property name='dataset_id' type='string' key='dataset_id'> + ナレッジベースID + </Property> + <Property name='document_id' type='string' key='document_id'> + ドキュメントID + </Property> + <Property name='segment_id' type='string' key='segment_id'> + セグメントID + </Property> + </Properties> + </Col> + <Col sticky> + <CodeGroup + title="リクエスト" + tag="GET" + label="/datasets/{dataset_id}/documents/{document_id}/segments/{segment_id}" + targetCode={`curl --location --request GET '${props.apiBaseUrl}/datasets/{dataset_id}/documents/{document_id}/segments/{segment_id}' \\\n--header 'Authorization: Bearer {api_key}'`} + > + ```bash {{ title: 'cURL' }} + curl --location --request GET '${props.apiBaseUrl}/datasets/{dataset_id}/documents/{document_id}/segments/{segment_id}' \ + --header 'Authorization: Bearer {api_key}' + ``` + </CodeGroup> + <CodeGroup title="レスポンス"> + ```json {{ title: 'Response' }} + { + "data": { + "id": "セグメントID", + "position": 2, + "document_id": "ドキュメントID", + "content": "セグメント内容テキスト", + "sign_content": "署名内容テキスト", + "answer": "回答内容(Q&Aモードの場合)", + "word_count": 470, + "tokens": 382, + "keywords": ["キーワード1", "キーワード2"], + "index_node_id": "インデックスノードID", + "index_node_hash": "インデックスノードハッシュ", + "hit_count": 0, + "enabled": true, + "status": "completed", + "created_by": "作成者ID", + "created_at": 作成タイムスタンプ, + "updated_at": 更新タイムスタンプ, + "indexing_at": インデックス作成タイムスタンプ, + "completed_at": 完了タイムスタンプ, + "error": null, + "child_chunks": [] + }, + "doc_form": "text_model" + } + ``` + </CodeGroup> + </Col> +</Row> + +<hr className='ml-0 mr-0' /> + +<Heading method='DELETE' title='ドキュメント内のチャンクを削除' name='#delete_segment' @@ -1092,10 +1159,8 @@ import { Row, Col, Properties, Property, Heading, SubProperty, PropertyInstructi ``` </CodeGroup> <CodeGroup title="レスポンス"> - ```json {{ title: 'Response' }} - { - "result": "success" - } + ```text {{ title: 'レスポンス' }} + 204 No Content ``` </CodeGroup> </Col> @@ -1104,7 +1169,6 @@ import { Row, Col, Properties, Property, Heading, SubProperty, PropertyInstructi <hr className='ml-0 mr-0' /> <Heading - url='/datasets/{dataset_id}/documents/{document_id}/segments/{segment_id}' method='POST' title='ドキュメント内のチャンクを更新' name='#update_segment' @@ -1377,10 +1441,8 @@ import { Row, Col, Properties, Property, Heading, SubProperty, PropertyInstructi ``` </CodeGroup> <CodeGroup title="レスポンス"> - ```json {{ title: 'Response' }} - { - "result": "success" - } + ```text {{ title: 'レスポンス' }} + 204 No Content ``` </CodeGroup> </Col> @@ -1534,20 +1596,45 @@ import { Row, Col, Properties, Property, Heading, SubProperty, PropertyInstructi クエリキーワード </Property> <Property name='retrieval_model' type='object' key='retrieval_model'> - 検索モデル (オプション、入力されない場合はデフォルトの方法でリコールされます) - - <code>search_method</code> (text) 検索方法: 以下の 4 つのキーワードのいずれかが必要です - - <code>keyword_search</code> キーワード検索 - - <code>semantic_search</code> セマンティック検索 - - <code>full_text_search</code> 全文検索 - - <code>hybrid_search</code> ハイブリッド検索 - - <code>reranking_enable</code> (bool) 再ランキングを有効にするかどうか、検索モードが semantic_search または hybrid_search の場合に必須 (オプション) - - <code>reranking_mode</code> (object) 再ランキングモデル構成、再ランキングが有効な場合に必須 - - <code>reranking_provider_name</code> (string) 再ランキングモデルプロバイダー - - <code>reranking_model_name</code> (string) 再ランキングモデル名 - - <code>weights</code> (float) ハイブリッド検索モードでのセマンティック検索の重み設定 - - <code>top_k</code> (integer) 返される結果の数 (オプション) - - <code>score_threshold_enabled</code> (bool) スコア閾値を有効にするかどうか - - <code>score_threshold</code> (float) スコア閾値 + 検索パラメータ(オプション、入力されない場合はデフォルトの方法でリコールされます) + - <code>search_method</code> (text) 検索方法: 以下の4つのキーワードのいずれかが必要です + - <code>keyword_search</code> キーワード検索 + - <code>semantic_search</code> セマンティック検索 + - <code>full_text_search</code> 全文検索 + - <code>hybrid_search</code> ハイブリッド検索 + - <code>reranking_enable</code> (bool) 再ランキングを有効にするかどうか、検索モードがsemantic_searchまたはhybrid_searchの場合に必須(オプション) + - <code>reranking_mode</code> (object) 再ランキングモデル構成、再ランキングが有効な場合に必須 + - <code>reranking_provider_name</code> (string) 再ランキングモデルプロバイダー + - <code>reranking_model_name</code> (string) 再ランキングモデル名 + - <code>weights</code> (float) ハイブリッド検索モードでのセマンティック検索の重み設定 + - <code>top_k</code> (integer) 返される結果の数(オプション) + - <code>score_threshold_enabled</code> (bool) スコア閾値を有効にするかどうか + - <code>score_threshold</code> (float) スコア閾値 + - <code>metadata_filtering_conditions</code> (object) メタデータフィルタリング条件 + - <code>logical_operator</code> (string) 論理演算子: <code>and</code> | <code>or</code> + - <code>conditions</code> (array[object]) 条件リスト + - <code>name</code> (string) メタデータフィールド名 + - <code>comparison_operator</code> (string) 比較演算子、許可される値: + - 文字列比較: + - <code>contains</code>: 含む + - <code>not contains</code>: 含まない + - <code>start with</code>: で始まる + - <code>end with</code>: で終わる + - <code>is</code>: 等しい + - <code>is not</code>: 等しくない + - <code>empty</code>: 空 + - <code>not empty</code>: 空でない + - 数値比較: + - <code>=</code>: 等しい + - <code>≠</code>: 等しくない + - <code>></code>: より大きい + - <code>< </code>: より小さい + - <code>≥</code>: 以上 + - <code>≤</code>: 以下 + - 時間比較: + - <code>before</code>: より前 + - <code>after</code>: より後 + - <code>value</code> (string|number|null) 比較値 </Property> <Property name='external_retrieval_model' type='object' key='external_retrieval_model'> 未使用フィールド @@ -1572,7 +1659,17 @@ import { Row, Col, Properties, Property, Heading, SubProperty, PropertyInstructi "weights": null, "top_k": 1, "score_threshold_enabled": false, - "score_threshold": null + "score_threshold": null, + "metadata_filtering_conditions": { + "logical_operator": "and", + "conditions": [ + { + "name": "document_name", + "comparison_operator": "contains", + "value": "test" + } + ] + } } }'`} > @@ -1904,6 +2001,313 @@ import { Row, Col, Properties, Property, Heading, SubProperty, PropertyInstructi </Col> </Row> +<hr className='ml-0 mr-0' /> +<Heading + url='/datasets/tags' + method='POST' + title='ナレッジベースタイプタグの新規作成' + name='#create_new_knowledge_tag' +/> +<Row> + <Col> + ### Request Body + <Properties> + <Property name='name' type='string'> + (text) 新しいタグ名、必須、最大長 50 文字 + </Property> + </Properties> + </Col> + <Col sticky> + <CodeGroup + title="Request" + tag="POST" + label="/datasets/tags" + targetCode={`curl --location --request POST '${props.apiBaseUrl}/datasets/tags' \\\n--header 'Authorization: Bearer {api_key}' \\\n--header 'Content-Type: application/json' \\\n--data-raw '{"name": "testtag1"}'`} + > + ```bash {{ title: 'cURL' }} + curl --location --request POST '${props.apiBaseUrl}/datasets/tags' \ + --header 'Authorization: Bearer {api_key}' \ + --header 'Content-Type: application/json' \ + --data-raw '{"name": "testtag1"}' + ``` + </CodeGroup> + <CodeGroup title="Response"> + ```json {{ title: 'Response' }} + { + "id": "eddb66c2-04a1-4e3a-8cb2-75abd01e12a6", + "name": "testtag1", + "type": "knowledge", + "binding_count": 0 + } + ``` + </CodeGroup> + </Col> +</Row> + + +<hr className='ml-0 mr-0' /> + +<Heading + url='/datasets/tags' + method='GET' + title='ナレッジベースタイプタグの取得' + name='#get_knowledge_type_tags' +/> +<Row> + <Col> + ### Request Body + </Col> + <Col sticky> + <CodeGroup + title="Request" + tag="GET" + label="/datasets/tags" + targetCode={`curl --location --request GET '${props.apiBaseUrl}/datasets/tags' \\\n--header 'Authorization: Bearer {api_key}' \\\n--header 'Content-Type: application/json'`} + > + ```bash {{ title: 'cURL' }} + curl --location --request GET '${props.apiBaseUrl}/datasets/tags' \ + --header 'Authorization: Bearer {api_key}' \ + --header 'Content-Type: application/json' + ``` + </CodeGroup> + <CodeGroup title="Response"> + ```json {{ title: 'Response' }} + [ + { + "id": "39d6934c-ed36-463d-b4a7-377fa1503dc0", + "name": "testtag1", + "type": "knowledge", + "binding_count": "0" + }, + ... + ] + ``` + </CodeGroup> + </Col> +</Row> + +<hr className='ml-0 mr-0' /> + +<Heading + url='/datasets/tags' + method='PATCH' + title='ナレッジベースタイプタグ名の変更' + name='#modify_knowledge_tag_name' +/> +<Row> + <Col> + ### Request Body + <Properties> + <Property name='name' type='string'> + (text) 変更後のタグ名、必須、最大長 50 文字 + </Property> + <Property name='tag_id' type='string'> + (text) タグ ID、必須 + </Property> + </Properties> + </Col> + <Col sticky> + <CodeGroup + title="Request" + tag="PATCH" + label="/datasets/tags" + targetCode={`curl --location --request PATCH '${props.apiBaseUrl}/datasets/tags' \\\n--header 'Authorization: Bearer {api_key}' \\\n--header 'Content-Type: application/json' \\\n--data-raw '{"name": "testtag2", "tag_id": "e1a0a3db-ee34-4e04-842a-81555d5316fd"}`} + > + ```bash {{ title: 'cURL' }} + curl --location --request PATCH '${props.apiBaseUrl}/datasets/tags' \ + --header 'Authorization: Bearer {api_key}' \ + --header 'Content-Type: application/json' \ + --data-raw '{"name": "testtag2", "tag_id": "e1a0a3db-ee34-4e04-842a-81555d5316fd"}' + ``` + </CodeGroup> + <CodeGroup title="Response"> + ```json {{ title: 'Response' }} + { + "id": "eddb66c2-04a1-4e3a-8cb2-75abd01e12a6", + "name": "tag-renamed", + "type": "knowledge", + "binding_count": 0 + } + ``` + </CodeGroup> + </Col> +</Row> + +<hr className='ml-0 mr-0' /> + + +<Heading + url='/datasets/tags' + method='DELETE' + title='ナレッジベースタイプタグの削除' + name='#delete_knowledge_tag' +/> +<Row> + <Col> + ### Request Body + <Properties> + <Property name='tag_id' type='string'> + (text) タグ ID、必須 + </Property> + </Properties> + </Col> + <Col sticky> + <CodeGroup + title="Request" + tag="DELETE" + label="/datasets/tags" + targetCode={`curl --location --request DELETE '${props.apiBaseUrl}/datasets/tags' \\\n--header 'Authorization: Bearer {api_key}' \\\n--header 'Content-Type: application/json' \\\n--data-raw '{ "tag_id": "e1a0a3db-ee34-4e04-842a-81555d5316fd"}`} + > + ```bash {{ title: 'cURL' }} + curl --location --request DELETE '${props.apiBaseUrl}/datasets/tags' \ + --header 'Authorization: Bearer {api_key}' \ + --header 'Content-Type: application/json' \ + --data-raw '{"tag_id": "e1a0a3db-ee34-4e04-842a-81555d5316fd"}' + ``` + </CodeGroup> + <CodeGroup title="Response"> + ```json {{ title: 'Response' }} + + {"result": "success"} + + ``` + </CodeGroup> + </Col> +</Row> + +<hr className='ml-0 mr-0' /> + +<Heading + url='/datasets/tags/binding' + method='POST' + title='ナレッジベースをナレッジベースタイプタグに紐付け' + name='#bind_dataset_to_knowledge_tag' +/> +<Row> + <Col> + ### Request Body + <Properties> + <Property name='tag_ids' type='list'> + (list) タグ ID リスト、必須 + </Property> + <Property name='target_id' type='string'> + (text) ナレッジベース ID、必須 + </Property> + </Properties> + </Col> + <Col sticky> + <CodeGroup + title="Request" + tag="POST" + label="/datasets/tags/binding" + targetCode={`curl --location --request POST '${props.apiBaseUrl}/datasets/tags/binding' \\\n--header 'Authorization: Bearer {api_key}' \\\n--header 'Content-Type: application/json' \\\n--data-raw '{"tag_ids": ["65cc29be-d072-4e26-adf4-2f727644da29","1e5348f3-d3ff-42b8-a1b7-0a86d518001a"], "target_id": "a932ea9f-fae1-4b2c-9b65-71c56e2cacd6"}'`} + > + ```bash {{ title: 'cURL' }} + curl --location --request POST '${props.apiBaseUrl}/datasets/tags/binding' \ + --header 'Authorization: Bearer {api_key}' \ + --header 'Content-Type: application/json' \ + --data-raw '{"tag_ids": ["65cc29be-d072-4e26-adf4-2f727644da29","1e5348f3-d3ff-42b8-a1b7-0a86d518001a"], "target_id": "a932ea9f-fae1-4b2c-9b65-71c56e2cacd6"}' + ``` + </CodeGroup> + <CodeGroup title="Response"> + ```json {{ title: 'Response' }} + {"result": "success"} + ``` + </CodeGroup> + </Col> +</Row> + +<hr className='ml-0 mr-0' /> + +<Heading + url='/datasets/tags/unbinding' + method='POST' + title='ナレッジベースとナレッジベースタイプタグの紐付け解除' + name='#unbind_dataset_and_knowledge_tag' +/> +<Row> + <Col> + ### Request Body + <Properties> + <Property name='tag_id' type='string'> + (text) タグ ID、必須 + </Property> + <Property name='target_id' type='string'> + (text) ナレッジベース ID、必須 + </Property> + </Properties> + </Col> + <Col sticky> + <CodeGroup + title="Request" + tag="POST" + label="/datasets/tags/unbinding" + targetCode={`curl --location --request POST '${props.apiBaseUrl}/datasets/tags/unbinding' \\\n--header 'Authorization: Bearer {api_key}' \\\n--header 'Content-Type: application/json' \\\n--data-raw '{"tag_id": "1e5348f3-d3ff-42b8-a1b7-0a86d518001a", "target_id": "a932ea9f-fae1-4b2c-9b65-71c56e2cacd6"}'`} + > + ```bash {{ title: 'cURL' }} + curl --location --request POST '${props.apiBaseUrl}/datasets/tags/unbinding' \ + --header 'Authorization: Bearer {api_key}' \ + --header 'Content-Type: application/json' \ + --data-raw '{"tag_id": "1e5348f3-d3ff-42b8-a1b7-0a86d518001a", "target_id": "a932ea9f-fae1-4b2c-9b65-71c56e2cacd6"}' + ``` + </CodeGroup> + <CodeGroup title="Response"> + ```json {{ title: 'Response' }} + {"result": "success"} + ``` + </CodeGroup> + </Col> +</Row> + + +<hr className='ml-0 mr-0' /> + +<Heading + url='/datasets/<uuid:dataset_id>/tags' + method='POST' + title='ナレッジベースに紐付けられたタグの照会' + name='#query_dataset_tags' +/> +<Row> + <Col> + ### Path + <Properties> + <Property name='dataset_id' type='string'> + (text) ナレッジベース ID + </Property> + </Properties> + </Col> + <Col sticky> + <CodeGroup + title="Request" + tag="POST" + label="/datasets/<uuid:dataset_id>/tags" + targetCode={`curl --location --request POST '${props.apiBaseUrl}/datasets/<uuid:dataset_id>/tags' \\\n--header 'Authorization: Bearer {api_key}' \\\n--header 'Content-Type: application/json' \\\n`} + > + ```bash {{ title: 'cURL' }} + curl --location --request POST '${props.apiBaseUrl}/datasets/<uuid:dataset_id>/tags' \ + --header 'Authorization: Bearer {api_key}' \ + --header 'Content-Type: application/json' \ + ``` + </CodeGroup> + <CodeGroup title="Response"> + ```json {{ title: 'Response' }} + { + "data": + [ + {"id": "4a601f4f-f8a2-4166-ae7c-58c3b252a524", + "name": "123" + }, + ... + ], + "total": 3 + } + ``` + </CodeGroup> + </Col> +</Row> + + <hr className='ml-0 mr-0' /> <Row> diff --git a/web/app/(commonLayout)/datasets/template/template.zh.mdx b/web/app/(commonLayout)/datasets/template/template.zh.mdx index a8bb7046e6..d121a93df2 100644 --- a/web/app/(commonLayout)/datasets/template/template.zh.mdx +++ b/web/app/(commonLayout)/datasets/template/template.zh.mdx @@ -11,7 +11,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty, PropertyInstructi <div> ### 鉴权 - Dify Service API 使用 `API-Key` 进行鉴权。 + Service API 使用 `API-Key` 进行鉴权。 建议开发者把 `API-Key` 放在后端存储,而非分享或者放在客户端存储,以免 `API-Key` 泄露,导致财产损失。 @@ -69,7 +69,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty, PropertyInstructi </Property> <Property name='process_rule' type='object' key='process_rule'> 处理规则 - - <code>mode</code> (string) 清洗、分段模式 ,automatic 自动 / custom 自定义 + - <code>mode</code> (string) 清洗、分段模式 ,automatic 自动 / custom 自定义 / hierarchical 父子 - <code>rules</code> (object) 自定义规则(自动模式下,该字段为空) - <code>pre_processing_rules</code> (array[object]) 预处理规则 - <code>id</code> (string) 预处理规则的唯一标识符 @@ -207,7 +207,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty, PropertyInstructi - <code>doc_language</code> 在 Q&A 模式下,指定文档的语言,例如:<code>English</code>、<code>Chinese</code> - <code>process_rule</code> 处理规则 - - <code>mode</code> (string) 清洗、分段模式 ,automatic 自动 / custom 自定义 + - <code>mode</code> (string) 清洗、分段模式,automatic 自动 / custom 自定义 / hierarchical 父子 - <code>rules</code> (object) 自定义规则(自动模式下,该字段为空) - <code>pre_processing_rules</code> (array[object]) 预处理规则 - <code>id</code> (string) 预处理规则的唯一标识符 @@ -234,12 +234,12 @@ import { Row, Col, Properties, Property, Heading, SubProperty, PropertyInstructi - <code>hybrid_search</code> 混合检索 - <code>semantic_search</code> 语义检索 - <code>full_text_search</code> 全文检索 - - <code>reranking_enable</code> (bool) 是否开启rerank + - <code>reranking_enable</code> (bool) 是否开启 rerank - <code>reranking_model</code> (object) Rerank 模型配置 - <code>reranking_provider_name</code> (string) Rerank 模型的提供商 - <code>reranking_model_name</code> (string) Rerank 模型的名称 - <code>top_k</code> (int) 召回条数 - - <code>score_threshold_enabled</code> (bool)是否开启召回分数限制 + - <code>score_threshold_enabled</code> (bool) 是否开启召回分数限制 - <code>score_threshold</code> (float) 召回分数限制 </Property> <Property name='embedding_model' type='string' key='embedding_model'> @@ -341,7 +341,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty, PropertyInstructi <Property name='embedding_model' type='str' key='embedding_model'> Embedding 模型名称 </Property> - <Property name='embedding_provider_name' type='str' key='embedding_provider_name'> + <Property name='embedding_model_provider' type='str' key='embedding_model_provider'> Embedding 模型供应商 </Property> <Property name='retrieval_model' type='object' key='retrieval_model'> @@ -350,12 +350,12 @@ import { Row, Col, Properties, Property, Heading, SubProperty, PropertyInstructi - <code>hybrid_search</code> 混合检索 - <code>semantic_search</code> 语义检索 - <code>full_text_search</code> 全文检索 - - <code>reranking_enable</code> (bool) 是否开启rerank + - <code>reranking_enable</code> (bool) 是否开启 rerank - <code>reranking_model</code> (object) Rerank 模型配置 - <code>reranking_provider_name</code> (string) Rerank 模型的提供商 - <code>reranking_model_name</code> (string) Rerank 模型的名称 - <code>top_k</code> (int) 召回条数 - - <code>score_threshold_enabled</code> (bool)是否开启召回分数限制 + - <code>score_threshold_enabled</code> (bool) 是否开启召回分数限制 - <code>score_threshold</code> (float) 召回分数限制 </Property> </Properties> @@ -790,7 +790,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty, PropertyInstructi </Property> <Property name='process_rule' type='object' key='process_rule'> 处理规则(选填) - - <code>mode</code> (string) 清洗、分段模式 ,automatic 自动 / custom 自定义 + - <code>mode</code> (string) 清洗、分段模式 ,automatic 自动 / custom 自定义 / hierarchical 父子 - <code>rules</code> (object) 自定义规则(自动模式下,该字段为空) - <code>pre_processing_rules</code> (array[object]) 预处理规则 - <code>id</code> (string) 预处理规则的唯一标识符 @@ -892,7 +892,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty, PropertyInstructi </Property> <Property name='process_rule' type='object' key='process_rule'> 处理规则(选填) - - <code>mode</code> (string) 清洗、分段模式 ,automatic 自动 / custom 自定义 + - <code>mode</code> (string) 清洗、分段模式 ,automatic 自动 / custom 自定义 / hierarchical 父子 - <code>rules</code> (object) 自定义规则(自动模式下,该字段为空) - <code>pre_processing_rules</code> (array[object]) 预处理规则 - <code>id</code> (string) 预处理规则的唯一标识符 @@ -1047,10 +1047,8 @@ import { Row, Col, Properties, Property, Heading, SubProperty, PropertyInstructi ``` </CodeGroup> <CodeGroup title="Response"> - ```json {{ title: 'Response' }} - { - "result": "success" - } + ```text {{ title: 'Response' }} + 204 No Content ``` </CodeGroup> </Col> @@ -1324,7 +1322,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty, PropertyInstructi 文档 ID </Property> <Property name='segment_id' type='string' key='segment_id'> - 文档分段ID + 文档分段 ID </Property> </Properties> </Col> @@ -1342,9 +1340,77 @@ import { Row, Col, Properties, Property, Heading, SubProperty, PropertyInstructi ``` </CodeGroup> <CodeGroup title="Response"> + ```text {{ title: 'Response' }} + 204 No Content + ``` + </CodeGroup> + </Col> +</Row> + +<hr className='ml-0 mr-0' /> + +<Heading + url='/datasets/{dataset_id}/documents/{document_id}/segments/{segment_id}' + method='GET' + title='查看文档分段详情' + name='#view_document_segment' +/> +<Row> + <Col> + 查看指定知识库中特定文档的分段详情 + + ### Path + <Properties> + <Property name='dataset_id' type='string' key='dataset_id'> + 知识库 ID + </Property> + <Property name='document_id' type='string' key='document_id'> + 文档 ID + </Property> + <Property name='segment_id' type='string' key='segment_id'> + 分段 ID + </Property> + </Properties> + </Col> + <Col sticky> + <CodeGroup + title="Request" + tag="GET" + label="/datasets/{dataset_id}/documents/{document_id}/segments/{segment_id}" + targetCode={`curl --location --request GET '${props.apiBaseUrl}/datasets/{dataset_id}/documents/{document_id}/segments/{segment_id}' \\\n--header 'Authorization: Bearer {api_key}'`} + > + ```bash {{ title: 'cURL' }} + curl --location --request GET '${props.apiBaseUrl}/datasets/{dataset_id}/documents/{document_id}/segments/{segment_id}' \ + --header 'Authorization: Bearer {api_key}' + ``` + </CodeGroup> + <CodeGroup title="Response"> ```json {{ title: 'Response' }} { - "result": "success" + "data": { + "id": "分段唯一ID", + "position": 2, + "document_id": "所属文档ID", + "content": "分段内容文本", + "sign_content": "签名内容文本", + "answer": "答案内容(如果有)", + "word_count": 470, + "tokens": 382, + "keywords": ["关键词1", "关键词2"], + "index_node_id": "索引节点ID", + "index_node_hash": "索引节点哈希值", + "hit_count": 0, + "enabled": true, + "status": "completed", + "created_by": "创建者ID", + "created_at": 创建时间戳, + "updated_at": 更新时间戳, + "indexing_at": 索引时间戳, + "completed_at": 完成时间戳, + "error": null, + "child_chunks": [] + }, + "doc_form": "text_model" } ``` </CodeGroup> @@ -1354,7 +1420,6 @@ import { Row, Col, Properties, Property, Heading, SubProperty, PropertyInstructi <hr className='ml-0 mr-0' /> <Heading - url='/datasets/{dataset_id}/documents/{document_id}/segments/{segment_id}' method='POST' title='更新文档分段' name='#update_segment' @@ -1370,7 +1435,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty, PropertyInstructi 文档 ID </Property> <Property name='segment_id' type='string' key='segment_id'> - 文档分段ID + 文档分段 ID </Property> </Properties> @@ -1628,10 +1693,8 @@ import { Row, Col, Properties, Property, Heading, SubProperty, PropertyInstructi ``` </CodeGroup> <CodeGroup title="Response"> - ```json {{ title: 'Response' }} - { - "result": "success" - } + ```text {{ title: 'Response' }} + 204 No Content ``` </CodeGroup> </Col> @@ -1833,6 +1896,31 @@ import { Row, Col, Properties, Property, Heading, SubProperty, PropertyInstructi - <code>top_k</code> (integer) 返回结果数量,非必填 - <code>score_threshold_enabled</code> (bool) 是否开启 score 阈值 - <code>score_threshold</code> (float) Score 阈值 + - <code>metadata_filtering_conditions</code> (object) 元数据过滤条件 + - <code>logical_operator</code> (string) 逻辑运算符: <code>and</code> | <code>or</code> + - <code>conditions</code> (array[object]) 条件列表 + - <code>name</code> (string) 元数据字段名 + - <code>comparison_operator</code> (string) 比较运算符,可选值: + - 字符串比较: + - <code>contains</code>: 包含 + - <code>not contains</code>: 不包含 + - <code>start with</code>: 以...开头 + - <code>end with</code>: 以...结尾 + - <code>is</code>: 等于 + - <code>is not</code>: 不等于 + - <code>empty</code>: 为空 + - <code>not empty</code>: 不为空 + - 数值比较: + - <code>=</code>: 等于 + - <code>≠</code>: 不等于 + - <code>></code>: 大于 + - <code> < </code>: 小于 + - <code>≥</code>: 大于等于 + - <code>≤</code>: 小于等于 + - 时间比较: + - <code>before</code>: 早于 + - <code>after</code>: 晚于 + - <code>value</code> (string|number|null) 比较值 </Property> <Property name='external_retrieval_model' type='object' key='external_retrieval_model'> 未启用字段 @@ -1857,7 +1945,17 @@ import { Row, Col, Properties, Property, Heading, SubProperty, PropertyInstructi "weights": null, "top_k": 1, "score_threshold_enabled": false, - "score_threshold": null + "score_threshold": null, + "metadata_filtering_conditions": { + "logical_operator": "and", + "conditions": [ + { + "name": "document_name", + "comparison_operator": "contains", + "value": "test" + } + ] + } } }'`} > @@ -2125,7 +2223,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty, PropertyInstructi - <code>document_id</code> (string) 文档 ID - <code>metadata_list</code> (list) 元数据列表 - <code>id</code> (string) 元数据 ID - - <code>type</code> (string) 元数据类型 + - <code>value</code> (string) 元数据值 - <code>name</code> (string) 元数据名称 </Property> </Properties> @@ -2293,6 +2391,314 @@ import { Row, Col, Properties, Property, Heading, SubProperty, PropertyInstructi </Col> </Row> +<hr className='ml-0 mr-0' /> + +<Heading + url='/datasets/tags' + method='POST' + title='新增知识库类型标签' + name='#create_new_knowledge_tag' +/> +<Row> + <Col> + ### Request Body + <Properties> + <Property name='name' type='string'> + (text) 新标签名称,必填,最大长度为 50 + </Property> + </Properties> + </Col> + <Col sticky> + <CodeGroup + title="Request" + tag="POST" + label="/datasets/tags" + targetCode={`curl --location --request POST '${props.apiBaseUrl}/datasets/tags' \\\n--header 'Authorization: Bearer {api_key}' \\\n--header 'Content-Type: application/json' \\\n--data-raw '{"name": "testtag1"}'`} + > + ```bash {{ title: 'cURL' }} + curl --location --request POST '${props.apiBaseUrl}/datasets/tags' \ + --header 'Authorization: Bearer {api_key}' \ + --header 'Content-Type: application/json' \ + --data-raw '{"name": "testtag1"}' + ``` + </CodeGroup> + <CodeGroup title="Response"> + ```json {{ title: 'Response' }} + { + "id": "eddb66c2-04a1-4e3a-8cb2-75abd01e12a6", + "name": "testtag1", + "type": "knowledge", + "binding_count": 0 + } + ``` + </CodeGroup> + </Col> +</Row> + + +<hr className='ml-0 mr-0' /> + +<Heading + url='/datasets/tags' + method='GET' + title='获取知识库类型标签' + name='#get_knowledge_type_tags' +/> +<Row> + <Col> + ### Request Body + </Col> + <Col sticky> + <CodeGroup + title="Request" + tag="GET" + label="/datasets/tags" + targetCode={`curl --location --request GET '${props.apiBaseUrl}/datasets/tags' \\\n--header 'Authorization: Bearer {api_key}' \\\n--header 'Content-Type: application/json'`} + > + ```bash {{ title: 'cURL' }} + curl --location --request GET '${props.apiBaseUrl}/datasets/tags' \ + --header 'Authorization: Bearer {api_key}' \ + --header 'Content-Type: application/json' + ``` + </CodeGroup> + <CodeGroup title="Response"> + ```json {{ title: 'Response' }} + [ + { + "id": "39d6934c-ed36-463d-b4a7-377fa1503dc0", + "name": "testtag1", + "type": "knowledge", + "binding_count": "0" + }, + ... + ] + ``` + </CodeGroup> + </Col> +</Row> + +<hr className='ml-0 mr-0' /> + +<Heading + url='/datasets/tags' + method='PATCH' + title='修改知识库类型标签名称' + name='#modify_knowledge_tag_name' +/> +<Row> + <Col> + ### Request Body + <Properties> + <Property name='name' type='string'> + (text) 修改后的标签名称,必填,最大长度为 50 + </Property> + <Property name='tag_id' type='string'> + (text) 标签 ID,必填 + </Property> + </Properties> + </Col> + <Col sticky> + <CodeGroup + title="Request" + tag="PATCH" + label="/datasets/tags" + targetCode={`curl --location --request PATCH '${props.apiBaseUrl}/datasets/tags' \\\n--header 'Authorization: Bearer {api_key}' \\\n--header 'Content-Type: application/json' \\\n--data-raw '{"name": "testtag2", "tag_id": "e1a0a3db-ee34-4e04-842a-81555d5316fd"}`} + > + ```bash {{ title: 'cURL' }} + curl --location --request PATCH '${props.apiBaseUrl}/datasets/tags' \ + --header 'Authorization: Bearer {api_key}' \ + --header 'Content-Type: application/json' \ + --data-raw '{"name": "testtag2", "tag_id": "e1a0a3db-ee34-4e04-842a-81555d5316fd"}' + ``` + </CodeGroup> + <CodeGroup title="Response"> + ```json {{ title: 'Response' }} + { + "id": "eddb66c2-04a1-4e3a-8cb2-75abd01e12a6", + "name": "tag-renamed", + "type": "knowledge", + "binding_count": 0 + } + ``` + </CodeGroup> + </Col> +</Row> + +<hr className='ml-0 mr-0' /> + + +<Heading + url='/datasets/tags' + method='DELETE' + title='删除知识库类型标签' + name='#delete_knowledge_tag' +/> +<Row> + <Col> + ### Request Body + <Properties> + <Property name='tag_id' type='string'> + (text) 标签 ID,必填 + </Property> + </Properties> + </Col> + <Col sticky> + <CodeGroup + title="Request" + tag="DELETE" + label="/datasets/tags" + targetCode={`curl --location --request DELETE '${props.apiBaseUrl}/datasets/tags' \\\n--header 'Authorization: Bearer {api_key}' \\\n--header 'Content-Type: application/json' \\\n--data-raw '{ "tag_id": "e1a0a3db-ee34-4e04-842a-81555d5316fd"}`} + > + ```bash {{ title: 'cURL' }} + curl --location --request DELETE '${props.apiBaseUrl}/datasets/tags' \ + --header 'Authorization: Bearer {api_key}' \ + --header 'Content-Type: application/json' \ + --data-raw '{"tag_id": "e1a0a3db-ee34-4e04-842a-81555d5316fd"}' + ``` + </CodeGroup> + <CodeGroup title="Response"> + ```json {{ title: 'Response' }} + + {"result": "success"} + + ``` + </CodeGroup> + </Col> +</Row> + +<hr className='ml-0 mr-0' /> + +<Heading + url='/datasets/tags/binding' + method='POST' + title='绑定知识库到知识库类型标签' + name='#bind_dataset_to_knowledge_tag' +/> +<Row> + <Col> + ### Request Body + <Properties> + <Property name='tag_ids' type='list'> + (list) 标签 ID 列表,必填 + </Property> + <Property name='target_id' type='string'> + (text) 知识库 ID,必填 + </Property> + </Properties> + </Col> + <Col sticky> + <CodeGroup + title="Request" + tag="POST" + label="/datasets/tags/binding" + targetCode={`curl --location --request POST '${props.apiBaseUrl}/datasets/tags/binding' \\\n--header 'Authorization: Bearer {api_key}' \\\n--header 'Content-Type: application/json' \\\n--data-raw '{"tag_ids": ["65cc29be-d072-4e26-adf4-2f727644da29","1e5348f3-d3ff-42b8-a1b7-0a86d518001a"], "target_id": "a932ea9f-fae1-4b2c-9b65-71c56e2cacd6"}'`} + > + ```bash {{ title: 'cURL' }} + curl --location --request POST '${props.apiBaseUrl}/datasets/tags/binding' \ + --header 'Authorization: Bearer {api_key}' \ + --header 'Content-Type: application/json' \ + --data-raw '{"tag_ids": ["65cc29be-d072-4e26-adf4-2f727644da29","1e5348f3-d3ff-42b8-a1b7-0a86d518001a"], "target_id": "a932ea9f-fae1-4b2c-9b65-71c56e2cacd6"}' + ``` + </CodeGroup> + <CodeGroup title="Response"> + ```json {{ title: 'Response' }} + {"result": "success"} + ``` + </CodeGroup> + </Col> +</Row> + +<hr className='ml-0 mr-0' /> + +<Heading + url='/datasets/tags/unbinding' + method='POST' + title='解绑知识库和知识库类型标签' + name='#unbind_dataset_and_knowledge_tag' +/> +<Row> + <Col> + ### Request Body + <Properties> + <Property name='tag_id' type='string'> + (text) 标签 ID,必填 + </Property> + <Property name='target_id' type='string'> + (text) 知识库 ID,必填 + </Property> + </Properties> + </Col> + <Col sticky> + <CodeGroup + title="Request" + tag="POST" + label="/datasets/tags/unbinding" + targetCode={`curl --location --request POST '${props.apiBaseUrl}/datasets/tags/unbinding' \\\n--header 'Authorization: Bearer {api_key}' \\\n--header 'Content-Type: application/json' \\\n--data-raw '{"tag_id": "1e5348f3-d3ff-42b8-a1b7-0a86d518001a", "target_id": "a932ea9f-fae1-4b2c-9b65-71c56e2cacd6"}'`} + > + ```bash {{ title: 'cURL' }} + curl --location --request POST '${props.apiBaseUrl}/datasets/tags/unbinding' \ + --header 'Authorization: Bearer {api_key}' \ + --header 'Content-Type: application/json' \ + --data-raw '{"tag_id": "1e5348f3-d3ff-42b8-a1b7-0a86d518001a", "target_id": "a932ea9f-fae1-4b2c-9b65-71c56e2cacd6"}' + ``` + </CodeGroup> + <CodeGroup title="Response"> + ```json {{ title: 'Response' }} + {"result": "success"} + ``` + </CodeGroup> + </Col> +</Row> + + +<hr className='ml-0 mr-0' /> + +<Heading + url='/datasets/<uuid:dataset_id>/tags' + method='POST' + title='查询知识库已绑定的标签' + name='#query_dataset_tags' +/> +<Row> + <Col> + ### Path + <Properties> + <Property name='dataset_id' type='string'> + (text) 知识库 ID + </Property> + </Properties> + </Col> + <Col sticky> + <CodeGroup + title="Request" + tag="POST" + label="/datasets/<uuid:dataset_id>/tags" + targetCode={`curl --location --request POST '${props.apiBaseUrl}/datasets/<uuid:dataset_id>/tags' \\\n--header 'Authorization: Bearer {api_key}' \\\n--header 'Content-Type: application/json' \\\n`} + > + ```bash {{ title: 'cURL' }} + curl --location --request POST '${props.apiBaseUrl}/datasets/<uuid:dataset_id>/tags' \ + --header 'Authorization: Bearer {api_key}' \ + --header 'Content-Type: application/json' \ + ``` + </CodeGroup> + <CodeGroup title="Response"> + ```json {{ title: 'Response' }} + { + "data": + [ + {"id": "4a601f4f-f8a2-4166-ae7c-58c3b252a524", + "name": "123" + }, + ... + ], + "total": 3 + } + ``` + </CodeGroup> + </Col> +</Row> + + <hr className='ml-0 mr-0' /> <Row> diff --git a/web/app/(commonLayout)/explore/layout.tsx b/web/app/(commonLayout)/explore/layout.tsx index 3af7bb1e07..46bd41cb0c 100644 --- a/web/app/(commonLayout)/explore/layout.tsx +++ b/web/app/(commonLayout)/explore/layout.tsx @@ -1,11 +1,13 @@ -import type { FC } from 'react' +'use client' +import type { FC, PropsWithChildren } from 'react' import React from 'react' +import { useTranslation } from 'react-i18next' import ExploreClient from '@/app/components/explore' -export type IAppDetail = { - children: React.ReactNode -} +import useDocumentTitle from '@/hooks/use-document-title' -const AppDetail: FC<IAppDetail> = ({ children }) => { +const ExploreLayout: FC<PropsWithChildren> = ({ children }) => { + const { t } = useTranslation() + useDocumentTitle(t('common.menus.explore')) return ( <ExploreClient> {children} @@ -13,4 +15,4 @@ const AppDetail: FC<IAppDetail> = ({ children }) => { ) } -export default React.memo(AppDetail) +export default React.memo(ExploreLayout) diff --git a/web/app/(commonLayout)/layout.tsx b/web/app/(commonLayout)/layout.tsx index af36d4d961..d07e2a99d9 100644 --- a/web/app/(commonLayout)/layout.tsx +++ b/web/app/(commonLayout)/layout.tsx @@ -30,9 +30,4 @@ const Layout = ({ children }: { children: ReactNode }) => { </> ) } - -export const metadata = { - title: 'Dify', -} - export default Layout diff --git a/web/app/(commonLayout)/list.module.css b/web/app/(commonLayout)/list.module.css index 2fc6469a6d..c4d3aec29f 100644 --- a/web/app/(commonLayout)/list.module.css +++ b/web/app/(commonLayout)/list.module.css @@ -214,4 +214,4 @@ .listItem:hover .unavailable { @apply opacity-100; -} \ No newline at end of file +} diff --git a/web/app/(commonLayout)/tools/page.tsx b/web/app/(commonLayout)/tools/page.tsx index 1b08d54ba3..dc7e189c43 100644 --- a/web/app/(commonLayout)/tools/page.tsx +++ b/web/app/(commonLayout)/tools/page.tsx @@ -1,22 +1,16 @@ 'use client' import type { FC } from 'react' import { useRouter } from 'next/navigation' -import { useTranslation } from 'react-i18next' import React, { useEffect } from 'react' +import { useTranslation } from 'react-i18next' import ToolProviderList from '@/app/components/tools/provider-list' import { useAppContext } from '@/context/app-context' - -const Layout: FC = () => { - const { t } = useTranslation() +import useDocumentTitle from '@/hooks/use-document-title' +const ToolsList: FC = () => { const router = useRouter() const { isCurrentWorkspaceDatasetOperator } = useAppContext() - - useEffect(() => { - if (typeof window !== 'undefined') - document.title = `${t('tools.title')} - Dify` - if (isCurrentWorkspaceDatasetOperator) - return router.replace('/datasets') - }, [isCurrentWorkspaceDatasetOperator, router, t]) + const { t } = useTranslation() + useDocumentTitle(t('common.menus.tools')) useEffect(() => { if (isCurrentWorkspaceDatasetOperator) @@ -25,4 +19,4 @@ const Layout: FC = () => { return <ToolProviderList /> } -export default React.memo(Layout) +export default React.memo(ToolsList) diff --git a/web/app/(shareLayout)/layout.tsx b/web/app/(shareLayout)/layout.tsx index 83adbd3cae..78b8835009 100644 --- a/web/app/(shareLayout)/layout.tsx +++ b/web/app/(shareLayout)/layout.tsx @@ -1,14 +1,48 @@ -import React from 'react' +'use client' +import React, { useEffect, useState } from 'react' import type { FC } from 'react' -import type { Metadata } from 'next' - -export const metadata: Metadata = { - icons: 'data:,', // prevent browser from using default favicon -} +import { usePathname, useSearchParams } from 'next/navigation' +import Loading from '../components/base/loading' +import { useGlobalPublicStore } from '@/context/global-public-context' +import { AccessMode } from '@/models/access-control' +import { getAppAccessModeByAppCode } from '@/service/share' const Layout: FC<{ children: React.ReactNode }> = ({ children }) => { + const isGlobalPending = useGlobalPublicStore(s => s.isGlobalPending) + const setWebAppAccessMode = useGlobalPublicStore(s => s.setWebAppAccessMode) + const systemFeatures = useGlobalPublicStore(s => s.systemFeatures) + const pathname = usePathname() + const searchParams = useSearchParams() + const redirectUrl = searchParams.get('redirect_url') + const [isLoading, setIsLoading] = useState(true) + useEffect(() => { + (async () => { + if (!isGlobalPending && !systemFeatures.webapp_auth.enabled) { + setIsLoading(false) + return + } + + let appCode: string | null = null + if (redirectUrl) + appCode = redirectUrl?.split('/').pop() || null + else + appCode = pathname.split('/').pop() || null + + if (!appCode) + return + setIsLoading(true) + const ret = await getAppAccessModeByAppCode(appCode) + setWebAppAccessMode(ret?.accessMode || AccessMode.PUBLIC) + setIsLoading(false) + })() + }, [pathname, redirectUrl, setWebAppAccessMode, isGlobalPending, systemFeatures.webapp_auth.enabled]) + if (isLoading || isGlobalPending) { + return <div className='flex h-full w-full items-center justify-center'> + <Loading /> + </div> + } return ( <div className="h-full min-w-[300px] pb-[env(safe-area-inset-bottom)]"> {children} diff --git a/web/app/(shareLayout)/webapp-reset-password/check-code/page.tsx b/web/app/(shareLayout)/webapp-reset-password/check-code/page.tsx new file mode 100644 index 0000000000..da754794b1 --- /dev/null +++ b/web/app/(shareLayout)/webapp-reset-password/check-code/page.tsx @@ -0,0 +1,96 @@ +'use client' +import { RiArrowLeftLine, RiMailSendFill } from '@remixicon/react' +import { useTranslation } from 'react-i18next' +import { useState } from 'react' +import { useRouter, useSearchParams } from 'next/navigation' +import { useContext } from 'use-context-selector' +import Countdown from '@/app/components/signin/countdown' +import Button from '@/app/components/base/button' +import Input from '@/app/components/base/input' +import Toast from '@/app/components/base/toast' +import { sendWebAppResetPasswordCode, verifyWebAppResetPasswordCode } from '@/service/common' +import I18NContext from '@/context/i18n' + +export default function CheckCode() { + const { t } = useTranslation() + const router = useRouter() + const searchParams = useSearchParams() + const email = decodeURIComponent(searchParams.get('email') as string) + const token = decodeURIComponent(searchParams.get('token') as string) + const [code, setVerifyCode] = useState('') + const [loading, setIsLoading] = useState(false) + const { locale } = useContext(I18NContext) + + const verify = async () => { + try { + if (!code.trim()) { + Toast.notify({ + type: 'error', + message: t('login.checkCode.emptyCode'), + }) + return + } + if (!/\d{6}/.test(code)) { + Toast.notify({ + type: 'error', + message: t('login.checkCode.invalidCode'), + }) + return + } + setIsLoading(true) + const ret = await verifyWebAppResetPasswordCode({ email, code, token }) + if (ret.is_valid) { + const params = new URLSearchParams(searchParams) + params.set('token', encodeURIComponent(ret.token)) + router.push(`/webapp-reset-password/set-password?${params.toString()}`) + } + } + catch (error) { console.error(error) } + finally { + setIsLoading(false) + } + } + + const resendCode = async () => { + try { + const res = await sendWebAppResetPasswordCode(email, locale) + if (res.result === 'success') { + const params = new URLSearchParams(searchParams) + params.set('token', encodeURIComponent(res.data)) + router.replace(`/webapp-reset-password/check-code?${params.toString()}`) + } + } + catch (error) { console.error(error) } + } + + return <div className='flex flex-col gap-3'> + <div className='inline-flex h-14 w-14 items-center justify-center rounded-2xl border border-components-panel-border-subtle bg-background-default-dodge text-text-accent-light-mode-only shadow-lg'> + <RiMailSendFill className='h-6 w-6 text-2xl' /> + </div> + <div className='pb-4 pt-2'> + <h2 className='title-4xl-semi-bold text-text-primary'>{t('login.checkCode.checkYourEmail')}</h2> + <p className='body-md-regular mt-2 text-text-secondary'> + <span dangerouslySetInnerHTML={{ __html: t('login.checkCode.tips', { email }) as string }}></span> + <br /> + {t('login.checkCode.validTime')} + </p> + </div> + + <form action=""> + <input type='text' className='hidden' /> + <label htmlFor="code" className='system-md-semibold mb-1 text-text-secondary'>{t('login.checkCode.verificationCode')}</label> + <Input value={code} onChange={e => setVerifyCode(e.target.value)} max-length={6} className='mt-1' placeholder={t('login.checkCode.verificationCodePlaceholder') as string} /> + <Button loading={loading} disabled={loading} className='my-3 w-full' variant='primary' onClick={verify}>{t('login.checkCode.verify')}</Button> + <Countdown onResend={resendCode} /> + </form> + <div className='py-2'> + <div className='h-px bg-gradient-to-r from-background-gradient-mask-transparent via-divider-regular to-background-gradient-mask-transparent'></div> + </div> + <div onClick={() => router.back()} className='flex h-9 cursor-pointer items-center justify-center text-text-tertiary'> + <div className='bg-background-default-dimm inline-block rounded-full p-1'> + <RiArrowLeftLine size={12} /> + </div> + <span className='system-xs-regular ml-2'>{t('login.back')}</span> + </div> + </div> +} diff --git a/web/app/(shareLayout)/webapp-reset-password/layout.tsx b/web/app/(shareLayout)/webapp-reset-password/layout.tsx new file mode 100644 index 0000000000..e0ac6b9ad6 --- /dev/null +++ b/web/app/(shareLayout)/webapp-reset-password/layout.tsx @@ -0,0 +1,30 @@ +'use client' +import Header from '@/app/signin/_header' + +import cn from '@/utils/classnames' +import { useGlobalPublicStore } from '@/context/global-public-context' + +export default function SignInLayout({ children }: any) { + const { systemFeatures } = useGlobalPublicStore() + return <> + <div className={cn('flex min-h-screen w-full justify-center bg-background-default-burn p-6')}> + <div className={cn('flex w-full shrink-0 flex-col rounded-2xl border border-effects-highlight bg-background-default-subtle')}> + <Header /> + <div className={ + cn( + 'flex w-full grow flex-col items-center justify-center', + 'px-6', + 'md:px-[108px]', + ) + }> + <div className='flex w-[400px] flex-col'> + {children} + </div> + </div> + {!systemFeatures.branding.enabled && <div className='system-xs-regular px-8 py-6 text-text-tertiary'> + © {new Date().getFullYear()} LangGenius, Inc. All rights reserved. + </div>} + </div> + </div> + </> +} diff --git a/web/app/(shareLayout)/webapp-reset-password/page.tsx b/web/app/(shareLayout)/webapp-reset-password/page.tsx new file mode 100644 index 0000000000..96cd4c5805 --- /dev/null +++ b/web/app/(shareLayout)/webapp-reset-password/page.tsx @@ -0,0 +1,104 @@ +'use client' +import Link from 'next/link' +import { RiArrowLeftLine, RiLockPasswordLine } from '@remixicon/react' +import { useTranslation } from 'react-i18next' +import { useState } from 'react' +import { useRouter, useSearchParams } from 'next/navigation' +import { useContext } from 'use-context-selector' +import { COUNT_DOWN_KEY, COUNT_DOWN_TIME_MS } from '@/app/components/signin/countdown' +import { emailRegex } from '@/config' +import Button from '@/app/components/base/button' +import Input from '@/app/components/base/input' +import Toast from '@/app/components/base/toast' +import { sendResetPasswordCode } from '@/service/common' +import I18NContext from '@/context/i18n' +import { noop } from 'lodash-es' +import useDocumentTitle from '@/hooks/use-document-title' + +export default function CheckCode() { + const { t } = useTranslation() + useDocumentTitle('') + const searchParams = useSearchParams() + const router = useRouter() + const [email, setEmail] = useState('') + const [loading, setIsLoading] = useState(false) + const { locale } = useContext(I18NContext) + + const handleGetEMailVerificationCode = async () => { + try { + if (!email) { + Toast.notify({ type: 'error', message: t('login.error.emailEmpty') }) + return + } + + if (!emailRegex.test(email)) { + Toast.notify({ + type: 'error', + message: t('login.error.emailInValid'), + }) + return + } + setIsLoading(true) + const res = await sendResetPasswordCode(email, locale) + if (res.result === 'success') { + localStorage.setItem(COUNT_DOWN_KEY, `${COUNT_DOWN_TIME_MS}`) + const params = new URLSearchParams(searchParams) + params.set('token', encodeURIComponent(res.data)) + params.set('email', encodeURIComponent(email)) + router.push(`/webapp-reset-password/check-code?${params.toString()}`) + } + else if (res.code === 'account_not_found') { + Toast.notify({ + type: 'error', + message: t('login.error.registrationNotAllowed'), + }) + } + else { + Toast.notify({ + type: 'error', + message: res.data, + }) + } + } + catch (error) { + console.error(error) + } + finally { + setIsLoading(false) + } + } + + return <div className='flex flex-col gap-3'> + <div className='inline-flex h-14 w-14 items-center justify-center rounded-2xl border border-components-panel-border-subtle bg-background-default-dodge shadow-lg'> + <RiLockPasswordLine className='h-6 w-6 text-2xl text-text-accent-light-mode-only' /> + </div> + <div className='pb-4 pt-2'> + <h2 className='title-4xl-semi-bold text-text-primary'>{t('login.resetPassword')}</h2> + <p className='body-md-regular mt-2 text-text-secondary'> + {t('login.resetPasswordDesc')} + </p> + </div> + + <form onSubmit={noop}> + <input type='text' className='hidden' /> + <div className='mb-2'> + <label htmlFor="email" className='system-md-semibold my-2 text-text-secondary'>{t('login.email')}</label> + <div className='mt-1'> + <Input id='email' type="email" disabled={loading} value={email} placeholder={t('login.emailPlaceholder') as string} onChange={e => setEmail(e.target.value)} /> + </div> + <div className='mt-3'> + <Button loading={loading} disabled={loading} variant='primary' className='w-full' onClick={handleGetEMailVerificationCode}>{t('login.sendVerificationCode')}</Button> + </div> + </div> + </form> + <div className='py-2'> + <div className='h-px bg-gradient-to-r from-background-gradient-mask-transparent via-divider-regular to-background-gradient-mask-transparent'></div> + </div> + <Link href={`/webapp-signin?${searchParams.toString()}`} className='flex h-9 items-center justify-center text-text-tertiary hover:text-text-primary'> + <div className='inline-block rounded-full bg-background-default-dimmed p-1'> + <RiArrowLeftLine size={12} /> + </div> + <span className='system-xs-regular ml-2'>{t('login.backToLogin')}</span> + </Link> + </div> +} diff --git a/web/app/(shareLayout)/webapp-reset-password/set-password/page.tsx b/web/app/(shareLayout)/webapp-reset-password/set-password/page.tsx new file mode 100644 index 0000000000..9f9a8ad4e3 --- /dev/null +++ b/web/app/(shareLayout)/webapp-reset-password/set-password/page.tsx @@ -0,0 +1,188 @@ +'use client' +import { useCallback, useState } from 'react' +import { useTranslation } from 'react-i18next' +import { useRouter, useSearchParams } from 'next/navigation' +import cn from 'classnames' +import { RiCheckboxCircleFill } from '@remixicon/react' +import { useCountDown } from 'ahooks' +import Button from '@/app/components/base/button' +import { changeWebAppPasswordWithToken } from '@/service/common' +import Toast from '@/app/components/base/toast' +import Input from '@/app/components/base/input' + +const validPassword = /^(?=.*[a-zA-Z])(?=.*\d).{8,}$/ + +const ChangePasswordForm = () => { + const { t } = useTranslation() + const router = useRouter() + const searchParams = useSearchParams() + const token = decodeURIComponent(searchParams.get('token') || '') + + const [password, setPassword] = useState('') + const [confirmPassword, setConfirmPassword] = useState('') + const [showSuccess, setShowSuccess] = useState(false) + const [showPassword, setShowPassword] = useState(false) + const [showConfirmPassword, setShowConfirmPassword] = useState(false) + + const showErrorMessage = useCallback((message: string) => { + Toast.notify({ + type: 'error', + message, + }) + }, []) + + const getSignInUrl = () => { + return `/webapp-signin?redirect_url=${searchParams.get('redirect_url') || ''}` + } + + const AUTO_REDIRECT_TIME = 5000 + const [leftTime, setLeftTime] = useState<number | undefined>(undefined) + const [countdown] = useCountDown({ + leftTime, + onEnd: () => { + router.replace(getSignInUrl()) + }, + }) + + const valid = useCallback(() => { + if (!password.trim()) { + showErrorMessage(t('login.error.passwordEmpty')) + return false + } + if (!validPassword.test(password)) { + showErrorMessage(t('login.error.passwordInvalid')) + return false + } + if (password !== confirmPassword) { + showErrorMessage(t('common.account.notEqual')) + return false + } + return true + }, [password, confirmPassword, showErrorMessage, t]) + + const handleChangePassword = useCallback(async () => { + if (!valid()) + return + try { + await changeWebAppPasswordWithToken({ + url: '/forgot-password/resets', + body: { + token, + new_password: password, + password_confirm: confirmPassword, + }, + }) + setShowSuccess(true) + setLeftTime(AUTO_REDIRECT_TIME) + } + catch (error) { + console.error(error) + } + }, [password, token, valid, confirmPassword]) + + return ( + <div className={ + cn( + 'flex w-full grow flex-col items-center justify-center', + 'px-6', + 'md:px-[108px]', + ) + }> + {!showSuccess && ( + <div className='flex flex-col md:w-[400px]'> + <div className="mx-auto w-full"> + <h2 className="title-4xl-semi-bold text-text-primary"> + {t('login.changePassword')} + </h2> + <p className='body-md-regular mt-2 text-text-secondary'> + {t('login.changePasswordTip')} + </p> + </div> + + <div className="mx-auto mt-6 w-full"> + <div className="bg-white"> + {/* Password */} + <div className='mb-5'> + <label htmlFor="password" className="system-md-semibold my-2 text-text-secondary"> + {t('common.account.newPassword')} + </label> + <div className='relative mt-1'> + <Input + id="password" type={showPassword ? 'text' : 'password'} + value={password} + onChange={e => setPassword(e.target.value)} + placeholder={t('login.passwordPlaceholder') || ''} + /> + + <div className="absolute inset-y-0 right-0 flex items-center"> + <Button + type="button" + variant='ghost' + onClick={() => setShowPassword(!showPassword)} + > + {showPassword ? '👀' : '😝'} + </Button> + </div> + </div> + <div className='body-xs-regular mt-1 text-text-secondary'>{t('login.error.passwordInvalid')}</div> + </div> + {/* Confirm Password */} + <div className='mb-5'> + <label htmlFor="confirmPassword" className="system-md-semibold my-2 text-text-secondary"> + {t('common.account.confirmPassword')} + </label> + <div className='relative mt-1'> + <Input + id="confirmPassword" + type={showConfirmPassword ? 'text' : 'password'} + value={confirmPassword} + onChange={e => setConfirmPassword(e.target.value)} + placeholder={t('login.confirmPasswordPlaceholder') || ''} + /> + <div className="absolute inset-y-0 right-0 flex items-center"> + <Button + type="button" + variant='ghost' + onClick={() => setShowConfirmPassword(!showConfirmPassword)} + > + {showConfirmPassword ? '👀' : '😝'} + </Button> + </div> + </div> + </div> + <div> + <Button + variant='primary' + className='w-full' + onClick={handleChangePassword} + > + {t('login.changePasswordBtn')} + </Button> + </div> + </div> + </div> + </div> + )} + {showSuccess && ( + <div className="flex flex-col md:w-[400px]"> + <div className="mx-auto w-full"> + <div className="mb-3 flex h-14 w-14 items-center justify-center rounded-2xl border border-components-panel-border-subtle font-bold shadow-lg"> + <RiCheckboxCircleFill className='h-6 w-6 text-text-success' /> + </div> + <h2 className="title-4xl-semi-bold text-text-primary"> + {t('login.passwordChangedTip')} + </h2> + </div> + <div className="mx-auto mt-6 w-full"> + <Button variant='primary' className='w-full' onClick={() => { + setLeftTime(undefined) + router.replace(getSignInUrl()) + }}>{t('login.passwordChanged')} ({Math.round(countdown / 1000)}) </Button> + </div> + </div> + )} + </div> + ) +} + +export default ChangePasswordForm diff --git a/web/app/(shareLayout)/webapp-signin/check-code/page.tsx b/web/app/(shareLayout)/webapp-signin/check-code/page.tsx new file mode 100644 index 0000000000..1b8f18c98f --- /dev/null +++ b/web/app/(shareLayout)/webapp-signin/check-code/page.tsx @@ -0,0 +1,115 @@ +'use client' +import { RiArrowLeftLine, RiMailSendFill } from '@remixicon/react' +import { useTranslation } from 'react-i18next' +import { useCallback, useState } from 'react' +import { useRouter, useSearchParams } from 'next/navigation' +import { useContext } from 'use-context-selector' +import Countdown from '@/app/components/signin/countdown' +import Button from '@/app/components/base/button' +import Input from '@/app/components/base/input' +import Toast from '@/app/components/base/toast' +import { sendWebAppEMailLoginCode, webAppEmailLoginWithCode } from '@/service/common' +import I18NContext from '@/context/i18n' +import { setAccessToken } from '@/app/components/share/utils' +import { fetchAccessToken } from '@/service/share' + +export default function CheckCode() { + const { t } = useTranslation() + const router = useRouter() + const searchParams = useSearchParams() + const email = decodeURIComponent(searchParams.get('email') as string) + const token = decodeURIComponent(searchParams.get('token') as string) + const [code, setVerifyCode] = useState('') + const [loading, setIsLoading] = useState(false) + const { locale } = useContext(I18NContext) + const redirectUrl = searchParams.get('redirect_url') + + const getAppCodeFromRedirectUrl = useCallback(() => { + const appCode = redirectUrl?.split('/').pop() + if (!appCode) + return null + + return appCode + }, [redirectUrl]) + + const verify = async () => { + try { + const appCode = getAppCodeFromRedirectUrl() + if (!code.trim()) { + Toast.notify({ + type: 'error', + message: t('login.checkCode.emptyCode'), + }) + return + } + if (!/\d{6}/.test(code)) { + Toast.notify({ + type: 'error', + message: t('login.checkCode.invalidCode'), + }) + return + } + if (!redirectUrl || !appCode) { + Toast.notify({ + type: 'error', + message: t('login.error.redirectUrlMissing'), + }) + return + } + setIsLoading(true) + const ret = await webAppEmailLoginWithCode({ email, code, token }) + if (ret.result === 'success') { + localStorage.setItem('webapp_access_token', ret.data.access_token) + const tokenResp = await fetchAccessToken({ appCode, webAppAccessToken: ret.data.access_token }) + await setAccessToken(appCode, tokenResp.access_token) + router.replace(redirectUrl) + } + } + catch (error) { console.error(error) } + finally { + setIsLoading(false) + } + } + + const resendCode = async () => { + try { + const ret = await sendWebAppEMailLoginCode(email, locale) + if (ret.result === 'success') { + const params = new URLSearchParams(searchParams) + params.set('token', encodeURIComponent(ret.data)) + router.replace(`/webapp-signin/check-code?${params.toString()}`) + } + } + catch (error) { console.error(error) } + } + + return <div className='flex w-[400px] flex-col gap-3'> + <div className='inline-flex h-14 w-14 items-center justify-center rounded-2xl border border-components-panel-border-subtle bg-background-default-dodge shadow-lg'> + <RiMailSendFill className='h-6 w-6 text-2xl text-text-accent-light-mode-only' /> + </div> + <div className='pb-4 pt-2'> + <h2 className='title-4xl-semi-bold text-text-primary'>{t('login.checkCode.checkYourEmail')}</h2> + <p className='body-md-regular mt-2 text-text-secondary'> + <span dangerouslySetInnerHTML={{ __html: t('login.checkCode.tips', { email }) as string }}></span> + <br /> + {t('login.checkCode.validTime')} + </p> + </div> + + <form action=""> + <label htmlFor="code" className='system-md-semibold mb-1 text-text-secondary'>{t('login.checkCode.verificationCode')}</label> + <Input value={code} onChange={e => setVerifyCode(e.target.value)} max-length={6} className='mt-1' placeholder={t('login.checkCode.verificationCodePlaceholder') as string} /> + <Button loading={loading} disabled={loading} className='my-3 w-full' variant='primary' onClick={verify}>{t('login.checkCode.verify')}</Button> + <Countdown onResend={resendCode} /> + </form> + <div className='py-2'> + <div className='h-px bg-gradient-to-r from-background-gradient-mask-transparent via-divider-regular to-background-gradient-mask-transparent'></div> + </div> + <div onClick={() => router.back()} className='flex h-9 cursor-pointer items-center justify-center text-text-tertiary'> + <div className='bg-background-default-dimm inline-block rounded-full p-1'> + <RiArrowLeftLine size={12} /> + </div> + <span className='system-xs-regular ml-2'>{t('login.back')}</span> + </div> + </div> +} diff --git a/web/app/(shareLayout)/webapp-signin/components/external-member-sso-auth.tsx b/web/app/(shareLayout)/webapp-signin/components/external-member-sso-auth.tsx new file mode 100644 index 0000000000..e9b15ae331 --- /dev/null +++ b/web/app/(shareLayout)/webapp-signin/components/external-member-sso-auth.tsx @@ -0,0 +1,80 @@ +'use client' +import { useRouter, useSearchParams } from 'next/navigation' +import React, { useCallback, useEffect } from 'react' +import Toast from '@/app/components/base/toast' +import { fetchWebOAuth2SSOUrl, fetchWebOIDCSSOUrl, fetchWebSAMLSSOUrl } from '@/service/share' +import { useGlobalPublicStore } from '@/context/global-public-context' +import { SSOProtocol } from '@/types/feature' +import Loading from '@/app/components/base/loading' +import AppUnavailable from '@/app/components/base/app-unavailable' + +const ExternalMemberSSOAuth = () => { + const systemFeatures = useGlobalPublicStore(s => s.systemFeatures) + const searchParams = useSearchParams() + const router = useRouter() + + const redirectUrl = searchParams.get('redirect_url') + + const showErrorToast = (message: string) => { + Toast.notify({ + type: 'error', + message, + }) + } + + const getAppCodeFromRedirectUrl = useCallback(() => { + const appCode = redirectUrl?.split('/').pop() + if (!appCode) + return null + + return appCode + }, [redirectUrl]) + + const handleSSOLogin = useCallback(async () => { + const appCode = getAppCodeFromRedirectUrl() + if (!appCode || !redirectUrl) { + showErrorToast('redirect url or app code is invalid.') + return + } + + switch (systemFeatures.webapp_auth.sso_config.protocol) { + case SSOProtocol.SAML: { + const samlRes = await fetchWebSAMLSSOUrl(appCode, redirectUrl) + router.push(samlRes.url) + break + } + case SSOProtocol.OIDC: { + const oidcRes = await fetchWebOIDCSSOUrl(appCode, redirectUrl) + router.push(oidcRes.url) + break + } + case SSOProtocol.OAuth2: { + const oauth2Res = await fetchWebOAuth2SSOUrl(appCode, redirectUrl) + router.push(oauth2Res.url) + break + } + case '': + break + default: + showErrorToast('SSO protocol is not supported.') + } + }, [getAppCodeFromRedirectUrl, redirectUrl, router, systemFeatures.webapp_auth.sso_config.protocol]) + + useEffect(() => { + handleSSOLogin() + }, [handleSSOLogin]) + + if (!systemFeatures.webapp_auth.sso_config.protocol) { + return <div className="flex h-full items-center justify-center"> + <AppUnavailable code={403} unknownReason='sso protocol is invalid.' /> + </div> + } + + return ( + <div className="flex h-full items-center justify-center"> + <Loading /> + </div> + ) +} + +export default React.memo(ExternalMemberSSOAuth) diff --git a/web/app/(shareLayout)/webapp-signin/components/mail-and-code-auth.tsx b/web/app/(shareLayout)/webapp-signin/components/mail-and-code-auth.tsx new file mode 100644 index 0000000000..29af3e3a57 --- /dev/null +++ b/web/app/(shareLayout)/webapp-signin/components/mail-and-code-auth.tsx @@ -0,0 +1,68 @@ +import { useState } from 'react' +import { useTranslation } from 'react-i18next' +import { useRouter, useSearchParams } from 'next/navigation' +import { useContext } from 'use-context-selector' +import Input from '@/app/components/base/input' +import Button from '@/app/components/base/button' +import { emailRegex } from '@/config' +import Toast from '@/app/components/base/toast' +import { sendWebAppEMailLoginCode } from '@/service/common' +import { COUNT_DOWN_KEY, COUNT_DOWN_TIME_MS } from '@/app/components/signin/countdown' +import I18NContext from '@/context/i18n' +import { noop } from 'lodash-es' + +export default function MailAndCodeAuth() { + const { t } = useTranslation() + const router = useRouter() + const searchParams = useSearchParams() + const emailFromLink = decodeURIComponent(searchParams.get('email') || '') + const [email, setEmail] = useState(emailFromLink) + const [loading, setIsLoading] = useState(false) + const { locale } = useContext(I18NContext) + + const handleGetEMailVerificationCode = async () => { + try { + if (!email) { + Toast.notify({ type: 'error', message: t('login.error.emailEmpty') }) + return + } + + if (!emailRegex.test(email)) { + Toast.notify({ + type: 'error', + message: t('login.error.emailInValid'), + }) + return + } + setIsLoading(true) + const ret = await sendWebAppEMailLoginCode(email, locale) + if (ret.result === 'success') { + localStorage.setItem(COUNT_DOWN_KEY, `${COUNT_DOWN_TIME_MS}`) + const params = new URLSearchParams(searchParams) + params.set('email', encodeURIComponent(email)) + params.set('token', encodeURIComponent(ret.data)) + router.push(`/webapp-signin/check-code?${params.toString()}`) + } + } + catch (error) { + console.error(error) + } + finally { + setIsLoading(false) + } + } + + return (<form onSubmit={noop}> + <input type='text' className='hidden' /> + <div className='mb-2'> + <label htmlFor="email" className='system-md-semibold my-2 text-text-secondary'>{t('login.email')}</label> + <div className='mt-1'> + <Input id='email' type="email" value={email} placeholder={t('login.emailPlaceholder') as string} onChange={e => setEmail(e.target.value)} /> + </div> + <div className='mt-3'> + <Button loading={loading} disabled={loading || !email} variant='primary' className='w-full' onClick={handleGetEMailVerificationCode}>{t('login.continueWithCode')}</Button> + </div> + </div> + </form> + ) +} diff --git a/web/app/(shareLayout)/webapp-signin/components/mail-and-password-auth.tsx b/web/app/(shareLayout)/webapp-signin/components/mail-and-password-auth.tsx new file mode 100644 index 0000000000..d9e56af1b8 --- /dev/null +++ b/web/app/(shareLayout)/webapp-signin/components/mail-and-password-auth.tsx @@ -0,0 +1,171 @@ +import Link from 'next/link' +import { useCallback, useState } from 'react' +import { useTranslation } from 'react-i18next' +import { useRouter, useSearchParams } from 'next/navigation' +import { useContext } from 'use-context-selector' +import Button from '@/app/components/base/button' +import Toast from '@/app/components/base/toast' +import { emailRegex } from '@/config' +import { webAppLogin } from '@/service/common' +import Input from '@/app/components/base/input' +import I18NContext from '@/context/i18n' +import { noop } from 'lodash-es' +import { setAccessToken } from '@/app/components/share/utils' +import { fetchAccessToken } from '@/service/share' + +type MailAndPasswordAuthProps = { + isEmailSetup: boolean +} + +const passwordRegex = /^(?=.*[a-zA-Z])(?=.*\d).{8,}$/ + +export default function MailAndPasswordAuth({ isEmailSetup }: MailAndPasswordAuthProps) { + const { t } = useTranslation() + const { locale } = useContext(I18NContext) + const router = useRouter() + const searchParams = useSearchParams() + const [showPassword, setShowPassword] = useState(false) + const emailFromLink = decodeURIComponent(searchParams.get('email') || '') + const [email, setEmail] = useState(emailFromLink) + const [password, setPassword] = useState('') + + const [isLoading, setIsLoading] = useState(false) + const redirectUrl = searchParams.get('redirect_url') + + const getAppCodeFromRedirectUrl = useCallback(() => { + const appCode = redirectUrl?.split('/').pop() + if (!appCode) + return null + + return appCode + }, [redirectUrl]) + const handleEmailPasswordLogin = async () => { + const appCode = getAppCodeFromRedirectUrl() + if (!email) { + Toast.notify({ type: 'error', message: t('login.error.emailEmpty') }) + return + } + if (!emailRegex.test(email)) { + Toast.notify({ + type: 'error', + message: t('login.error.emailInValid'), + }) + return + } + if (!password?.trim()) { + Toast.notify({ type: 'error', message: t('login.error.passwordEmpty') }) + return + } + if (!passwordRegex.test(password)) { + Toast.notify({ + type: 'error', + message: t('login.error.passwordInvalid'), + }) + return + } + if (!redirectUrl || !appCode) { + Toast.notify({ + type: 'error', + message: t('login.error.redirectUrlMissing'), + }) + return + } + try { + setIsLoading(true) + const loginData: Record<string, any> = { + email, + password, + language: locale, + remember_me: true, + } + + const res = await webAppLogin({ + url: '/login', + body: loginData, + }) + if (res.result === 'success') { + localStorage.setItem('webapp_access_token', res.data.access_token) + const tokenResp = await fetchAccessToken({ appCode, webAppAccessToken: res.data.access_token }) + await setAccessToken(appCode, tokenResp.access_token) + router.replace(redirectUrl) + } + else { + Toast.notify({ + type: 'error', + message: res.data, + }) + } + } + + finally { + setIsLoading(false) + } + } + + return <form onSubmit={noop}> + <div className='mb-3'> + <label htmlFor="email" className="system-md-semibold my-2 text-text-secondary"> + {t('login.email')} + </label> + <div className="mt-1"> + <Input + value={email} + onChange={e => setEmail(e.target.value)} + id="email" + type="email" + autoComplete="email" + placeholder={t('login.emailPlaceholder') || ''} + tabIndex={1} + /> + </div> + </div> + + <div className='mb-3'> + <label htmlFor="password" className="my-2 flex items-center justify-between"> + <span className='system-md-semibold text-text-secondary'>{t('login.password')}</span> + <Link + href={`/webapp-reset-password?${searchParams.toString()}`} + className={`system-xs-regular ${isEmailSetup ? 'text-components-button-secondary-accent-text' : 'pointer-events-none text-components-button-secondary-accent-text-disabled'}`} + tabIndex={isEmailSetup ? 0 : -1} + aria-disabled={!isEmailSetup} + > + {t('login.forget')} + </Link> + </label> + <div className="relative mt-1"> + <Input + id="password" + value={password} + onChange={e => setPassword(e.target.value)} + onKeyDown={(e) => { + if (e.key === 'Enter') + handleEmailPasswordLogin() + }} + type={showPassword ? 'text' : 'password'} + autoComplete="current-password" + placeholder={t('login.passwordPlaceholder') || ''} + tabIndex={2} + /> + <div className="absolute inset-y-0 right-0 flex items-center"> + <Button + type="button" + variant='ghost' + onClick={() => setShowPassword(!showPassword)} + > + {showPassword ? '👀' : '😝'} + </Button> + </div> + </div> + </div> + + <div className='mb-2'> + <Button + tabIndex={2} + variant='primary' + onClick={handleEmailPasswordLogin} + disabled={isLoading || !email || !password} + className="w-full" + >{t('login.signBtn')}</Button> + </div> + </form> +} diff --git a/web/app/(shareLayout)/webapp-signin/components/sso-auth.tsx b/web/app/(shareLayout)/webapp-signin/components/sso-auth.tsx new file mode 100644 index 0000000000..5d649322ba --- /dev/null +++ b/web/app/(shareLayout)/webapp-signin/components/sso-auth.tsx @@ -0,0 +1,88 @@ +'use client' +import { useRouter, useSearchParams } from 'next/navigation' +import type { FC } from 'react' +import { useCallback } from 'react' +import { useState } from 'react' +import { useTranslation } from 'react-i18next' +import { Lock01 } from '@/app/components/base/icons/src/vender/solid/security' +import Toast from '@/app/components/base/toast' +import Button from '@/app/components/base/button' +import { SSOProtocol } from '@/types/feature' +import { fetchMembersOAuth2SSOUrl, fetchMembersOIDCSSOUrl, fetchMembersSAMLSSOUrl } from '@/service/share' + +type SSOAuthProps = { + protocol: SSOProtocol | '' +} + +const SSOAuth: FC<SSOAuthProps> = ({ + protocol, +}) => { + const router = useRouter() + const { t } = useTranslation() + const searchParams = useSearchParams() + + const redirectUrl = searchParams.get('redirect_url') + const getAppCodeFromRedirectUrl = useCallback(() => { + const appCode = redirectUrl?.split('/').pop() + if (!appCode) + return null + + return appCode + }, [redirectUrl]) + + const [isLoading, setIsLoading] = useState(false) + + const handleSSOLogin = () => { + const appCode = getAppCodeFromRedirectUrl() + if (!redirectUrl || !appCode) { + Toast.notify({ + type: 'error', + message: 'invalid redirect URL or app code', + }) + return + } + setIsLoading(true) + if (protocol === SSOProtocol.SAML) { + fetchMembersSAMLSSOUrl(appCode, redirectUrl).then((res) => { + router.push(res.url) + }).finally(() => { + setIsLoading(false) + }) + } + else if (protocol === SSOProtocol.OIDC) { + fetchMembersOIDCSSOUrl(appCode, redirectUrl).then((res) => { + router.push(res.url) + }).finally(() => { + setIsLoading(false) + }) + } + else if (protocol === SSOProtocol.OAuth2) { + fetchMembersOAuth2SSOUrl(appCode, redirectUrl).then((res) => { + router.push(res.url) + }).finally(() => { + setIsLoading(false) + }) + } + else { + Toast.notify({ + type: 'error', + message: 'invalid SSO protocol', + }) + setIsLoading(false) + } + } + + return ( + <Button + tabIndex={0} + onClick={() => { handleSSOLogin() }} + disabled={isLoading} + className="w-full" + > + <Lock01 className='mr-2 h-5 w-5 text-text-accent-light-mode-only' /> + <span className="truncate">{t('login.withSSO')}</span> + </Button> + ) +} + +export default SSOAuth diff --git a/web/app/(shareLayout)/webapp-signin/layout.tsx b/web/app/(shareLayout)/webapp-signin/layout.tsx new file mode 100644 index 0000000000..a03364d326 --- /dev/null +++ b/web/app/(shareLayout)/webapp-signin/layout.tsx @@ -0,0 +1,25 @@ +'use client' + +import cn from '@/utils/classnames' +import { useGlobalPublicStore } from '@/context/global-public-context' +import useDocumentTitle from '@/hooks/use-document-title' + +export default function SignInLayout({ children }: any) { + const { systemFeatures } = useGlobalPublicStore() + useDocumentTitle('') + return <> + <div className={cn('flex min-h-screen w-full justify-center bg-background-default-burn p-6')}> + <div className={cn('flex w-full shrink-0 flex-col rounded-2xl border border-effects-highlight bg-background-default-subtle')}> + {/* <Header /> */} + <div className={cn('flex w-full grow flex-col items-center justify-center px-6 md:px-[108px]')}> + <div className='flex justify-center md:w-[440px] lg:w-[600px]'> + {children} + </div> + </div> + {systemFeatures.branding.enabled === false && <div className='system-xs-regular px-8 py-6 text-text-tertiary'> + © {new Date().getFullYear()} LangGenius, Inc. All rights reserved. + </div>} + </div> + </div> + </> +} diff --git a/web/app/(shareLayout)/webapp-signin/normalForm.tsx b/web/app/(shareLayout)/webapp-signin/normalForm.tsx new file mode 100644 index 0000000000..d6bdf607ba --- /dev/null +++ b/web/app/(shareLayout)/webapp-signin/normalForm.tsx @@ -0,0 +1,176 @@ +import React, { useCallback, useEffect, useState } from 'react' +import { useTranslation } from 'react-i18next' +import Link from 'next/link' +import { RiContractLine, RiDoorLockLine, RiErrorWarningFill } from '@remixicon/react' +import Loading from '@/app/components/base/loading' +import MailAndCodeAuth from './components/mail-and-code-auth' +import MailAndPasswordAuth from './components/mail-and-password-auth' +import SSOAuth from './components/sso-auth' +import cn from '@/utils/classnames' +import { LicenseStatus } from '@/types/feature' +import { IS_CE_EDITION } from '@/config' +import { useGlobalPublicStore } from '@/context/global-public-context' + +const NormalForm = () => { + const { t } = useTranslation() + + const [isLoading, setIsLoading] = useState(true) + const { systemFeatures } = useGlobalPublicStore() + const [authType, updateAuthType] = useState<'code' | 'password'>('password') + const [showORLine, setShowORLine] = useState(false) + const [allMethodsAreDisabled, setAllMethodsAreDisabled] = useState(false) + + const init = useCallback(async () => { + try { + setAllMethodsAreDisabled(!systemFeatures.enable_social_oauth_login && !systemFeatures.enable_email_code_login && !systemFeatures.enable_email_password_login && !systemFeatures.sso_enforced_for_signin) + setShowORLine((systemFeatures.enable_social_oauth_login || systemFeatures.sso_enforced_for_signin) && (systemFeatures.enable_email_code_login || systemFeatures.enable_email_password_login)) + updateAuthType(systemFeatures.enable_email_password_login ? 'password' : 'code') + } + catch (error) { + console.error(error) + setAllMethodsAreDisabled(true) + } + finally { setIsLoading(false) } + }, [systemFeatures]) + useEffect(() => { + init() + }, [init]) + if (isLoading) { + return <div className={ + cn( + 'flex w-full grow flex-col items-center justify-center', + 'px-6', + 'md:px-[108px]', + ) + }> + <Loading type='area' /> + </div> + } + if (systemFeatures.license?.status === LicenseStatus.LOST) { + return <div className='mx-auto mt-8 w-full'> + <div className='relative'> + <div className="rounded-lg bg-gradient-to-r from-workflow-workflow-progress-bg-1 to-workflow-workflow-progress-bg-2 p-4"> + <div className='shadows-shadow-lg relative mb-2 flex h-10 w-10 items-center justify-center rounded-xl bg-components-card-bg shadow'> + <RiContractLine className='h-5 w-5' /> + <RiErrorWarningFill className='absolute -right-1 -top-1 h-4 w-4 text-text-warning-secondary' /> + </div> + <p className='system-sm-medium text-text-primary'>{t('login.licenseLost')}</p> + <p className='system-xs-regular mt-1 text-text-tertiary'>{t('login.licenseLostTip')}</p> + </div> + </div> + </div> + } + if (systemFeatures.license?.status === LicenseStatus.EXPIRED) { + return <div className='mx-auto mt-8 w-full'> + <div className='relative'> + <div className="rounded-lg bg-gradient-to-r from-workflow-workflow-progress-bg-1 to-workflow-workflow-progress-bg-2 p-4"> + <div className='shadows-shadow-lg relative mb-2 flex h-10 w-10 items-center justify-center rounded-xl bg-components-card-bg shadow'> + <RiContractLine className='h-5 w-5' /> + <RiErrorWarningFill className='absolute -right-1 -top-1 h-4 w-4 text-text-warning-secondary' /> + </div> + <p className='system-sm-medium text-text-primary'>{t('login.licenseExpired')}</p> + <p className='system-xs-regular mt-1 text-text-tertiary'>{t('login.licenseExpiredTip')}</p> + </div> + </div> + </div> + } + if (systemFeatures.license?.status === LicenseStatus.INACTIVE) { + return <div className='mx-auto mt-8 w-full'> + <div className='relative'> + <div className="rounded-lg bg-gradient-to-r from-workflow-workflow-progress-bg-1 to-workflow-workflow-progress-bg-2 p-4"> + <div className='shadows-shadow-lg relative mb-2 flex h-10 w-10 items-center justify-center rounded-xl bg-components-card-bg shadow'> + <RiContractLine className='h-5 w-5' /> + <RiErrorWarningFill className='absolute -right-1 -top-1 h-4 w-4 text-text-warning-secondary' /> + </div> + <p className='system-sm-medium text-text-primary'>{t('login.licenseInactive')}</p> + <p className='system-xs-regular mt-1 text-text-tertiary'>{t('login.licenseInactiveTip')}</p> + </div> + </div> + </div> + } + + return ( + <> + <div className="mx-auto mt-8 w-full"> + <div className="mx-auto w-full"> + <h2 className="title-4xl-semi-bold text-text-primary">{t('login.pageTitle')}</h2> + {!systemFeatures.branding.enabled && <p className='body-md-regular mt-2 text-text-tertiary'>{t('login.welcome')}</p>} + </div> + <div className="relative"> + <div className="mt-6 flex flex-col gap-3"> + {systemFeatures.sso_enforced_for_signin && <div className='w-full'> + <SSOAuth protocol={systemFeatures.sso_enforced_for_signin_protocol} /> + </div>} + </div> + + {showORLine && <div className="relative mt-6"> + <div className="absolute inset-0 flex items-center" aria-hidden="true"> + <div className='h-px w-full bg-gradient-to-r from-background-gradient-mask-transparent via-divider-regular to-background-gradient-mask-transparent'></div> + </div> + <div className="relative flex justify-center"> + <span className="system-xs-medium-uppercase px-2 text-text-tertiary">{t('login.or')}</span> + </div> + </div>} + { + (systemFeatures.enable_email_code_login || systemFeatures.enable_email_password_login) && <> + {systemFeatures.enable_email_code_login && authType === 'code' && <> + <MailAndCodeAuth /> + {systemFeatures.enable_email_password_login && <div className='cursor-pointer py-1 text-center' onClick={() => { updateAuthType('password') }}> + <span className='system-xs-medium text-components-button-secondary-accent-text'>{t('login.usePassword')}</span> + </div>} + </>} + {systemFeatures.enable_email_password_login && authType === 'password' && <> + <MailAndPasswordAuth isEmailSetup={systemFeatures.is_email_setup} /> + {systemFeatures.enable_email_code_login && <div className='cursor-pointer py-1 text-center' onClick={() => { updateAuthType('code') }}> + <span className='system-xs-medium text-components-button-secondary-accent-text'>{t('login.useVerificationCode')}</span> + </div>} + </>} + </> + } + {allMethodsAreDisabled && <> + <div className="rounded-lg bg-gradient-to-r from-workflow-workflow-progress-bg-1 to-workflow-workflow-progress-bg-2 p-4"> + <div className='shadows-shadow-lg mb-2 flex h-10 w-10 items-center justify-center rounded-xl bg-components-card-bg shadow'> + <RiDoorLockLine className='h-5 w-5' /> + </div> + <p className='system-sm-medium text-text-primary'>{t('login.noLoginMethod')}</p> + <p className='system-xs-regular mt-1 text-text-tertiary'>{t('login.noLoginMethodTip')}</p> + </div> + <div className="relative my-2 py-2"> + <div className="absolute inset-0 flex items-center" aria-hidden="true"> + <div className='h-px w-full bg-gradient-to-r from-background-gradient-mask-transparent via-divider-regular to-background-gradient-mask-transparent'></div> + </div> + </div> + </>} + {!systemFeatures.branding.enabled && <> + <div className="system-xs-regular mt-2 block w-full text-text-tertiary"> + {t('login.tosDesc')} +   + <Link + className='system-xs-medium text-text-secondary hover:underline' + target='_blank' rel='noopener noreferrer' + href='https://dify.ai/terms' + >{t('login.tos')}</Link> +  &  + <Link + className='system-xs-medium text-text-secondary hover:underline' + target='_blank' rel='noopener noreferrer' + href='https://dify.ai/privacy' + >{t('login.pp')}</Link> + </div> + {IS_CE_EDITION && <div className="w-hull system-xs-regular mt-2 block text-text-tertiary"> + {t('login.goToInit')} +   + <Link + className='system-xs-medium text-text-secondary hover:underline' + href='/install' + >{t('login.setAdminAccount')}</Link> + </div>} + </>} + + </div> + </div> + </> + ) +} + +export default NormalForm diff --git a/web/app/(shareLayout)/webapp-signin/page.tsx b/web/app/(shareLayout)/webapp-signin/page.tsx index d58fafa151..07b7c88430 100644 --- a/web/app/(shareLayout)/webapp-signin/page.tsx +++ b/web/app/(shareLayout)/webapp-signin/page.tsx @@ -1,103 +1,125 @@ 'use client' import { useRouter, useSearchParams } from 'next/navigation' import type { FC } from 'react' -import React, { useEffect } from 'react' -import cn from '@/utils/classnames' +import React, { useCallback, useEffect } from 'react' +import { useTranslation } from 'react-i18next' import Toast from '@/app/components/base/toast' -import { fetchSystemFeatures, fetchWebOAuth2SSOUrl, fetchWebOIDCSSOUrl, fetchWebSAMLSSOUrl } from '@/service/share' -import { setAccessToken } from '@/app/components/share/utils' +import { removeAccessToken, setAccessToken } from '@/app/components/share/utils' +import { useGlobalPublicStore } from '@/context/global-public-context' import Loading from '@/app/components/base/loading' +import AppUnavailable from '@/app/components/base/app-unavailable' +import NormalForm from './normalForm' +import { AccessMode } from '@/models/access-control' +import ExternalMemberSsoAuth from './components/external-member-sso-auth' +import { fetchAccessToken } from '@/service/share' const WebSSOForm: FC = () => { + const { t } = useTranslation() + const systemFeatures = useGlobalPublicStore(s => s.systemFeatures) + const webAppAccessMode = useGlobalPublicStore(s => s.webAppAccessMode) const searchParams = useSearchParams() const router = useRouter() const redirectUrl = searchParams.get('redirect_url') const tokenFromUrl = searchParams.get('web_sso_token') const message = searchParams.get('message') - - const showErrorToast = (message: string) => { + const code = searchParams.get('code') + + const getSigninUrl = useCallback(() => { + const params = new URLSearchParams(searchParams) + params.delete('message') + params.delete('code') + return `/webapp-signin?${params.toString()}` + }, [searchParams]) + + const backToHome = useCallback(() => { + removeAccessToken() + const url = getSigninUrl() + router.replace(url) + }, [getSigninUrl, router]) + + const showErrorToast = (msg: string) => { Toast.notify({ type: 'error', - message, + message: msg, }) } - const getAppCodeFromRedirectUrl = () => { + const getAppCodeFromRedirectUrl = useCallback(() => { const appCode = redirectUrl?.split('/').pop() if (!appCode) return null return appCode - } - - const processTokenAndRedirect = async () => { - const appCode = getAppCodeFromRedirectUrl() - if (!appCode || !tokenFromUrl || !redirectUrl) { - showErrorToast('redirect url or app code or token is invalid.') - return - } - - await setAccessToken(appCode, tokenFromUrl) - router.push(redirectUrl) - } - - const handleSSOLogin = async (protocol: string) => { - const appCode = getAppCodeFromRedirectUrl() - if (!appCode || !redirectUrl) { - showErrorToast('redirect url or app code is invalid.') - return - } - - switch (protocol) { - case 'saml': { - const samlRes = await fetchWebSAMLSSOUrl(appCode, redirectUrl) - router.push(samlRes.url) - break - } - case 'oidc': { - const oidcRes = await fetchWebOIDCSSOUrl(appCode, redirectUrl) - router.push(oidcRes.url) - break - } - case 'oauth2': { - const oauth2Res = await fetchWebOAuth2SSOUrl(appCode, redirectUrl) - router.push(oauth2Res.url) - break - } - default: - showErrorToast('SSO protocol is not supported.') - } - } + }, [redirectUrl]) useEffect(() => { - const init = async () => { - const res = await fetchSystemFeatures() - const protocol = res.sso_enforced_for_web_protocol - - if (message) { - showErrorToast(message) + (async () => { + if (message) return - } - if (!tokenFromUrl) { - await handleSSOLogin(protocol) + const appCode = getAppCodeFromRedirectUrl() + if (appCode && tokenFromUrl && redirectUrl) { + localStorage.setItem('webapp_access_token', tokenFromUrl) + const tokenResp = await fetchAccessToken({ appCode, webAppAccessToken: tokenFromUrl }) + await setAccessToken(appCode, tokenResp.access_token) + router.replace(redirectUrl) return } + if (appCode && redirectUrl && localStorage.getItem('webapp_access_token')) { + const tokenResp = await fetchAccessToken({ appCode, webAppAccessToken: localStorage.getItem('webapp_access_token') }) + await setAccessToken(appCode, tokenResp.access_token) + router.replace(redirectUrl) + } + })() + }, [getAppCodeFromRedirectUrl, redirectUrl, router, tokenFromUrl, message]) - await processTokenAndRedirect() - } + useEffect(() => { + if (webAppAccessMode && webAppAccessMode === AccessMode.PUBLIC && redirectUrl) + router.replace(redirectUrl) + }, [webAppAccessMode, router, redirectUrl]) - init() - }, [message, tokenFromUrl]) // Added dependencies to useEffect + if (tokenFromUrl) { + return <div className='flex h-full items-center justify-center'> + <Loading /> + </div> + } - return ( - <div className="flex h-full items-center justify-center"> - <div className={cn('flex w-full grow flex-col items-center justify-center', 'px-6', 'md:px-[108px]')}> - <Loading type='area' /> - </div> + if (message) { + return <div className='flex h-full flex-col items-center justify-center gap-y-4'> + <AppUnavailable className='h-auto w-auto' code={code || t('share.common.appUnavailable')} unknownReason={message} /> + <span className='system-sm-regular cursor-pointer text-text-tertiary' onClick={backToHome}>{code === '403' ? t('common.userProfile.logout') : t('share.login.backToHome')}</span> + </div> + } + if (!redirectUrl) { + showErrorToast('redirect url is invalid.') + return <div className='flex h-full items-center justify-center'> + <AppUnavailable code={t('share.common.appUnavailable')} unknownReason='redirect url is invalid.' /> + </div> + } + if (webAppAccessMode && webAppAccessMode === AccessMode.PUBLIC) { + return <div className='flex h-full items-center justify-center'> + <Loading /> </div> - ) + } + if (!systemFeatures.webapp_auth.enabled) { + return <div className="flex h-full items-center justify-center"> + <p className='system-xs-regular text-text-tertiary'>{t('login.webapp.disabled')}</p> + </div> + } + if (webAppAccessMode && (webAppAccessMode === AccessMode.ORGANIZATION || webAppAccessMode === AccessMode.SPECIFIC_GROUPS_MEMBERS)) { + return <div className='w-full max-w-[400px]'> + <NormalForm /> + </div> + } + + if (webAppAccessMode && webAppAccessMode === AccessMode.EXTERNAL_MEMBERS) + return <ExternalMemberSsoAuth /> + + return <div className='flex h-full flex-col items-center justify-center gap-y-4'> + <AppUnavailable className='h-auto w-auto' isUnknownReason={true} /> + <span className='system-sm-regular cursor-pointer text-text-tertiary' onClick={backToHome}>{t('share.login.backToHome')}</span> + </div> } export default React.memo(WebSSOForm) diff --git a/web/app/account/account-page/index.tsx b/web/app/account/account-page/index.tsx index 72d2648c23..19c1e44236 100644 --- a/web/app/account/account-page/index.tsx +++ b/web/app/account/account-page/index.tsx @@ -20,6 +20,7 @@ import AppIcon from '@/app/components/base/app-icon' import { IS_CE_EDITION } from '@/config' import Input from '@/app/components/base/input' import PremiumBadge from '@/app/components/base/premium-badge' +import { useGlobalPublicStore } from '@/context/global-public-context' const titleClassName = ` system-sm-semibold text-text-secondary @@ -32,7 +33,7 @@ const validPassword = /^(?=.*[a-zA-Z])(?=.*\d).{8,}$/ export default function AccountPage() { const { t } = useTranslation() - const { systemFeatures } = useAppContext() + const { systemFeatures } = useGlobalPublicStore() const { mutateUserProfile, userProfile, apps } = useAppContext() const { isEducationAccount } = useProviderContext() const { notify } = useContext(ToastContext) @@ -138,7 +139,7 @@ export default function AccountPage() { <h4 className='title-2xl-semi-bold text-text-primary'>{t('common.account.myAccount')}</h4> </div> <div className='mb-8 flex items-center rounded-xl bg-gradient-to-r from-background-gradient-bg-fill-chat-bg-2 to-background-gradient-bg-fill-chat-bg-1 p-6'> - <AvatarWithEdit avatar={userProfile.avatar_url} name={userProfile.name} onSave={ mutateUserProfile } size={64} /> + <AvatarWithEdit avatar={userProfile.avatar_url} name={userProfile.name} onSave={mutateUserProfile} size={64} /> <div className='ml-4'> <p className='system-xl-semibold text-text-primary'> {userProfile.name} diff --git a/web/app/account/header.tsx b/web/app/account/header.tsx index 2bb89552c8..d033bfab61 100644 --- a/web/app/account/header.tsx +++ b/web/app/account/header.tsx @@ -4,23 +4,33 @@ import { RiArrowRightUpLine, RiRobot2Line } from '@remixicon/react' import { useRouter } from 'next/navigation' import Button from '../components/base/button' import Avatar from './avatar' -import LogoSite from '@/app/components/base/logo/logo-site' +import DifyLogo from '@/app/components/base/logo/dify-logo' +import { useCallback } from 'react' +import { useGlobalPublicStore } from '@/context/global-public-context' const Header = () => { const { t } = useTranslation() const router = useRouter() + const systemFeatures = useGlobalPublicStore(s => s.systemFeatures) - const back = () => { + const back = useCallback(() => { router.back() - } + }, [router]) + return ( <div className='flex flex-1 items-center justify-between px-4'> <div className='flex items-center gap-3'> <div className='flex cursor-pointer items-center' onClick={back}> - <LogoSite className='object-contain' /> + {systemFeatures.branding.enabled && systemFeatures.branding.login_page_logo + ? <img + src={systemFeatures.branding.login_page_logo} + className='block h-[22px] w-auto object-contain' + alt='Dify logo' + /> + : <DifyLogo />} </div> - <div className='h-4 w-[1px] bg-divider-regular' /> - <p className='title-3xl-semi-bold text-text-primary'>{t('common.account.account')}</p> + <div className='h-4 w-[1px] origin-center rotate-[11.31deg] bg-divider-regular' /> + <p className='title-3xl-semi-bold relative mt-[-2px] text-text-primary'>{t('common.account.account')}</p> </div> <div className='flex shrink-0 items-center gap-3'> <Button className='system-sm-medium gap-2 px-3 py-2' onClick={back}> diff --git a/web/app/account/layout.tsx b/web/app/account/layout.tsx index 9ee7435ac0..e74716fb3b 100644 --- a/web/app/account/layout.tsx +++ b/web/app/account/layout.tsx @@ -32,9 +32,4 @@ const Layout = ({ children }: { children: ReactNode }) => { </> ) } - -export const metadata = { - title: 'Dify', -} - export default Layout diff --git a/web/app/account/page.tsx b/web/app/account/page.tsx index baf386e2ba..e4896cdeb6 100644 --- a/web/app/account/page.tsx +++ b/web/app/account/page.tsx @@ -1,6 +1,11 @@ +'use client' +import { useTranslation } from 'react-i18next' import AccountPage from './account-page' +import useDocumentTitle from '@/hooks/use-document-title' export default function Account() { + const { t } = useTranslation() + useDocumentTitle(t('common.menus.account')) return <div className='mx-auto w-full max-w-[640px] px-6 pt-12'> <AccountPage /> </div> diff --git a/web/app/activate/activateForm.tsx b/web/app/activate/activateForm.tsx index 782b24be6e..d9d07cbfa1 100644 --- a/web/app/activate/activateForm.tsx +++ b/web/app/activate/activateForm.tsx @@ -7,8 +7,10 @@ import Button from '@/app/components/base/button' import { invitationCheck } from '@/service/common' import Loading from '@/app/components/base/loading' +import useDocumentTitle from '@/hooks/use-document-title' const ActivateForm = () => { + useDocumentTitle('') const router = useRouter() const { t } = useTranslation() const searchParams = useSearchParams() diff --git a/web/app/activate/page.tsx b/web/app/activate/page.tsx index 221559ff28..cfb1e6b149 100644 --- a/web/app/activate/page.tsx +++ b/web/app/activate/page.tsx @@ -1,17 +1,20 @@ +'use client' import React from 'react' import Header from '../signin/_header' import ActivateForm from './activateForm' import cn from '@/utils/classnames' +import { useGlobalPublicStore } from '@/context/global-public-context' const Activate = () => { + const { systemFeatures } = useGlobalPublicStore() return ( <div className={cn('flex min-h-screen w-full justify-center bg-background-default-burn p-6')}> <div className={cn('flex w-full shrink-0 flex-col rounded-2xl border border-effects-highlight bg-background-default-subtle')}> <Header /> <ActivateForm /> - <div className='px-8 py-6 text-sm font-normal text-text-tertiary'> + {!systemFeatures.branding.enabled && <div className='px-8 py-6 text-sm font-normal text-text-tertiary'> © {new Date().getFullYear()} LangGenius, Inc. All rights reserved. - </div> + </div>} </div> </div> ) diff --git a/web/app/components/app-sidebar/app-info.tsx b/web/app/components/app-sidebar/app-info.tsx index 18a9ac8bc8..7b6e66f7e7 100644 --- a/web/app/components/app-sidebar/app-info.tsx +++ b/web/app/components/app-sidebar/app-info.tsx @@ -6,9 +6,11 @@ import { RiDeleteBinLine, RiEditLine, RiEqualizer2Line, + RiExchange2Line, RiFileCopy2Line, RiFileDownloadLine, RiFileUploadLine, + RiMoreLine, } from '@remixicon/react' import AppIcon from '../base/app-icon' import SwitchAppModal from '../app/switch-app-modal' @@ -32,6 +34,8 @@ import { fetchWorkflowDraft } from '@/service/workflow' import ContentDialog from '@/app/components/base/content-dialog' import Button from '@/app/components/base/button' import CardView from '@/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/cardView' +import Divider from '../base/divider' +import { PortalToFollowElem, PortalToFollowElemContent, PortalToFollowElemTrigger } from '../base/portal-to-follow-elem' export type IAppInfoProps = { expand: boolean @@ -179,6 +183,11 @@ const AppInfo = ({ expand }: IAppInfoProps) => { const { isCurrentWorkspaceEditor } = useAppContext() + const [showMore, setShowMore] = useState(false) + const handleTriggerMore = useCallback(() => { + setShowMore(true) + }, [setShowMore]) + if (!appDetail) return null @@ -239,7 +248,7 @@ const AppInfo = ({ expand }: IAppInfoProps) => { </div> {/* description */} {appDetail.description && ( - <div className='system-xs-regular text-text-tertiary'>{appDetail.description}</div> + <div className='system-xs-regular overflow-wrap-anywhere w-full max-w-full whitespace-normal break-words text-text-tertiary'>{appDetail.description}</div> )} {/* operations */} <div className='flex flex-wrap items-center gap-1 self-stretch'> @@ -262,8 +271,7 @@ const AppInfo = ({ expand }: IAppInfoProps) => { onClick={() => { setOpen(false) setShowDuplicateModal(true) - }} - > + }}> <RiFileCopy2Line className='h-3.5 w-3.5 text-components-button-secondary-text' /> <span className='system-xs-medium text-components-button-secondary-text'>{t('app.duplicate')}</span> </Button> @@ -276,22 +284,50 @@ const AppInfo = ({ expand }: IAppInfoProps) => { <RiFileDownloadLine className='h-3.5 w-3.5 text-components-button-secondary-text' /> <span className='system-xs-medium text-components-button-secondary-text'>{t('app.export')}</span> </Button> - { - (appDetail.mode === 'advanced-chat' || appDetail.mode === 'workflow') && ( + {appDetail.mode !== 'agent-chat' && <PortalToFollowElem + open={showMore} + onOpenChange={setShowMore} + placement='bottom-end' + offset={{ + mainAxis: 4, + }}> + <PortalToFollowElemTrigger onClick={handleTriggerMore}> <Button size={'small'} variant={'secondary'} className='gap-[1px]' - onClick={() => { - setOpen(false) - setShowImportDSLModal(true) - }} > - <RiFileUploadLine className='h-3.5 w-3.5 text-components-button-secondary-text' /> - <span className='system-xs-medium text-components-button-secondary-text'>{t('workflow.common.importDSL')}</span> + <RiMoreLine className='h-3.5 w-3.5 text-components-button-secondary-text' /> + <span className='system-xs-medium text-components-button-secondary-text'>{t('common.operation.more')}</span> </Button> - ) - } + </PortalToFollowElemTrigger> + <PortalToFollowElemContent className='z-[21]'> + <div className='flex w-[264px] flex-col rounded-[12px] border-[0.5px] border-components-panel-border bg-components-panel-bg-blur p-1 shadow-lg backdrop-blur-[5px]'> + { + (appDetail.mode === 'advanced-chat' || appDetail.mode === 'workflow') + && <div className='flex h-8 cursor-pointer items-center gap-x-1 rounded-lg p-1.5 hover:bg-state-base-hover' + onClick={() => { + setOpen(false) + setShowImportDSLModal(true) + }}> + <RiFileUploadLine className='h-4 w-4 text-text-tertiary' /> + <span className='system-md-regular text-text-secondary'>{t('workflow.common.importDSL')}</span> + </div> + } + { + (appDetail.mode === 'completion' || appDetail.mode === 'chat') + && <div className='flex h-8 cursor-pointer items-center gap-x-1 rounded-lg p-1.5 hover:bg-state-base-hover' + onClick={() => { + setOpen(false) + setShowSwitchModal(true) + }}> + <RiExchange2Line className='h-4 w-4 text-text-tertiary' /> + <span className='system-md-regular text-text-secondary'>{t('app.switch')}</span> + </div> + } + </div> + </PortalToFollowElemContent> + </PortalToFollowElem>} </div> </div> <div className='flex flex-1'> @@ -301,6 +337,7 @@ const AppInfo = ({ expand }: IAppInfoProps) => { className='flex grow flex-col gap-2 overflow-auto px-2 py-1' /> </div> + <Divider /> <div className='flex min-h-fit shrink-0 flex-col items-start justify-center gap-3 self-stretch border-t-[0.5px] border-divider-subtle p-2'> <Button size={'medium'} diff --git a/web/app/components/app-sidebar/index.tsx b/web/app/components/app-sidebar/index.tsx index 3276a1c0a7..f58985ed96 100644 --- a/web/app/components/app-sidebar/index.tsx +++ b/web/app/components/app-sidebar/index.tsx @@ -16,7 +16,7 @@ export type IAppDetailNavProps = { desc: string isExternal?: boolean icon: string - icon_background: string + icon_background: string | null navigation: Array<{ name: string href: string diff --git a/web/app/components/app-sidebar/style.module.css b/web/app/components/app-sidebar/style.module.css index 722b35bc71..ca0978b760 100644 --- a/web/app/components/app-sidebar/style.module.css +++ b/web/app/components/app-sidebar/style.module.css @@ -5,7 +5,7 @@ .completionPic { background-image: url('./completion.png') } - + .expertPic { background-image: url('./expert.png') -} \ No newline at end of file +} diff --git a/web/app/components/app/annotation/index.tsx b/web/app/components/app/annotation/index.tsx index f010f5f8b1..04bce1947b 100644 --- a/web/app/components/app/annotation/index.tsx +++ b/web/app/components/app/annotation/index.tsx @@ -31,126 +31,98 @@ type Props = { appDetail: App } -const Annotation: FC<Props> = ({ - appDetail, -}) => { +const Annotation: FC<Props> = (props) => { + const { appDetail } = props const { t } = useTranslation() - const [isShowEdit, setIsShowEdit] = React.useState(false) + const [isShowEdit, setIsShowEdit] = useState(false) const [annotationConfig, setAnnotationConfig] = useState<AnnotationReplyConfig | null>(null) - const [isChatApp, setIsChatApp] = useState(false) + const [isChatApp] = useState(appDetail.mode !== 'completion') + const [controlRefreshSwitch, setControlRefreshSwitch] = useState(Date.now()) + const { plan, enableBilling } = useProviderContext() + const isAnnotationFull = enableBilling && plan.usage.annotatedResponse >= plan.total.annotatedResponse + const [isShowAnnotationFullModal, setIsShowAnnotationFullModal] = useState(false) + const [queryParams, setQueryParams] = useState<QueryParam>({}) + const [currPage, setCurrPage] = useState(0) + const [limit, setLimit] = useState(APP_PAGE_LIMIT) + const [list, setList] = useState<AnnotationItem[]>([]) + const [total, setTotal] = useState(0) + const [isLoading, setIsLoading] = useState(false) + const [controlUpdateList, setControlUpdateList] = useState(Date.now()) + const [currItem, setCurrItem] = useState<AnnotationItem | null>(null) + const [isShowViewModal, setIsShowViewModal] = useState(false) + const debouncedQueryParams = useDebounce(queryParams, { wait: 500 }) const fetchAnnotationConfig = async () => { const res = await doFetchAnnotationConfig(appDetail.id) setAnnotationConfig(res as AnnotationReplyConfig) return (res as AnnotationReplyConfig).id } + useEffect(() => { - const isChatApp = appDetail.mode !== 'completion' - setIsChatApp(isChatApp) - if (isChatApp) - fetchAnnotationConfig() + if (isChatApp) fetchAnnotationConfig() + // eslint-disable-next-line react-hooks/exhaustive-deps }, []) - const [controlRefreshSwitch, setControlRefreshSwitch] = useState(Date.now()) - const { plan, enableBilling } = useProviderContext() - const isAnnotationFull = (enableBilling && plan.usage.annotatedResponse >= plan.total.annotatedResponse) - const [isShowAnnotationFullModal, setIsShowAnnotationFullModal] = useState(false) + const ensureJobCompleted = async (jobId: string, status: AnnotationEnableStatus) => { - let isCompleted = false - while (!isCompleted) { + while (true) { const res: any = await queryAnnotationJobStatus(appDetail.id, status, jobId) - isCompleted = res.job_status === JobStatus.completed - if (isCompleted) - break - + if (res.job_status === JobStatus.completed) break await sleep(2000) } } - const [queryParams, setQueryParams] = useState<QueryParam>({}) - const [currPage, setCurrPage] = React.useState<number>(0) - const debouncedQueryParams = useDebounce(queryParams, { wait: 500 }) - const [limit, setLimit] = React.useState<number>(APP_PAGE_LIMIT) - const query = { - page: currPage + 1, - limit, - keyword: debouncedQueryParams.keyword || '', - } - - const [controlUpdateList, setControlUpdateList] = useState(Date.now()) - const [list, setList] = useState<AnnotationItem[]>([]) - const [total, setTotal] = useState(10) - const [isLoading, setIsLoading] = useState(false) const fetchList = async (page = 1) => { setIsLoading(true) try { const { data, total }: any = await fetchAnnotationList(appDetail.id, { - ...query, page, + limit, + keyword: debouncedQueryParams.keyword || '', }) setList(data as AnnotationItem[]) setTotal(total) } - catch { - + finally { + setIsLoading(false) } - setIsLoading(false) } useEffect(() => { fetchList(currPage + 1) - }, [currPage]) - - useEffect(() => { - fetchList(1) - setControlUpdateList(Date.now()) - }, [queryParams]) + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [currPage, limit, debouncedQueryParams]) const handleAdd = async (payload: AnnotationItemBasic) => { - await addAnnotation(appDetail.id, { - ...payload, - }) - Toast.notify({ - message: t('common.api.actionSuccess'), - type: 'success', - }) + await addAnnotation(appDetail.id, payload) + Toast.notify({ message: t('common.api.actionSuccess'), type: 'success' }) fetchList() setControlUpdateList(Date.now()) } const handleRemove = async (id: string) => { await delAnnotation(appDetail.id, id) - Toast.notify({ - message: t('common.api.actionSuccess'), - type: 'success', - }) + Toast.notify({ message: t('common.api.actionSuccess'), type: 'success' }) fetchList() setControlUpdateList(Date.now()) } - const [currItem, setCurrItem] = useState<AnnotationItem | null>(list[0]) - const [isShowViewModal, setIsShowViewModal] = useState(false) - useEffect(() => { - if (!isShowEdit) - setControlRefreshSwitch(Date.now()) - }, [isShowEdit]) const handleView = (item: AnnotationItem) => { setCurrItem(item) setIsShowViewModal(true) } const handleSave = async (question: string, answer: string) => { - await editAnnotation(appDetail.id, (currItem as AnnotationItem).id, { - question, - answer, - }) - Toast.notify({ - message: t('common.api.actionSuccess'), - type: 'success', - }) + if (!currItem) return + await editAnnotation(appDetail.id, currItem.id, { question, answer }) + Toast.notify({ message: t('common.api.actionSuccess'), type: 'success' }) fetchList() setControlUpdateList(Date.now()) } + useEffect(() => { + if (!isShowEdit) setControlRefreshSwitch(Date.now()) + }, [isShowEdit]) + return ( <div className='flex h-full flex-col'> <p className='system-sm-regular text-text-tertiary'>{t('appLog.description')}</p> @@ -211,6 +183,7 @@ const Annotation: FC<Props> = ({ </Filter> {isLoading ? <Loading type='app' /> + // eslint-disable-next-line sonarjs/no-nested-conditional : total > 0 ? <List list={list} diff --git a/web/app/components/app/app-access-control/access-control-dialog.tsx b/web/app/components/app/app-access-control/access-control-dialog.tsx new file mode 100644 index 0000000000..e3e013fbd4 --- /dev/null +++ b/web/app/components/app/app-access-control/access-control-dialog.tsx @@ -0,0 +1,61 @@ +import { Fragment, useCallback } from 'react' +import type { ReactNode } from 'react' +import { Dialog, Transition } from '@headlessui/react' +import { RiCloseLine } from '@remixicon/react' +import cn from '@/utils/classnames' + +type DialogProps = { + className?: string + children: ReactNode + show: boolean + onClose?: () => void +} + +const AccessControlDialog = ({ + className, + children, + show, + onClose, +}: DialogProps) => { + const close = useCallback(() => { + onClose?.() + }, [onClose]) + return ( + <Transition appear show={show} as={Fragment}> + <Dialog as="div" open={true} className="relative z-20" onClose={() => null}> + <Transition.Child + as={Fragment} + enter="ease-out duration-300" + enterFrom="opacity-0" + enterTo="opacity-100" + leave="ease-in duration-200" + leaveFrom="opacity-100" + leaveTo="opacity-0" + > + <div className="fixed inset-0 bg-background-overlay bg-opacity-25" /> + </Transition.Child> + + <div className="fixed inset-0 flex items-center justify-center"> + <Transition.Child + as={Fragment} + enter="ease-out duration-300" + enterFrom="opacity-0 scale-95" + enterTo="opacity-100 scale-100" + leave="ease-in duration-200" + leaveFrom="opacity-100 scale-100" + leaveTo="opacity-0 scale-95" + > + <Dialog.Panel className={cn('relative h-auto min-h-[323px] w-[600px] overflow-y-auto rounded-2xl bg-components-panel-bg p-0 shadow-xl transition-all', className)}> + <div onClick={() => close()} className="absolute right-5 top-5 z-10 flex h-8 w-8 cursor-pointer items-center justify-center"> + <RiCloseLine className='h-5 w-5' /> + </div> + {children} + </Dialog.Panel> + </Transition.Child> + </div> + </Dialog> + </Transition > + ) +} + +export default AccessControlDialog diff --git a/web/app/components/app/app-access-control/access-control-item.tsx b/web/app/components/app/app-access-control/access-control-item.tsx new file mode 100644 index 0000000000..0840902371 --- /dev/null +++ b/web/app/components/app/app-access-control/access-control-item.tsx @@ -0,0 +1,30 @@ +'use client' +import type { FC, PropsWithChildren } from 'react' +import useAccessControlStore from '../../../../context/access-control-store' +import type { AccessMode } from '@/models/access-control' + +type AccessControlItemProps = PropsWithChildren<{ + type: AccessMode +}> + +const AccessControlItem: FC<AccessControlItemProps> = ({ type, children }) => { + const { currentMenu, setCurrentMenu } = useAccessControlStore(s => ({ currentMenu: s.currentMenu, setCurrentMenu: s.setCurrentMenu })) + if (currentMenu !== type) { + return <div + className="cursor-pointer rounded-[10px] border-[1px] + border-components-option-card-option-border bg-components-option-card-option-bg + hover:border-components-option-card-option-border-hover hover:bg-components-option-card-option-bg-hover" + onClick={() => setCurrentMenu(type)} > + {children} + </div> + } + + return <div className="rounded-[10px] border-[1.5px] + border-components-option-card-option-selected-border bg-components-option-card-option-selected-bg shadow-sm"> + {children} + </div> +} + +AccessControlItem.displayName = 'AccessControlItem' + +export default AccessControlItem diff --git a/web/app/components/app/app-access-control/add-member-or-group-pop.tsx b/web/app/components/app/app-access-control/add-member-or-group-pop.tsx new file mode 100644 index 0000000000..da4a25c1d8 --- /dev/null +++ b/web/app/components/app/app-access-control/add-member-or-group-pop.tsx @@ -0,0 +1,204 @@ +'use client' +import { RiAddCircleFill, RiArrowRightSLine, RiOrganizationChart } from '@remixicon/react' +import { useTranslation } from 'react-i18next' +import { useCallback, useEffect, useRef, useState } from 'react' +import { useDebounce } from 'ahooks' +import { FloatingOverlay } from '@floating-ui/react' +import Avatar from '../../base/avatar' +import Button from '../../base/button' +import Checkbox from '../../base/checkbox' +import Input from '../../base/input' +import { PortalToFollowElem, PortalToFollowElemContent, PortalToFollowElemTrigger } from '../../base/portal-to-follow-elem' +import Loading from '../../base/loading' +import useAccessControlStore from '../../../../context/access-control-store' +import classNames from '@/utils/classnames' +import { useSearchForWhiteListCandidates } from '@/service/access-control' +import type { AccessControlAccount, AccessControlGroup, Subject, SubjectAccount, SubjectGroup } from '@/models/access-control' +import { SubjectType } from '@/models/access-control' +import { useSelector } from '@/context/app-context' + +export default function AddMemberOrGroupDialog() { + const { t } = useTranslation() + const [open, setOpen] = useState(false) + const [keyword, setKeyword] = useState('') + const selectedGroupsForBreadcrumb = useAccessControlStore(s => s.selectedGroupsForBreadcrumb) + const debouncedKeyword = useDebounce(keyword, { wait: 500 }) + + const lastAvailableGroup = selectedGroupsForBreadcrumb[selectedGroupsForBreadcrumb.length - 1] + const { isLoading, isFetchingNextPage, fetchNextPage, data } = useSearchForWhiteListCandidates({ keyword: debouncedKeyword, groupId: lastAvailableGroup?.id, resultsPerPage: 10 }, open) + const handleKeywordChange = (e: React.ChangeEvent<HTMLInputElement>) => { + setKeyword(e.target.value) + } + + const anchorRef = useRef<HTMLDivElement>(null) + useEffect(() => { + const hasMore = data?.pages?.[0].hasMore ?? false + let observer: IntersectionObserver | undefined + if (anchorRef.current) { + observer = new IntersectionObserver((entries) => { + if (entries[0].isIntersecting && !isLoading && hasMore) + fetchNextPage() + }, { rootMargin: '20px' }) + observer.observe(anchorRef.current) + } + return () => observer?.disconnect() + }, [isLoading, fetchNextPage, anchorRef, data]) + + return <PortalToFollowElem open={open} onOpenChange={setOpen} offset={{ crossAxis: 300 }} placement='bottom-end'> + <PortalToFollowElemTrigger asChild> + <Button variant='ghost-accent' size='small' className='flex shrink-0 items-center gap-x-0.5' onClick={() => setOpen(!open)}> + <RiAddCircleFill className='h-4 w-4' /> + <span>{t('common.operation.add')}</span> + </Button> + </PortalToFollowElemTrigger> + {open && <FloatingOverlay />} + <PortalToFollowElemContent className='z-[25]'> + <div className='relative flex max-h-[400px] w-[400px] flex-col overflow-y-auto rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg-blur shadow-lg backdrop-blur-[5px]'> + <div className='sticky top-0 z-10 bg-components-panel-bg-blur p-2 pb-0.5 backdrop-blur-[5px]'> + <Input value={keyword} onChange={handleKeywordChange} showLeftIcon placeholder={t('app.accessControlDialog.operateGroupAndMember.searchPlaceholder') as string} /> + </div> + { + isLoading + ? <div className='p-1'><Loading /></div> + : (data?.pages?.length ?? 0) > 0 + ? <> + <div className='flex h-7 items-center px-2 py-0.5'> + <SelectedGroupsBreadCrumb /> + </div> + <div className='p-1'> + {renderGroupOrMember(data?.pages ?? [])} + {isFetchingNextPage && <Loading />} + </div> + <div ref={anchorRef} className='h-0'> </div> + </> + : <div className='flex h-7 items-center justify-center px-2 py-0.5'> + <span className='system-xs-regular text-text-tertiary'>{t('app.accessControlDialog.operateGroupAndMember.noResult')}</span> + </div> + } + </div> + </PortalToFollowElemContent> + </PortalToFollowElem> +} + +type GroupOrMemberData = { subjects: Subject[]; currPage: number }[] +function renderGroupOrMember(data: GroupOrMemberData) { + return data?.map((page) => { + return <div key={`search_group_member_page_${page.currPage}`}> + {page.subjects?.map((item, index) => { + if (item.subjectType === SubjectType.GROUP) + return <GroupItem key={index} group={(item as SubjectGroup).groupData} /> + return <MemberItem key={index} member={(item as SubjectAccount).accountData} /> + })} + </div> + }) ?? null +} + +function SelectedGroupsBreadCrumb() { + const selectedGroupsForBreadcrumb = useAccessControlStore(s => s.selectedGroupsForBreadcrumb) + const setSelectedGroupsForBreadcrumb = useAccessControlStore(s => s.setSelectedGroupsForBreadcrumb) + const { t } = useTranslation() + + const handleBreadCrumbClick = useCallback((index: number) => { + const newGroups = selectedGroupsForBreadcrumb.slice(0, index + 1) + setSelectedGroupsForBreadcrumb(newGroups) + }, [setSelectedGroupsForBreadcrumb, selectedGroupsForBreadcrumb]) + const handleReset = useCallback(() => { + setSelectedGroupsForBreadcrumb([]) + }, [setSelectedGroupsForBreadcrumb]) + return <div className='flex h-7 items-center gap-x-0.5 px-2 py-0.5'> + <span className={classNames('system-xs-regular text-text-tertiary', selectedGroupsForBreadcrumb.length > 0 && 'text-text-accent cursor-pointer')} onClick={handleReset}>{t('app.accessControlDialog.operateGroupAndMember.allMembers')}</span> + {selectedGroupsForBreadcrumb.map((group, index) => { + return <div key={index} className='system-xs-regular flex items-center gap-x-0.5 text-text-tertiary'> + <span>/</span> + <span className={index === selectedGroupsForBreadcrumb.length - 1 ? '' : 'cursor-pointer text-text-accent'} onClick={() => handleBreadCrumbClick(index)}>{group.name}</span> + </div> + })} + </div> +} + +type GroupItemProps = { + group: AccessControlGroup +} +function GroupItem({ group }: GroupItemProps) { + const { t } = useTranslation() + const specificGroups = useAccessControlStore(s => s.specificGroups) + const setSpecificGroups = useAccessControlStore(s => s.setSpecificGroups) + const selectedGroupsForBreadcrumb = useAccessControlStore(s => s.selectedGroupsForBreadcrumb) + const setSelectedGroupsForBreadcrumb = useAccessControlStore(s => s.setSelectedGroupsForBreadcrumb) + const isChecked = specificGroups.some(g => g.id === group.id) + const handleCheckChange = useCallback(() => { + if (!isChecked) { + const newGroups = [...specificGroups, group] + setSpecificGroups(newGroups) + } + else { + const newGroups = specificGroups.filter(g => g.id !== group.id) + setSpecificGroups(newGroups) + } + }, [specificGroups, setSpecificGroups, group, isChecked]) + + const handleExpandClick = useCallback(() => { + setSelectedGroupsForBreadcrumb([...selectedGroupsForBreadcrumb, group]) + }, [selectedGroupsForBreadcrumb, setSelectedGroupsForBreadcrumb, group]) + return <BaseItem> + <Checkbox checked={isChecked} className='h-4 w-4 shrink-0' onCheck={handleCheckChange} /> + <div className='item-center flex grow'> + <div className='mr-2 h-5 w-5 overflow-hidden rounded-full bg-components-icon-bg-blue-solid'> + <div className='bg-access-app-icon-mask-bg flex h-full w-full items-center justify-center'> + <RiOrganizationChart className='h-[14px] w-[14px] text-components-avatar-shape-fill-stop-0' /> + </div> + </div> + <p className='system-sm-medium mr-1 text-text-secondary'>{group.name}</p> + <p className='system-xs-regular text-text-tertiary'>{group.groupSize}</p> + </div> + <Button size="small" disabled={isChecked} variant='ghost-accent' + className='flex shrink-0 items-center justify-between px-1.5 py-1' onClick={handleExpandClick}> + <span className='px-[3px]'>{t('app.accessControlDialog.operateGroupAndMember.expand')}</span> + <RiArrowRightSLine className='h-4 w-4' /> + </Button> + </BaseItem> +} + +type MemberItemProps = { + member: AccessControlAccount +} +function MemberItem({ member }: MemberItemProps) { + const currentUser = useSelector(s => s.userProfile) + const { t } = useTranslation() + const specificMembers = useAccessControlStore(s => s.specificMembers) + const setSpecificMembers = useAccessControlStore(s => s.setSpecificMembers) + const isChecked = specificMembers.some(m => m.id === member.id) + const handleCheckChange = useCallback(() => { + if (!isChecked) { + const newMembers = [...specificMembers, member] + setSpecificMembers(newMembers) + } + else { + const newMembers = specificMembers.filter(m => m.id !== member.id) + setSpecificMembers(newMembers) + } + }, [specificMembers, setSpecificMembers, member, isChecked]) + return <BaseItem className='pr-3'> + <Checkbox checked={isChecked} className='h-4 w-4 shrink-0' onCheck={handleCheckChange} /> + <div className='flex grow items-center'> + <div className='mr-2 h-5 w-5 overflow-hidden rounded-full bg-components-icon-bg-blue-solid'> + <div className='bg-access-app-icon-mask-bg flex h-full w-full items-center justify-center'> + <Avatar className='h-[14px] w-[14px]' textClassName='text-[12px]' avatar={null} name={member.name} /> + </div> + </div> + <p className='system-sm-medium mr-1 text-text-secondary'>{member.name}</p> + {currentUser.email === member.email && <p className='system-xs-regular text-text-tertiary'>({t('common.you')})</p>} + </div> + <p className='system-xs-regular text-text-quaternary'>{member.email}</p> + </BaseItem> +} + +type BaseItemProps = { + className?: string + children: React.ReactNode +} +function BaseItem({ children, className }: BaseItemProps) { + return <div className={classNames('p-1 pl-2 flex items-center space-x-2 hover:rounded-lg hover:bg-state-base-hover cursor-pointer', className)}> + {children} + </div> +} diff --git a/web/app/components/app/app-access-control/index.tsx b/web/app/components/app/app-access-control/index.tsx new file mode 100644 index 0000000000..13faaea957 --- /dev/null +++ b/web/app/components/app/app-access-control/index.tsx @@ -0,0 +1,110 @@ +'use client' +import { Description as DialogDescription, DialogTitle } from '@headlessui/react' +import { RiBuildingLine, RiGlobalLine, RiVerifiedBadgeLine } from '@remixicon/react' +import { useTranslation } from 'react-i18next' +import { useCallback, useEffect } from 'react' +import Button from '../../base/button' +import Toast from '../../base/toast' +import useAccessControlStore from '../../../../context/access-control-store' +import AccessControlDialog from './access-control-dialog' +import AccessControlItem from './access-control-item' +import SpecificGroupsOrMembers, { WebAppSSONotEnabledTip } from './specific-groups-or-members' +import { useGlobalPublicStore } from '@/context/global-public-context' +import type { App } from '@/types/app' +import type { Subject } from '@/models/access-control' +import { AccessMode, SubjectType } from '@/models/access-control' +import { useUpdateAccessMode } from '@/service/access-control' + +type AccessControlProps = { + app: App + onClose: () => void + onConfirm?: () => void +} + +export default function AccessControl(props: AccessControlProps) { + const { app, onClose, onConfirm } = props + const { t } = useTranslation() + const systemFeatures = useGlobalPublicStore(s => s.systemFeatures) + const setAppId = useAccessControlStore(s => s.setAppId) + const specificGroups = useAccessControlStore(s => s.specificGroups) + const specificMembers = useAccessControlStore(s => s.specificMembers) + const currentMenu = useAccessControlStore(s => s.currentMenu) + const setCurrentMenu = useAccessControlStore(s => s.setCurrentMenu) + const hideTip = systemFeatures.webapp_auth.enabled + && (systemFeatures.webapp_auth.allow_sso + || systemFeatures.webapp_auth.allow_email_password_login + || systemFeatures.webapp_auth.allow_email_code_login) + + useEffect(() => { + setAppId(app.id) + setCurrentMenu(app.access_mode ?? AccessMode.SPECIFIC_GROUPS_MEMBERS) + }, [app, setAppId, setCurrentMenu]) + + const { isPending, mutateAsync: updateAccessMode } = useUpdateAccessMode() + const handleConfirm = useCallback(async () => { + const submitData: { + appId: string + accessMode: AccessMode + subjects?: Pick<Subject, 'subjectId' | 'subjectType'>[] + } = { appId: app.id, accessMode: currentMenu } + if (currentMenu === AccessMode.SPECIFIC_GROUPS_MEMBERS) { + const subjects: Pick<Subject, 'subjectId' | 'subjectType'>[] = [] + specificGroups.forEach((group) => { + subjects.push({ subjectId: group.id, subjectType: SubjectType.GROUP }) + }) + specificMembers.forEach((member) => { + subjects.push({ + subjectId: member.id, + subjectType: SubjectType.ACCOUNT, + }) + }) + submitData.subjects = subjects + } + await updateAccessMode(submitData) + Toast.notify({ type: 'success', message: t('app.accessControlDialog.updateSuccess') }) + onConfirm?.() + }, [updateAccessMode, app, specificGroups, specificMembers, t, onConfirm, currentMenu]) + return <AccessControlDialog show onClose={onClose}> + <div className='flex flex-col gap-y-3'> + <div className='pb-3 pl-6 pr-14 pt-6'> + <DialogTitle className='title-2xl-semi-bold text-text-primary'>{t('app.accessControlDialog.title')}</DialogTitle> + <DialogDescription className='system-xs-regular mt-1 text-text-tertiary'>{t('app.accessControlDialog.description')}</DialogDescription> + </div> + <div className='flex flex-col gap-y-1 px-6 pb-3'> + <div className='leading-6'> + <p className='system-sm-medium'>{t('app.accessControlDialog.accessLabel')}</p> + </div> + <AccessControlItem type={AccessMode.ORGANIZATION}> + <div className='flex items-center p-3'> + <div className='flex grow items-center gap-x-2'> + <RiBuildingLine className='h-4 w-4 text-text-primary' /> + <p className='system-sm-medium text-text-primary'>{t('app.accessControlDialog.accessItems.organization')}</p> + </div> + </div> + </AccessControlItem> + <AccessControlItem type={AccessMode.SPECIFIC_GROUPS_MEMBERS}> + <SpecificGroupsOrMembers /> + </AccessControlItem> + <AccessControlItem type={AccessMode.EXTERNAL_MEMBERS}> + <div className='flex items-center p-3'> + <div className='flex grow items-center gap-x-2'> + <RiVerifiedBadgeLine className='h-4 w-4 text-text-primary' /> + <p className='system-sm-medium text-text-primary'>{t('app.accessControlDialog.accessItems.external')}</p> + </div> + {!hideTip && <WebAppSSONotEnabledTip />} + </div> + </AccessControlItem> + <AccessControlItem type={AccessMode.PUBLIC}> + <div className='flex items-center gap-x-2 p-3'> + <RiGlobalLine className='h-4 w-4 text-text-primary' /> + <p className='system-sm-medium text-text-primary'>{t('app.accessControlDialog.accessItems.anyone')}</p> + </div> + </AccessControlItem> + </div> + <div className='flex items-center justify-end gap-x-2 p-6 pt-5'> + <Button onClick={onClose}>{t('common.operation.cancel')}</Button> + <Button disabled={isPending} loading={isPending} variant='primary' onClick={handleConfirm}>{t('common.operation.confirm')}</Button> + </div> + </div> + </AccessControlDialog> +} diff --git a/web/app/components/app/app-access-control/specific-groups-or-members.tsx b/web/app/components/app/app-access-control/specific-groups-or-members.tsx new file mode 100644 index 0000000000..b30c8f1ba3 --- /dev/null +++ b/web/app/components/app/app-access-control/specific-groups-or-members.tsx @@ -0,0 +1,127 @@ +'use client' +import { RiAlertFill, RiCloseCircleFill, RiLockLine, RiOrganizationChart } from '@remixicon/react' +import { useTranslation } from 'react-i18next' +import { useCallback, useEffect } from 'react' +import Avatar from '../../base/avatar' +import Tooltip from '../../base/tooltip' +import Loading from '../../base/loading' +import useAccessControlStore from '../../../../context/access-control-store' +import AddMemberOrGroupDialog from './add-member-or-group-pop' +import type { AccessControlAccount, AccessControlGroup } from '@/models/access-control' +import { AccessMode } from '@/models/access-control' +import { useAppWhiteListSubjects } from '@/service/access-control' + +export default function SpecificGroupsOrMembers() { + const currentMenu = useAccessControlStore(s => s.currentMenu) + const appId = useAccessControlStore(s => s.appId) + const setSpecificGroups = useAccessControlStore(s => s.setSpecificGroups) + const setSpecificMembers = useAccessControlStore(s => s.setSpecificMembers) + const { t } = useTranslation() + + const { isPending, data } = useAppWhiteListSubjects(appId, Boolean(appId) && currentMenu === AccessMode.SPECIFIC_GROUPS_MEMBERS) + useEffect(() => { + setSpecificGroups(data?.groups ?? []) + setSpecificMembers(data?.members ?? []) + }, [data, setSpecificGroups, setSpecificMembers]) + + if (currentMenu !== AccessMode.SPECIFIC_GROUPS_MEMBERS) { + return <div className='flex items-center p-3'> + <div className='flex grow items-center gap-x-2'> + <RiLockLine className='h-4 w-4 text-text-primary' /> + <p className='system-sm-medium text-text-primary'>{t('app.accessControlDialog.accessItems.specific')}</p> + </div> + </div> + } + + return <div> + <div className='flex items-center gap-x-1 p-3'> + <div className='flex grow items-center gap-x-1'> + <RiLockLine className='h-4 w-4 text-text-primary' /> + <p className='system-sm-medium text-text-primary'>{t('app.accessControlDialog.accessItems.specific')}</p> + </div> + <div className='flex items-center gap-x-1'> + <AddMemberOrGroupDialog /> + </div> + </div> + <div className='px-1 pb-1'> + <div className='flex max-h-[400px] flex-col gap-y-2 overflow-y-auto rounded-lg bg-background-section p-2'> + {isPending ? <Loading /> : <RenderGroupsAndMembers />} + </div> + </div> + </div > +} + +function RenderGroupsAndMembers() { + const { t } = useTranslation() + const specificGroups = useAccessControlStore(s => s.specificGroups) + const specificMembers = useAccessControlStore(s => s.specificMembers) + if (specificGroups.length <= 0 && specificMembers.length <= 0) + return <div className='px-2 pb-1.5 pt-5'><p className='system-xs-regular text-center text-text-tertiary'>{t('app.accessControlDialog.noGroupsOrMembers')}</p></div> + return <> + <p className='system-2xs-medium-uppercase sticky top-0 text-text-tertiary'>{t('app.accessControlDialog.groups', { count: specificGroups.length ?? 0 })}</p> + <div className='flex flex-row flex-wrap gap-1'> + {specificGroups.map((group, index) => <GroupItem key={index} group={group} />)} + </div> + <p className='system-2xs-medium-uppercase sticky top-0 text-text-tertiary'>{t('app.accessControlDialog.members', { count: specificMembers.length ?? 0 })}</p> + <div className='flex flex-row flex-wrap gap-1'> + {specificMembers.map((member, index) => <MemberItem key={index} member={member} />)} + </div> + </> +} + +type GroupItemProps = { + group: AccessControlGroup +} +function GroupItem({ group }: GroupItemProps) { + const specificGroups = useAccessControlStore(s => s.specificGroups) + const setSpecificGroups = useAccessControlStore(s => s.setSpecificGroups) + const handleRemoveGroup = useCallback(() => { + setSpecificGroups(specificGroups.filter(g => g.id !== group.id)) + }, [group, setSpecificGroups, specificGroups]) + return <BaseItem icon={<RiOrganizationChart className='h-[14px] w-[14px] text-components-avatar-shape-fill-stop-0' />} + onRemove={handleRemoveGroup}> + <p className='system-xs-regular text-text-primary'>{group.name}</p> + <p className='system-xs-regular text-text-tertiary'>{group.groupSize}</p> + </BaseItem> +} + +type MemberItemProps = { + member: AccessControlAccount +} +function MemberItem({ member }: MemberItemProps) { + const specificMembers = useAccessControlStore(s => s.specificMembers) + const setSpecificMembers = useAccessControlStore(s => s.setSpecificMembers) + const handleRemoveMember = useCallback(() => { + setSpecificMembers(specificMembers.filter(m => m.id !== member.id)) + }, [member, setSpecificMembers, specificMembers]) + return <BaseItem icon={<Avatar className='h-[14px] w-[14px]' textClassName='text-[12px]' avatar={null} name={member.name} />} + onRemove={handleRemoveMember}> + <p className='system-xs-regular text-text-primary'>{member.name}</p> + </BaseItem> +} + +type BaseItemProps = { + icon: React.ReactNode + children: React.ReactNode + onRemove?: () => void +} +function BaseItem({ icon, onRemove, children }: BaseItemProps) { + return <div className='group flex flex-row items-center gap-x-1 rounded-full border-[0.5px] bg-components-badge-white-to-dark p-1 pr-1.5 shadow-xs'> + <div className='h-5 w-5 overflow-hidden rounded-full bg-components-icon-bg-blue-solid'> + <div className='bg-access-app-icon-mask-bg flex h-full w-full items-center justify-center'> + {icon} + </div> + </div> + {children} + <div className='flex h-4 w-4 cursor-pointer items-center justify-center' onClick={onRemove}> + <RiCloseCircleFill className='h-[14px] w-[14px] text-text-quaternary' /> + </div> + </div> +} + +export function WebAppSSONotEnabledTip() { + const { t } = useTranslation() + return <Tooltip asChild={false} popupContent={t('app.accessControlDialog.webAppSSONotEnabledTip')}> + <RiAlertFill className='h-4 w-4 shrink-0 text-text-warning-secondary' /> + </Tooltip> +} diff --git a/web/app/components/app/app-publisher/index.tsx b/web/app/components/app/app-publisher/index.tsx index 360741ab2e..1485964198 100644 --- a/web/app/components/app/app-publisher/index.tsx +++ b/web/app/components/app/app-publisher/index.tsx @@ -1,21 +1,31 @@ import { memo, useCallback, + useEffect, useState, } from 'react' import { useTranslation } from 'react-i18next' import dayjs from 'dayjs' import { RiArrowDownSLine, + RiArrowRightSLine, + RiBuildingLine, + RiGlobalLine, + RiLockLine, RiPlanetLine, RiPlayCircleLine, RiPlayList2Line, RiTerminalBoxLine, + RiVerifiedBadgeLine, } from '@remixicon/react' import { useKeyPress } from 'ahooks' +import { getKeyboardKeyCodeBySystem } from '../../workflow/utils' import Toast from '../../base/toast' import type { ModelAndParameter } from '../configuration/debug/types' -import { getKeyboardKeyCodeBySystem } from '../../workflow/utils' +import Divider from '../../base/divider' +import AccessControl from '../app-access-control' +import Loading from '../../base/loading' +import Tooltip from '../../base/tooltip' import SuggestedAction from './suggested-action' import PublishWithMultipleModel from './publish-with-multiple-model' import Button from '@/app/components/base/button' @@ -34,6 +44,10 @@ import WorkflowToolConfigureButton from '@/app/components/tools/workflow-tool/co import type { InputVar } from '@/app/components/workflow/types' import { appDefaultIconBackground } from '@/config' import type { PublishWorkflowParams } from '@/types/workflow' +import { useAppWhiteListSubjects, useGetUserCanAccessApp } from '@/service/access-control' +import { AccessMode } from '@/models/access-control' +import { fetchAppDetail } from '@/service/apps' +import { useGlobalPublicStore } from '@/context/global-public-context' export type AppPublisherProps = { disabled?: boolean @@ -74,11 +88,33 @@ const AppPublisher = ({ const [published, setPublished] = useState(false) const [open, setOpen] = useState(false) const appDetail = useAppStore(state => state.appDetail) + const setAppDetail = useAppStore(s => s.setAppDetail) + const systemFeatures = useGlobalPublicStore(s => s.systemFeatures) const { app_base_url: appBaseURL = '', access_token: accessToken = '' } = appDetail?.site ?? {} const appMode = (appDetail?.mode !== 'completion' && appDetail?.mode !== 'workflow') ? 'chat' : appDetail.mode - const appURL = `${appBaseURL}/${basePath}/${appMode}/${accessToken}` + const appURL = `${appBaseURL}${basePath}/${appMode}/${accessToken}` const isChatApp = ['chat', 'agent-chat', 'completion'].includes(appDetail?.mode || '') + const { data: userCanAccessApp, isLoading: isGettingUserCanAccessApp, refetch } = useGetUserCanAccessApp({ appId: appDetail?.id, enabled: false }) + const { data: appAccessSubjects, isLoading: isGettingAppWhiteListSubjects } = useAppWhiteListSubjects(appDetail?.id, open && systemFeatures.webapp_auth.enabled && appDetail?.access_mode === AccessMode.SPECIFIC_GROUPS_MEMBERS) + useEffect(() => { + if (systemFeatures.webapp_auth.enabled && open && appDetail) + refetch() + }, [open, appDetail, refetch, systemFeatures]) + + const [showAppAccessControl, setShowAppAccessControl] = useState(false) + const [isAppAccessSet, setIsAppAccessSet] = useState(true) + useEffect(() => { + if (appDetail && appAccessSubjects) { + if (appDetail.access_mode === AccessMode.SPECIFIC_GROUPS_MEMBERS && appAccessSubjects.groups?.length === 0 && appAccessSubjects.members?.length === 0) + setIsAppAccessSet(false) + else + setIsAppAccessSet(true) + } + else { + setIsAppAccessSet(true) + } + }, [appAccessSubjects, appDetail]) const language = useGetLanguage() const formatTimeFromNow = useCallback((time: number) => { return dayjs(time).locale(language === 'zh_Hans' ? 'zh-cn' : language.replace('_', '-')).fromNow() @@ -99,7 +135,7 @@ const AppPublisher = ({ await onRestore?.() setOpen(false) } - catch {} + catch { } }, [onRestore]) const handleTrigger = useCallback(() => { @@ -130,6 +166,13 @@ const AppPublisher = ({ } }, [appDetail?.id]) + const handleAccessControlUpdate = useCallback(() => { + fetchAppDetail({ url: '/apps', id: appDetail!.id }).then((res) => { + setAppDetail(res) + setShowAppAccessControl(false) + }) + }, [appDetail, setAppDetail]) + const [embeddingModalOpen, setEmbeddingModalOpen] = useState(false) useKeyPress(`${getKeyboardKeyCodeBySystem('ctrl')}.shift.p`, (e) => { @@ -138,7 +181,7 @@ const AppPublisher = ({ return handlePublish() }, - { exactMatch: true, useCapture: true }) + { exactMatch: true, useCapture: true }) return ( <> @@ -223,70 +266,127 @@ const AppPublisher = ({ ) } </div> - <div className='border-t-[0.5px] border-t-divider-regular p-4 pt-3'> - <SuggestedAction - disabled={!publishedAt} - link={appURL} - icon={<RiPlayCircleLine className='h-4 w-4' />} - > - {t('workflow.common.runApp')} - </SuggestedAction> - {appDetail?.mode === 'workflow' - ? ( - <SuggestedAction - disabled={!publishedAt} - link={`${appURL}${appURL.includes('?') ? '&' : '?'}mode=batch`} - icon={<RiPlayList2Line className='h-4 w-4' />} - > - {t('workflow.common.batchRunApp')} - </SuggestedAction> - ) - : ( - <SuggestedAction + {(systemFeatures.webapp_auth.enabled && (isGettingUserCanAccessApp || isGettingAppWhiteListSubjects)) + ? <div className='py-2'><Loading /></div> + : <> + <Divider className='my-0' /> + {systemFeatures.webapp_auth.enabled && <div className='p-4 pt-3'> + <div className='flex h-6 items-center'> + <p className='system-xs-medium text-text-tertiary'>{t('app.publishApp.title')}</p> + </div> + <div className='flex h-8 cursor-pointer items-center gap-x-0.5 rounded-lg bg-components-input-bg-normal py-1 pl-2.5 pr-2 hover:bg-primary-50 hover:text-text-accent' onClick={() => { - setEmbeddingModalOpen(true) - handleTrigger() - }} + setShowAppAccessControl(true) + }}> + <div className='flex grow items-center gap-x-1.5 overflow-hidden pr-1'> + {appDetail?.access_mode === AccessMode.ORGANIZATION + && <> + <RiBuildingLine className='h-4 w-4 shrink-0 text-text-secondary' /> + <p className='system-sm-medium text-text-secondary'>{t('app.accessControlDialog.accessItems.organization')}</p> + </> + } + {appDetail?.access_mode === AccessMode.SPECIFIC_GROUPS_MEMBERS + && <> + <RiLockLine className='h-4 w-4 shrink-0 text-text-secondary' /> + <div className='grow truncate'> + <span className='system-sm-medium text-text-secondary'>{t('app.accessControlDialog.accessItems.specific')}</span> + </div> + </> + } + {appDetail?.access_mode === AccessMode.PUBLIC + && <> + <RiGlobalLine className='h-4 w-4 shrink-0 text-text-secondary' /> + <p className='system-sm-medium text-text-secondary'>{t('app.accessControlDialog.accessItems.anyone')}</p> + </> + } + {appDetail?.access_mode === AccessMode.EXTERNAL_MEMBERS + && <> + <RiVerifiedBadgeLine className='h-4 w-4 shrink-0 text-text-secondary' /> + <p className='system-sm-medium text-text-secondary'>{t('app.accessControlDialog.accessItems.external')}</p> + </> + } + </div> + {!isAppAccessSet && <p className='system-xs-regular shrink-0 text-text-tertiary'>{t('app.publishApp.notSet')}</p>} + <div className='flex h-4 w-4 shrink-0 items-center justify-center'> + <RiArrowRightSLine className='h-4 w-4 text-text-quaternary' /> + </div> + </div> + {!isAppAccessSet && <p className='system-xs-regular mt-1 text-text-warning'>{t('app.publishApp.notSetDesc')}</p>} + </div>} + <div className='flex flex-col gap-y-1 border-t-[0.5px] border-t-divider-regular p-4 pt-3'> + <Tooltip triggerClassName='flex' disabled={!systemFeatures.webapp_auth.enabled || userCanAccessApp?.result} popupContent={t('app.noAccessPermission')} asChild={false}> + <SuggestedAction + className='flex-1' + disabled={!publishedAt || (systemFeatures.webapp_auth.enabled && !userCanAccessApp?.result)} + link={appURL} + icon={<RiPlayCircleLine className='h-4 w-4' />} + > + {t('workflow.common.runApp')} + </SuggestedAction> + </Tooltip> + {appDetail?.mode === 'workflow' || appDetail?.mode === 'completion' + ? ( + <Tooltip triggerClassName='flex' disabled={!systemFeatures.webapp_auth.enabled || userCanAccessApp?.result} popupContent={t('app.noAccessPermission')} asChild={false}> + <SuggestedAction + className='flex-1' + disabled={!publishedAt || (systemFeatures.webapp_auth.enabled && !userCanAccessApp?.result)} + link={`${appURL}${appURL.includes('?') ? '&' : '?'}mode=batch`} + icon={<RiPlayList2Line className='h-4 w-4' />} + > + {t('workflow.common.batchRunApp')} + </SuggestedAction> + </Tooltip> + ) + : ( + <SuggestedAction + onClick={() => { + setEmbeddingModalOpen(true) + handleTrigger() + }} + disabled={!publishedAt} + icon={<CodeBrowser className='h-4 w-4' />} + > + {t('workflow.common.embedIntoSite')} + </SuggestedAction> + )} + <Tooltip triggerClassName='flex' disabled={!systemFeatures.webapp_auth.enabled || userCanAccessApp?.result} popupContent={t('app.noAccessPermission')} asChild={false}> + <SuggestedAction + className='flex-1' + onClick={() => { + publishedAt && handleOpenInExplore() + }} + disabled={!publishedAt || (systemFeatures.webapp_auth.enabled && !userCanAccessApp?.result)} + icon={<RiPlanetLine className='h-4 w-4' />} + > + {t('workflow.common.openInExplore')} + </SuggestedAction> + </Tooltip> + <SuggestedAction disabled={!publishedAt} - icon={<CodeBrowser className='h-4 w-4' />} + link='./develop' + icon={<RiTerminalBoxLine className='h-4 w-4' />} > - {t('workflow.common.embedIntoSite')} + {t('workflow.common.accessAPIReference')} </SuggestedAction> - )} - <SuggestedAction - onClick={() => { - publishedAt && handleOpenInExplore() - }} - disabled={!publishedAt} - icon={<RiPlanetLine className='h-4 w-4' />} - > - {t('workflow.common.openInExplore')} - </SuggestedAction> - <SuggestedAction - disabled={!publishedAt} - link='./develop' - icon={<RiTerminalBoxLine className='h-4 w-4' />} - > - {t('workflow.common.accessAPIReference')} - </SuggestedAction> - {appDetail?.mode === 'workflow' && ( - <WorkflowToolConfigureButton - disabled={!publishedAt} - published={!!toolPublished} - detailNeedUpdate={!!toolPublished && published} - workflowAppId={appDetail?.id} - icon={{ - content: (appDetail.icon_type === 'image' ? '🤖' : appDetail?.icon) || '🤖', - background: (appDetail.icon_type === 'image' ? appDefaultIconBackground : appDetail?.icon_background) || appDefaultIconBackground, - }} - name={appDetail?.name} - description={appDetail?.description} - inputs={inputs} - handlePublish={handlePublish} - onRefreshData={onRefreshData} - /> - )} - </div> + {appDetail?.mode === 'workflow' && ( + <WorkflowToolConfigureButton + disabled={!publishedAt} + published={!!toolPublished} + detailNeedUpdate={!!toolPublished && published} + workflowAppId={appDetail?.id} + icon={{ + content: (appDetail.icon_type === 'image' ? '🤖' : appDetail?.icon) || '🤖', + background: (appDetail.icon_type === 'image' ? appDefaultIconBackground : appDetail?.icon_background) || appDefaultIconBackground, + }} + name={appDetail?.name} + description={appDetail?.description} + inputs={inputs} + handlePublish={handlePublish} + onRefreshData={onRefreshData} + /> + )} + </div> + </>} </div> </PortalToFollowElemContent> <EmbeddedModal @@ -296,9 +396,9 @@ const AppPublisher = ({ appBaseUrl={appBaseURL} accessToken={accessToken} /> + {showAppAccessControl && <AccessControl app={appDetail!} onConfirm={handleAccessControlUpdate} onClose={() => { setShowAppAccessControl(false) }} />} </PortalToFollowElem > - </> - ) + </>) } export default memo(AppPublisher) diff --git a/web/app/components/app/app-publisher/suggested-action.tsx b/web/app/components/app/app-publisher/suggested-action.tsx index 388fb8bc76..8d4ab3d39c 100644 --- a/web/app/components/app/app-publisher/suggested-action.tsx +++ b/web/app/components/app/app-publisher/suggested-action.tsx @@ -8,22 +8,30 @@ export type SuggestedActionProps = PropsWithChildren<HTMLProps<HTMLAnchorElement disabled?: boolean }> -const SuggestedAction = ({ icon, link, disabled, children, className, ...props }: SuggestedActionProps) => ( - <a - href={disabled ? undefined : link} - target='_blank' - rel='noreferrer' - className={classNames( - 'flex justify-start items-center gap-2 py-2 px-2.5 bg-background-section-burn rounded-lg transition-colors [&:not(:first-child)]:mt-1', - disabled ? 'shadow-xs opacity-30 cursor-not-allowed' : 'text-text-secondary hover:bg-state-accent-hover hover:text-text-accent cursor-pointer', - className, - )} - {...props} - > - <div className='relative h-4 w-4'>{icon}</div> - <div className='system-sm-medium shrink grow basis-0'>{children}</div> - <RiArrowRightUpLine className='h-3.5 w-3.5' /> - </a> -) +const SuggestedAction = ({ icon, link, disabled, children, className, onClick, ...props }: SuggestedActionProps) => { + const handleClick = (e: React.MouseEvent<HTMLAnchorElement>) => { + if (disabled) + return + onClick?.(e) + } + return ( + <a + href={disabled ? undefined : link} + target='_blank' + rel='noreferrer' + className={classNames( + 'flex justify-start items-center gap-2 py-2 px-2.5 bg-background-section-burn rounded-lg text-text-secondary transition-colors [&:not(:first-child)]:mt-1', + disabled ? 'shadow-xs opacity-30 cursor-not-allowed' : 'text-text-secondary hover:bg-state-accent-hover hover:text-text-accent cursor-pointer', + className, + )} + onClick={handleClick} + {...props} + > + <div className='relative h-4 w-4'>{icon}</div> + <div className='system-sm-medium shrink grow basis-0'>{children}</div> + <RiArrowRightUpLine className='h-3.5 w-3.5' /> + </a> + ) +} export default SuggestedAction diff --git a/web/app/components/app/configuration/base/var-highlight/style.module.css b/web/app/components/app/configuration/base/var-highlight/style.module.css index cd5c8f8d77..2bcef0dabb 100644 --- a/web/app/components/app/configuration/base/var-highlight/style.module.css +++ b/web/app/components/app/configuration/base/var-highlight/style.module.css @@ -1,3 +1,3 @@ .item { background-color: rgba(21, 94, 239, 0.05); -} \ No newline at end of file +} diff --git a/web/app/components/app/configuration/base/warning-mask/style.module.css b/web/app/components/app/configuration/base/warning-mask/style.module.css index e1d6f10de9..87f226fd96 100644 --- a/web/app/components/app/configuration/base/warning-mask/style.module.css +++ b/web/app/components/app/configuration/base/warning-mask/style.module.css @@ -5,4 +5,4 @@ .icon { box-shadow: 0px 12px 16px -4px rgba(16, 24, 40, 0.08), 0px 4px 6px -2px rgba(16, 24, 40, 0.03); -} \ No newline at end of file +} diff --git a/web/app/components/app/configuration/config-prompt/conversation-history/history-panel.tsx b/web/app/components/app/configuration/config-prompt/conversation-history/history-panel.tsx index 78e8d8f506..94a02945bb 100644 --- a/web/app/components/app/configuration/config-prompt/conversation-history/history-panel.tsx +++ b/web/app/components/app/configuration/config-prompt/conversation-history/history-panel.tsx @@ -1,13 +1,11 @@ 'use client' import type { FC } from 'react' import React from 'react' -import { useContext } from 'use-context-selector' import { useTranslation } from 'react-i18next' import OperationBtn from '@/app/components/app/configuration/base/operation-btn' import Panel from '@/app/components/app/configuration/base/feature-panel' import { MessageClockCircle } from '@/app/components/base/icons/src/vender/solid/general' -import I18n from '@/context/i18n' -import { LanguagesSupported } from '@/i18n/language' +import { useDocLink } from '@/context/i18n' type Props = { showWarning: boolean @@ -19,7 +17,7 @@ const HistoryPanel: FC<Props> = ({ onShowEditModal, }) => { const { t } = useTranslation() - const { locale } = useContext(I18n) + const docLink = useDocLink() return ( <Panel @@ -45,9 +43,8 @@ const HistoryPanel: FC<Props> = ({ {showWarning && ( <div className='flex justify-between rounded-b-xl bg-background-section-burn px-3 py-2 text-xs text-text-secondary'> <div>{t('appDebug.feature.conversationHistory.tip')} - <a href={`${locale === LanguagesSupported[1] - ? 'https://docs.dify.ai/v/zh-hans/guides/application-design/prompt-engineering' - : 'https://docs.dify.ai/features/prompt-engineering'}`} + <a href={docLink('/learn-more/extended-reading/what-is-llmops', + { 'zh-Hans': '/learn-more/extended-reading/prompt-engineering/README' })} target='_blank' rel='noopener noreferrer' className='text-[#155EEF]'>{t('appDebug.feature.conversationHistory.learnMore')} </a> diff --git a/web/app/components/app/configuration/config-prompt/style.module.css b/web/app/components/app/configuration/config-prompt/style.module.css index 66785620b3..224d59d9c8 100644 --- a/web/app/components/app/configuration/config-prompt/style.module.css +++ b/web/app/components/app/configuration/config-prompt/style.module.css @@ -25,4 +25,4 @@ .boxHeader:hover .optionWrap { display: flex; -} \ No newline at end of file +} diff --git a/web/app/components/app/configuration/config-var/config-modal/index.tsx b/web/app/components/app/configuration/config-var/config-modal/index.tsx index 4b6bfdacd6..29cbc55b90 100644 --- a/web/app/components/app/configuration/config-var/config-modal/index.tsx +++ b/web/app/components/app/configuration/config-var/config-modal/index.tsx @@ -234,9 +234,14 @@ const ConfigModal: FC<IConfigModalProps> = ({ )} <div className='!mt-5 flex h-6 items-center space-x-2'> - <Checkbox checked={tempPayload.required} onCheck={() => handlePayloadChange('required')(!tempPayload.required)} /> + <Checkbox checked={tempPayload.required} disabled={tempPayload.hide} onCheck={() => handlePayloadChange('required')(!tempPayload.required)} /> <span className='system-sm-semibold text-text-secondary'>{t('appDebug.variableConfig.required')}</span> </div> + + <div className='!mt-5 flex h-6 items-center space-x-2'> + <Checkbox checked={tempPayload.hide} disabled={tempPayload.required} onCheck={() => handlePayloadChange('hide')(!tempPayload.hide)} /> + <span className='system-sm-semibold text-text-secondary'>{t('appDebug.variableConfig.hide')}</span> + </div> </div> </div> <ModalFoot diff --git a/web/app/components/app/configuration/config/agent/agent-tools/index.tsx b/web/app/components/app/configuration/config/agent/agent-tools/index.tsx index 4b773c01ba..a3149447d4 100644 --- a/web/app/components/app/configuration/config/agent/agent-tools/index.tsx +++ b/web/app/components/app/configuration/config/agent/agent-tools/index.tsx @@ -1,6 +1,6 @@ 'use client' import type { FC } from 'react' -import React, { useMemo, useState } from 'react' +import React, { useCallback, useMemo, useState } from 'react' import { useTranslation } from 'react-i18next' import { useContext } from 'use-context-selector' import copy from 'copy-to-clipboard' @@ -32,6 +32,7 @@ import cn from '@/utils/classnames' import ToolPicker from '@/app/components/workflow/block-selector/tool-picker' import type { ToolDefaultValue } from '@/app/components/workflow/block-selector/types' import { canFindTool } from '@/utils' +import { useMittContextSelector } from '@/context/mitt-context' type AgentToolWithMoreInfo = AgentTool & { icon: any; collection?: Collection } | null const AgentTools: FC = () => { @@ -39,7 +40,6 @@ const AgentTools: FC = () => { const [isShowChooseTool, setIsShowChooseTool] = useState(false) const { modelConfig, setModelConfig, collectionList } = useContext(ConfigContext) const formattingChangedDispatcher = useFormattingChangedDispatcher() - const [currentTool, setCurrentTool] = useState<AgentToolWithMoreInfo>(null) const currentCollection = useMemo(() => { if (!currentTool) return null @@ -61,6 +61,17 @@ const AgentTools: FC = () => { collection, } }) + const useSubscribe = useMittContextSelector(s => s.useSubscribe) + const handleUpdateToolsWhenInstallToolSuccess = useCallback((installedPluginNames: string[]) => { + const newModelConfig = produce(modelConfig, (draft) => { + draft.agentConfig.tools.forEach((item: any) => { + if (item.isDeleted && installedPluginNames.includes(item.provider_id)) + item.isDeleted = false + }) + }) + setModelConfig(newModelConfig) + }, [modelConfig, setModelConfig]) + useSubscribe('plugin:install:success', handleUpdateToolsWhenInstallToolSuccess as any) const handleToolSettingChange = (value: Record<string, any>) => { const newModelConfig = produce(modelConfig, (draft) => { @@ -132,7 +143,7 @@ const AgentTools: FC = () => { disabled={false} supportAddCustomTool onSelect={handleSelectTool} - selectedTools={tools} + selectedTools={tools as any} /> </> )} diff --git a/web/app/components/app/configuration/config/automatic/style.module.css b/web/app/components/app/configuration/config/automatic/style.module.css index 7ad3180a5a..15bedd84ca 100644 --- a/web/app/components/app/configuration/config/automatic/style.module.css +++ b/web/app/components/app/configuration/config/automatic/style.module.css @@ -4,4 +4,4 @@ -webkit-text-fill-color: transparent; background-clip: text; text-fill-color: transparent; -} \ No newline at end of file +} diff --git a/web/app/components/app/configuration/ctrl-btn-group/style.module.css b/web/app/components/app/configuration/ctrl-btn-group/style.module.css index c7250b8f96..3e874868a9 100644 --- a/web/app/components/app/configuration/ctrl-btn-group/style.module.css +++ b/web/app/components/app/configuration/ctrl-btn-group/style.module.css @@ -3,4 +3,4 @@ right: -16px; bottom: -16px; border-top: 1px solid #F3F4F6; -} \ No newline at end of file +} diff --git a/web/app/components/app/configuration/dataset-config/card-item/item.tsx b/web/app/components/app/configuration/dataset-config/card-item/item.tsx index 65ad2ca941..4feba8b01e 100644 --- a/web/app/components/app/configuration/dataset-config/card-item/item.tsx +++ b/web/app/components/app/configuration/dataset-config/card-item/item.tsx @@ -12,6 +12,7 @@ import { DataSourceType } from '@/models/datasets' import FileIcon from '@/app/components/base/file-icon' import { Folder } from '@/app/components/base/icons/src/vender/solid/files' import { Globe06 } from '@/app/components/base/icons/src/vender/solid/mapsAndTravel' +import ActionButton, { ActionButtonState } from '@/app/components/base/action-button' import Drawer from '@/app/components/base/drawer' import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints' import Badge from '@/app/components/base/badge' @@ -47,56 +48,66 @@ const Item: FC<ItemProps> = ({ const [isDeleting, setIsDeleting] = useState(false) return ( - <div className={cn('group relative mb-1 flex w-full items-center rounded-lg border-[0.5px] border-components-panel-border-subtle bg-components-panel-on-panel-item-bg py-2 pl-2.5 pr-3 shadow-xs last-of-type:mb-0 hover:bg-components-panel-on-panel-item-bg-hover hover:shadow-sm', isDeleting && 'border-state-destructive-border hover:bg-state-destructive-hover')}> - { - config.data_source_type === DataSourceType.FILE && ( - <div className='mr-2 flex h-6 w-6 shrink-0 items-center justify-center rounded-md border-[0.5px] border-[#E0EAFF] bg-[#F5F8FF]'> - <Folder className='h-4 w-4 text-[#444CE7]' /> - </div> - ) - } - { - config.data_source_type === DataSourceType.NOTION && ( - <div className='mr-2 flex h-6 w-6 shrink-0 items-center justify-center rounded-md border-[0.5px] border-[#EAECF5]'> - <FileIcon type='notion' className='h-4 w-4' /> - </div> - ) - } - { - config.data_source_type === DataSourceType.WEB && ( - <div className='mr-2 flex h-6 w-6 shrink-0 items-center justify-center rounded-md border-[0.5px] border-blue-100 bg-[#F5FAFF]'> - <Globe06 className='h-4 w-4 text-blue-600' /> - </div> - ) - } - <div className='grow'> - <div className='flex h-[18px] items-center'> - <div className='grow truncate text-[13px] font-medium text-text-secondary' title={config.name}>{config.name}</div> - {config.provider === 'external' - ? <Badge text={t('dataset.externalTag') as string} /> - : <Badge - text={formatIndexingTechniqueAndMethod(config.indexing_technique, config.retrieval_model_dict?.search_method)} - />} - </div> - </div > - <div className='absolute bottom-0 right-0 top-0 hidden w-[124px] items-center justify-end rounded-lg bg-gradient-to-r from-white/50 to-white to-50% pr-2 group-hover:flex'> + <div className={cn( + 'group relative mb-1 flex h-10 w-full cursor-pointer items-center justify-between rounded-lg border-[0.5px] border-components-panel-border-subtle bg-components-panel-on-panel-item-bg px-2 last-of-type:mb-0 hover:bg-components-panel-on-panel-item-bg-hover', + isDeleting && 'border-state-destructive-border hover:bg-state-destructive-hover', + )}> + <div className='flex w-0 grow items-center space-x-1.5'> + { + config.data_source_type === DataSourceType.FILE && ( + <div className='mr-2 flex h-6 w-6 shrink-0 items-center justify-center rounded-md border-[0.5px] border-[#E0EAFF] bg-[#F5F8FF]'> + <Folder className='h-4 w-4 text-[#444CE7]' /> + </div> + ) + } + { + config.data_source_type === DataSourceType.NOTION && ( + <div className='mr-2 flex h-6 w-6 shrink-0 items-center justify-center rounded-md border-[0.5px] border-[#EAECF5]'> + <FileIcon type='notion' className='h-4 w-4' /> + </div> + ) + } { - editable && <div - className='mr-1 flex h-6 w-6 cursor-pointer items-center justify-center rounded-md hover:bg-black/5' - onClick={() => setShowSettingsModal(true)} + config.data_source_type === DataSourceType.WEB && ( + <div className='mr-2 flex h-6 w-6 shrink-0 items-center justify-center rounded-md border-[0.5px] border-blue-100 bg-[#F5FAFF]'> + <Globe06 className='h-4 w-4 text-blue-600' /> + </div> + ) + } + <div className='system-sm-medium w-0 grow truncate text-text-secondary' title={config.name}>{config.name}</div> + </div> + <div className='ml-2 hidden shrink-0 items-center space-x-1 group-hover:flex'> + { + editable && <ActionButton + onClick={(e) => { + e.stopPropagation() + setShowSettingsModal(true) + }} > - <RiEditLine className='h-4 w-4 text-text-tertiary' /> - </div> + <RiEditLine className='h-4 w-4 shrink-0 text-text-tertiary' /> + </ActionButton> } - <div - className='flex h-6 w-6 cursor-pointer items-center justify-center text-text-tertiary hover:text-text-destructive' + <ActionButton onClick={() => onRemove(config.id)} - onMouseOver={() => setIsDeleting(true)} + state={isDeleting ? ActionButtonState.Destructive : ActionButtonState.Default} + onMouseEnter={() => setIsDeleting(true)} onMouseLeave={() => setIsDeleting(false)} > - <RiDeleteBinLine className='h-4 w-4' /> - </div> + <RiDeleteBinLine className={cn('h-4 w-4 shrink-0 text-text-tertiary', isDeleting && 'text-text-destructive')} /> + </ActionButton> </div> + { + config.indexing_technique && <Badge + className='shrink-0 group-hover:hidden' + text={formatIndexingTechniqueAndMethod(config.indexing_technique, config.retrieval_model_dict?.search_method)} + /> + } + { + config.provider === 'external' && <Badge + className='shrink-0 group-hover:hidden' + text={t('dataset.externalTag') as string} + /> + } <Drawer isOpen={showSettingsModal} onClose={() => setShowSettingsModal(false)} footer={null} mask={isMobile} panelClassName='mt-16 mx-2 sm:mr-2 mb-3 !p-0 !max-w-[640px] rounded-xl'> <SettingsModal currentDataset={config} diff --git a/web/app/components/app/configuration/dataset-config/card-item/style.module.css b/web/app/components/app/configuration/dataset-config/card-item/style.module.css index 4ddec9ea37..da07056cbc 100644 --- a/web/app/components/app/configuration/dataset-config/card-item/style.module.css +++ b/web/app/components/app/configuration/dataset-config/card-item/style.module.css @@ -19,4 +19,4 @@ .settingBtn:hover { background-color: rgba(0, 0, 0, 0.05); -} \ No newline at end of file +} diff --git a/web/app/components/app/configuration/dataset-config/params-config/weighted-score.css b/web/app/components/app/configuration/dataset-config/params-config/weighted-score.css index 5bfea0cac9..ef9350645a 100644 --- a/web/app/components/app/configuration/dataset-config/params-config/weighted-score.css +++ b/web/app/components/app/configuration/dataset-config/params-config/weighted-score.css @@ -4,4 +4,4 @@ .weightedScoreSliderTrack-1 { background: transparent !important; -} \ No newline at end of file +} diff --git a/web/app/components/app/configuration/dataset-config/select-dataset/index.tsx b/web/app/components/app/configuration/dataset-config/select-dataset/index.tsx index 64a7f6dc46..ffdb714f08 100644 --- a/web/app/components/app/configuration/dataset-config/select-dataset/index.tsx +++ b/web/app/components/app/configuration/dataset-config/select-dataset/index.tsx @@ -14,6 +14,7 @@ import Loading from '@/app/components/base/loading' import Badge from '@/app/components/base/badge' import { useKnowledge } from '@/hooks/use-knowledge' import cn from '@/utils/classnames' +import { basePath } from '@/utils/var' export type ISelectDataSetProps = { isShow: boolean @@ -111,7 +112,7 @@ const SelectDataSet: FC<ISelectDataSetProps> = ({ }} > <span className='text-text-tertiary'>{t('appDebug.feature.dataSet.noDataSet')}</span> - <Link href="/datasets/create" className='font-normal text-text-accent'>{t('appDebug.feature.dataSet.toCreate')}</Link> + <Link href={`${basePath}/datasets/create`} className='font-normal text-text-accent'>{t('appDebug.feature.dataSet.toCreate')}</Link> </div> )} diff --git a/web/app/components/app/configuration/dataset-config/settings-modal/index.tsx b/web/app/components/app/configuration/dataset-config/settings-modal/index.tsx index 645f6045f0..9835481ae0 100644 --- a/web/app/components/app/configuration/dataset-config/settings-modal/index.tsx +++ b/web/app/components/app/configuration/dataset-config/settings-modal/index.tsx @@ -4,7 +4,6 @@ import { useMount } from 'ahooks' import { useTranslation } from 'react-i18next' import { isEqual } from 'lodash-es' import { RiCloseLine } from '@remixicon/react' -import { BookOpenIcon } from '@heroicons/react/24/outline' import { ApiConnectionMod } from '@/app/components/base/icons/src/vender/solid/development' import cn from '@/utils/classnames' import IndexMethodRadio from '@/app/components/datasets/settings/index-method-radio' @@ -32,6 +31,7 @@ import { import { ModelTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations' import { fetchMembers } from '@/service/common' import type { Member } from '@/models/common' +import { useDocLink } from '@/context/i18n' type SettingsModalProps = { currentDataset: DataSet @@ -59,6 +59,7 @@ const SettingsModal: FC<SettingsModalProps> = ({ currentModel: isRerankDefaultModelValid, } = useModelListAndDefaultModelAndCurrentProviderAndModel(ModelTypeEnum.rerank) const { t } = useTranslation() + const docLink = useDocLink() const { notify } = useToastContext() const ref = useRef(null) const isExternal = currentDataset.provider === 'external' @@ -223,10 +224,6 @@ const SettingsModal: FC<SettingsModalProps> = ({ className='resize-none' placeholder={t('datasetSettings.form.descPlaceholder') || ''} /> - <a className='mt-2 flex h-[18px] items-center px-3 text-xs text-text-tertiary' href="https://docs.dify.ai/features/datasets#how-to-write-a-good-dataset-description" target='_blank' rel='noopener noreferrer'> - <BookOpenIcon className='mr-1 h-[18px] w-3' /> - {t('datasetSettings.form.descWrite')} - </a> </div> </div> <div className={rowClass}> @@ -333,7 +330,7 @@ const SettingsModal: FC<SettingsModalProps> = ({ <div> <div className='system-sm-semibold text-text-secondary'>{t('datasetSettings.form.retrievalSetting.title')}</div> <div className='text-xs font-normal leading-[18px] text-text-tertiary'> - <a target='_blank' rel='noopener noreferrer' href='https://docs.dify.ai/guides/knowledge-base/create-knowledge-and-upload-documents#id-4-retrieval-settings' className='text-text-accent'>{t('datasetSettings.form.retrievalSetting.learnMore')}</a> + <a target='_blank' rel='noopener noreferrer' href={docLink('/guides/knowledge-base/create-knowledge-and-upload-documents/setting-indexing-methods#setting-the-retrieval-setting')} className='text-text-accent'>{t('datasetSettings.form.retrievalSetting.learnMore')}</a> {t('datasetSettings.form.retrievalSetting.description')} </div> </div> diff --git a/web/app/components/app/configuration/debug/debug-with-multiple-model/index.tsx b/web/app/components/app/configuration/debug/debug-with-multiple-model/index.tsx index 75ba8362ae..b876adfa3d 100644 --- a/web/app/components/app/configuration/debug/debug-with-multiple-model/index.tsx +++ b/web/app/components/app/configuration/debug/debug-with-multiple-model/index.tsx @@ -99,7 +99,15 @@ const DebugWithMultipleModel = () => { }, [twoLine, threeLine, fourLine]) const setShowAppConfigureFeaturesModal = useAppStore(s => s.setShowAppConfigureFeaturesModal) - const inputsForm = modelConfig.configs.prompt_variables.filter(item => item.type !== 'api').map(item => ({ ...item, label: item.name, variable: item.key })) as InputForm[] + const inputsForm = modelConfig.configs.prompt_variables + .filter(item => item.type !== 'api') + .map(item => ({ + ...item, + label: item.name, + variable: item.key, + hide: item.hide ?? false, + required: item.required ?? false, + })) as InputForm[] return ( <div className='flex h-full flex-col'> @@ -133,6 +141,7 @@ const DebugWithMultipleModel = () => { {isChatMode && ( <div className='shrink-0 px-6 pb-0'> <ChatInputArea + botName='Bot' showFeatureBar showFileUpload={false} onFeatureBarClick={setShowAppConfigureFeaturesModal} diff --git a/web/app/components/app/configuration/index.tsx b/web/app/components/app/configuration/index.tsx index 5b8f8659f8..2b97a64f5b 100644 --- a/web/app/components/app/configuration/index.tsx +++ b/web/app/components/app/configuration/index.tsx @@ -79,6 +79,7 @@ import { } from '@/utils' import PluginDependency from '@/app/components/workflow/plugin-dependency' import { supportFunctionCall } from '@/utils/tool-call' +import { MittProvider } from '@/context/mitt-context' type PublishConfig = { modelConfig: ModelConfig @@ -908,7 +909,7 @@ const Configuration: FC = () => { }} > <FeaturesProvider features={featuresData}> - <> + <MittProvider> <div className="flex h-full flex-col"> <div className='relative flex h-[200px] grow pt-14'> {/* Header */} @@ -1060,7 +1061,7 @@ const Configuration: FC = () => { /> )} <PluginDependency /> - </> + </MittProvider> </FeaturesProvider> </ConfigContext.Provider> ) diff --git a/web/app/components/app/configuration/prompt-mode/advanced-mode-waring.tsx b/web/app/components/app/configuration/prompt-mode/advanced-mode-waring.tsx index d1cd25e80a..f207cddd16 100644 --- a/web/app/components/app/configuration/prompt-mode/advanced-mode-waring.tsx +++ b/web/app/components/app/configuration/prompt-mode/advanced-mode-waring.tsx @@ -2,9 +2,7 @@ import type { FC } from 'react' import React from 'react' import { useTranslation } from 'react-i18next' -import { useContext } from 'use-context-selector' -import I18n from '@/context/i18n' -import { LanguagesSupported } from '@/i18n/language' +import { useDocLink } from '@/context/i18n' type Props = { onReturnToSimpleMode: () => void } @@ -13,7 +11,7 @@ const AdvancedModeWarning: FC<Props> = ({ onReturnToSimpleMode, }) => { const { t } = useTranslation() - const { locale } = useContext(I18n) + const docLink = useDocLink() const [show, setShow] = React.useState(true) if (!show) return null @@ -25,7 +23,7 @@ const AdvancedModeWarning: FC<Props> = ({ <span className='text-gray-700'>{t('appDebug.promptMode.advancedWarning.description')}</span> <a className='font-medium text-[#155EEF]' - href={`https://docs.dify.ai/${locale === LanguagesSupported[1] ? 'v/zh-hans/guides/application-design/prompt-engineering' : 'features/prompt-engineering'}`} + href={docLink('/guides/features/prompt-engineering')} target='_blank' rel='noopener noreferrer' > {t('appDebug.promptMode.advancedWarning.learnMore')} diff --git a/web/app/components/app/configuration/style.module.css b/web/app/components/app/configuration/style.module.css index f0e57cefbf..01f2c93167 100644 --- a/web/app/components/app/configuration/style.module.css +++ b/web/app/components/app/configuration/style.module.css @@ -11,4 +11,4 @@ height: 3px; background-color: rgba(68, 76, 231, 0.18); transform: skewX(-30deg); -} \ No newline at end of file +} diff --git a/web/app/components/app/configuration/tools/index.tsx b/web/app/components/app/configuration/tools/index.tsx index cc34041ba3..ba586bb20d 100644 --- a/web/app/components/app/configuration/tools/index.tsx +++ b/web/app/components/app/configuration/tools/index.tsx @@ -87,7 +87,7 @@ const Tools = () => { <div className='flex grow items-center'> <div className={` - group mr-1 flex h-6 w-6 items-center justify-center rounded-md + group mr-1 flex h-6 w-6 items-center justify-center rounded-md ${externalDataToolsConfig.length && 'hover:bg-white hover:shadow-xs'} `} onClick={() => setExpanded(v => !v)} diff --git a/web/app/components/app/create-app-dialog/app-list/index.tsx b/web/app/components/app/create-app-dialog/app-list/index.tsx index 702a07397d..0b0b325d9a 100644 --- a/web/app/components/app/create-app-dialog/app-list/index.tsx +++ b/web/app/components/app/create-app-dialog/app-list/index.tsx @@ -191,14 +191,16 @@ const Apps = ({ </div> <div className='relative flex flex-1 overflow-y-auto'> {!searchKeywords && <div className='h-full w-[200px] p-4'> - <Sidebar current={currCategory as AppCategories} onClick={(category) => { setCurrCategory(category) }} onCreateFromBlank={onCreateFromBlank} /> + <Sidebar current={currCategory as AppCategories} categories={categories} onClick={(category) => { setCurrCategory(category) }} onCreateFromBlank={onCreateFromBlank} /> </div>} <div className='h-full flex-1 shrink-0 grow overflow-auto border-l border-divider-burn p-6 pt-2'> {searchFilteredList && searchFilteredList.length > 0 && <> <div className='pb-1 pt-4'> {searchKeywords ? <p className='title-md-semi-bold text-text-tertiary'>{searchFilteredList.length > 1 ? t('app.newApp.foundResults', { count: searchFilteredList.length }) : t('app.newApp.foundResult', { count: searchFilteredList.length })}</p> - : <AppCategoryLabel category={currCategory as AppCategories} className='title-md-semi-bold text-text-primary' />} + : <div className='flex h-[22px] items-center'> + <AppCategoryLabel category={currCategory as AppCategories} className='title-md-semi-bold text-text-primary' /> + </div>} </div> <div className={cn( diff --git a/web/app/components/app/create-app-dialog/app-list/sidebar.tsx b/web/app/components/app/create-app-dialog/app-list/sidebar.tsx index ee843675a5..346de078b4 100644 --- a/web/app/components/app/create-app-dialog/app-list/sidebar.tsx +++ b/web/app/components/app/create-app-dialog/app-list/sidebar.tsx @@ -1,39 +1,29 @@ 'use client' -import { RiAppsFill, RiChatSmileAiFill, RiExchange2Fill, RiPassPendingFill, RiQuillPenAiFill, RiSpeakAiFill, RiStickyNoteAddLine, RiTerminalBoxFill, RiThumbUpFill } from '@remixicon/react' +import { RiStickyNoteAddLine, RiThumbUpLine } from '@remixicon/react' import { useTranslation } from 'react-i18next' import classNames from '@/utils/classnames' import Divider from '@/app/components/base/divider' export enum AppCategories { RECOMMENDED = 'Recommended', - ASSISTANT = 'Assistant', - AGENT = 'Agent', - HR = 'HR', - PROGRAMMING = 'Programming', - WORKFLOW = 'Workflow', - WRITING = 'Writing', } type SidebarProps = { - current: AppCategories - onClick?: (category: AppCategories) => void + current: AppCategories | string + categories: string[] + onClick?: (category: AppCategories | string) => void onCreateFromBlank?: () => void } -export default function Sidebar({ current, onClick, onCreateFromBlank }: SidebarProps) { +export default function Sidebar({ current, categories, onClick, onCreateFromBlank }: SidebarProps) { const { t } = useTranslation() return <div className="flex h-full w-full flex-col"> - <ul> + <ul className='pt-0.5'> <CategoryItem category={AppCategories.RECOMMENDED} active={current === AppCategories.RECOMMENDED} onClick={onClick} /> </ul> - <div className='system-xs-medium-uppercase px-3 pb-1 pt-2 text-text-tertiary'>{t('app.newAppFromTemplate.byCategories')}</div> + <div className='system-xs-medium-uppercase mb-0.5 mt-3 px-3 pb-1 pt-2 text-text-tertiary'>{t('app.newAppFromTemplate.byCategories')}</div> <ul className='flex grow flex-col gap-0.5'> - <CategoryItem category={AppCategories.ASSISTANT} active={current === AppCategories.ASSISTANT} onClick={onClick} /> - <CategoryItem category={AppCategories.AGENT} active={current === AppCategories.AGENT} onClick={onClick} /> - <CategoryItem category={AppCategories.HR} active={current === AppCategories.HR} onClick={onClick} /> - <CategoryItem category={AppCategories.PROGRAMMING} active={current === AppCategories.PROGRAMMING} onClick={onClick} /> - <CategoryItem category={AppCategories.WORKFLOW} active={current === AppCategories.WORKFLOW} onClick={onClick} /> - <CategoryItem category={AppCategories.WRITING} active={current === AppCategories.WRITING} onClick={onClick} /> + {categories.map(category => (<CategoryItem key={category} category={category} active={current === category} onClick={onClick} />))} </ul> <Divider bgStyle='gradient' /> <div className='flex cursor-pointer items-center gap-1 px-3 py-1 text-text-tertiary' onClick={onCreateFromBlank}> @@ -45,47 +35,26 @@ export default function Sidebar({ current, onClick, onCreateFromBlank }: Sidebar type CategoryItemProps = { active: boolean - category: AppCategories - onClick?: (category: AppCategories) => void + category: AppCategories | string + onClick?: (category: AppCategories | string) => void } function CategoryItem({ category, active, onClick }: CategoryItemProps) { return <li - className={classNames('p-1 pl-3 rounded-lg flex items-center gap-2 group cursor-pointer hover:bg-state-base-hover [&.active]:bg-state-base-active', active && 'active')} + className={classNames('p-1 pl-3 h-8 rounded-lg flex items-center gap-2 group cursor-pointer hover:bg-state-base-hover [&.active]:bg-state-base-active', active && 'active')} onClick={() => { onClick?.(category) }}> - <div className='inline-flex h-5 w-5 items-center justify-center rounded-md border border-divider-regular bg-components-icon-bg-midnight-solid group-[.active]:bg-components-icon-bg-blue-solid'> - <AppCategoryIcon category={category} /> - </div> + {category === AppCategories.RECOMMENDED && <div className='inline-flex h-5 w-5 items-center justify-center rounded-md'> + <RiThumbUpLine className='h-4 w-4 text-components-menu-item-text group-[.active]:text-components-menu-item-text-active' /> + </div>} <AppCategoryLabel category={category} className={classNames('system-sm-medium text-components-menu-item-text group-[.active]:text-components-menu-item-text-active group-hover:text-components-menu-item-text-hover', active && 'system-sm-semibold')} /> </li > } type AppCategoryLabelProps = { - category: AppCategories + category: AppCategories | string className?: string } export function AppCategoryLabel({ category, className }: AppCategoryLabelProps) { const { t } = useTranslation() - return <span className={className}>{t(`app.newAppFromTemplate.sidebar.${category}`)}</span> -} - -type AppCategoryIconProps = { - category: AppCategories -} -function AppCategoryIcon({ category }: AppCategoryIconProps) { - if (category === AppCategories.AGENT) - return <RiSpeakAiFill className='h-3.5 w-3.5 text-components-avatar-shape-fill-stop-100' /> - if (category === AppCategories.ASSISTANT) - return <RiChatSmileAiFill className='h-3.5 w-3.5 text-components-avatar-shape-fill-stop-100' /> - if (category === AppCategories.HR) - return <RiPassPendingFill className='h-3.5 w-3.5 text-components-avatar-shape-fill-stop-100' /> - if (category === AppCategories.PROGRAMMING) - return <RiTerminalBoxFill className='h-3.5 w-3.5 text-components-avatar-shape-fill-stop-100' /> - if (category === AppCategories.RECOMMENDED) - return <RiThumbUpFill className='h-3.5 w-3.5 text-components-avatar-shape-fill-stop-100' /> - if (category === AppCategories.WRITING) - return <RiQuillPenAiFill className='h-3.5 w-3.5 text-components-avatar-shape-fill-stop-100' /> - if (category === AppCategories.WORKFLOW) - return <RiExchange2Fill className='h-3.5 w-3.5 text-components-avatar-shape-fill-stop-100' /> - return <RiAppsFill className='h-3.5 w-3.5 text-components-avatar-shape-fill-stop-100' /> + return <span className={className}>{category === AppCategories.RECOMMENDED ? t('app.newAppFromTemplate.sidebar.Recommended') : category}</span> } diff --git a/web/app/components/app/create-app-modal/index.tsx b/web/app/components/app/create-app-modal/index.tsx index 88bccc95af..f0a0da41a5 100644 --- a/web/app/components/app/create-app-modal/index.tsx +++ b/web/app/components/app/create-app-modal/index.tsx @@ -1,11 +1,11 @@ 'use client' -import { useCallback, useEffect, useRef, useState } from 'react' +import { useCallback, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' -import { useRouter, useSearchParams } from 'next/navigation' +import { useRouter } from 'next/navigation' import { useContext, useContextSelector } from 'use-context-selector' -import { RiArrowRightLine, RiCommandLine, RiCornerDownLeftLine, RiExchange2Fill } from '@remixicon/react' +import { RiArrowRightLine, RiArrowRightSLine, RiCommandLine, RiCornerDownLeftLine, RiExchange2Fill } from '@remixicon/react' import Link from 'next/link' import { useDebounceFn, useKeyPress } from 'ahooks' import Image from 'next/image' @@ -19,7 +19,6 @@ import AppsContext, { useAppContext } from '@/context/app-context' import { useProviderContext } from '@/context/provider-context' import { ToastContext } from '@/app/components/base/toast' import type { AppMode } from '@/types/app' -import { AppModes } from '@/types/app' import { createApp } from '@/service/apps' import Input from '@/app/components/base/input' import Textarea from '@/app/components/base/textarea' @@ -30,6 +29,7 @@ import { NEED_REFRESH_APP_LIST_KEY } from '@/config' import { getRedirection } from '@/utils/app-redirection' import FullScreenModal from '@/app/components/base/fullscreen-modal' import useTheme from '@/hooks/use-theme' +import { useDocLink } from '@/context/i18n' type CreateAppProps = { onSuccess: () => void @@ -43,11 +43,12 @@ function CreateApp({ onClose, onSuccess, onCreateFromTemplate }: CreateAppProps) const { notify } = useContext(ToastContext) const mutateApps = useContextSelector(AppsContext, state => state.mutateApps) - const [appMode, setAppMode] = useState<AppMode>('chat') + const [appMode, setAppMode] = useState<AppMode>('advanced-chat') const [appIcon, setAppIcon] = useState<AppIconSelection>({ type: 'emoji', icon: '🤖', background: '#FFEAD5' }) const [showAppIconPicker, setShowAppIconPicker] = useState(false) const [name, setName] = useState('') const [description, setDescription] = useState('') + const [isAppTypeExpanded, setIsAppTypeExpanded] = useState(false) const { plan, enableBilling } = useProviderContext() const isAppsFull = (enableBilling && plan.usage.buildApps >= plan.total.buildApps) @@ -55,14 +56,6 @@ function CreateApp({ onClose, onSuccess, onCreateFromTemplate }: CreateAppProps) const isCreatingRef = useRef(false) - const searchParams = useSearchParams() - - useEffect(() => { - const category = searchParams.get('category') - if (category && AppModes.includes(category as AppMode)) - setAppMode(category as AppMode) - }, [searchParams]) - const onCreate = useCallback(async () => { if (!appMode) { notify({ type: 'error', message: t('app.newApp.appTypeRequired') }) @@ -116,47 +109,17 @@ function CreateApp({ onClose, onSuccess, onCreateFromTemplate }: CreateAppProps) </div> <div className='flex w-[660px] flex-col gap-4'> <div> - <div className='mb-2'> - <span className='system-2xs-medium-uppercase text-text-tertiary'>{t('app.newApp.forBeginners')}</span> - </div> <div className='flex flex-row gap-2'> <AppTypeCard - active={appMode === 'chat'} - title={t('app.types.chatbot')} - description={t('app.newApp.chatbotShortDescription')} - icon={<div className='flex h-6 w-6 items-center justify-center rounded-md bg-components-icon-bg-blue-solid'> - <ChatBot className='h-4 w-4 text-components-avatar-shape-fill-stop-100' /> - </div>} - onClick={() => { - setAppMode('chat') - }} /> - <AppTypeCard - active={appMode === 'agent-chat'} - title={t('app.types.agent')} - description={t('app.newApp.agentShortDescription')} - icon={<div className='flex h-6 w-6 items-center justify-center rounded-md bg-components-icon-bg-violet-solid'> - <Logic className='h-4 w-4 text-components-avatar-shape-fill-stop-100' /> - </div>} - onClick={() => { - setAppMode('agent-chat') - }} /> - <AppTypeCard - active={appMode === 'completion'} - title={t('app.newApp.completeApp')} - description={t('app.newApp.completionShortDescription')} - icon={<div className='flex h-6 w-6 items-center justify-center rounded-md bg-components-icon-bg-teal-solid'> - <ListSparkle className='h-4 w-4 text-components-avatar-shape-fill-stop-100' /> + active={appMode === 'workflow'} + title={t('app.types.workflow')} + description={t('app.newApp.workflowShortDescription')} + icon={<div className='flex h-6 w-6 items-center justify-center rounded-md bg-components-icon-bg-indigo-solid'> + <RiExchange2Fill className='h-4 w-4 text-components-avatar-shape-fill-stop-100' /> </div>} onClick={() => { - setAppMode('completion') + setAppMode('workflow') }} /> - </div> - </div> - <div> - <div className='mb-2'> - <span className='system-2xs-medium-uppercase text-text-tertiary'>{t('app.newApp.forAdvanced')}</span> - </div> - <div className='flex flex-row gap-2'> <AppTypeCard active={appMode === 'advanced-chat'} title={t('app.types.advanced')} @@ -167,18 +130,53 @@ function CreateApp({ onClose, onSuccess, onCreateFromTemplate }: CreateAppProps) onClick={() => { setAppMode('advanced-chat') }} /> - <AppTypeCard - active={appMode === 'workflow'} - title={t('app.types.workflow')} - description={t('app.newApp.workflowShortDescription')} - icon={<div className='flex h-6 w-6 items-center justify-center rounded-md bg-components-icon-bg-indigo-solid'> - <RiExchange2Fill className='h-4 w-4 text-components-avatar-shape-fill-stop-100' /> - </div>} - onClick={() => { - setAppMode('workflow') - }} /> </div> </div> + <div> + <div className='mb-2 flex items-center'> + <button + className='flex cursor-pointer items-center border-0 bg-transparent p-0' + onClick={() => setIsAppTypeExpanded(!isAppTypeExpanded)} + > + <span className='system-2xs-medium-uppercase text-text-tertiary'>{t('app.newApp.forBeginners')}</span> + <RiArrowRightSLine className={`ml-1 h-4 w-4 text-text-tertiary transition-transform ${isAppTypeExpanded ? 'rotate-90' : ''}`} /> + </button> + </div> + {isAppTypeExpanded && ( + <div className='flex flex-row gap-2'> + <AppTypeCard + active={appMode === 'chat'} + title={t('app.types.chatbot')} + description={t('app.newApp.chatbotShortDescription')} + icon={<div className='flex h-6 w-6 items-center justify-center rounded-md bg-components-icon-bg-blue-solid'> + <ChatBot className='h-4 w-4 text-components-avatar-shape-fill-stop-100' /> + </div>} + onClick={() => { + setAppMode('chat') + }} /> + <AppTypeCard + active={appMode === 'agent-chat'} + title={t('app.types.agent')} + description={t('app.newApp.agentShortDescription')} + icon={<div className='flex h-6 w-6 items-center justify-center rounded-md bg-components-icon-bg-violet-solid'> + <Logic className='h-4 w-4 text-components-avatar-shape-fill-stop-100' /> + </div>} + onClick={() => { + setAppMode('agent-chat') + }} /> + <AppTypeCard + active={appMode === 'completion'} + title={t('app.newApp.completeApp')} + description={t('app.newApp.completionShortDescription')} + icon={<div className='flex h-6 w-6 items-center justify-center rounded-md bg-components-icon-bg-teal-solid'> + <ListSparkle className='h-4 w-4 text-components-avatar-shape-fill-stop-100' /> + </div>} + onClick={() => { + setAppMode('completion') + }} /> + </div> + )} + </div> <Divider style={{ margin: 0 }} /> <div className='flex items-center space-x-3'> <div className='flex-1'> @@ -306,31 +304,41 @@ function AppTypeCard({ icon, title, description, active, onClick }: AppTypeCardP function AppPreview({ mode }: { mode: AppMode }) { const { t } = useTranslation() + const docLink = useDocLink() const modeToPreviewInfoMap = { 'chat': { title: t('app.types.chatbot'), description: t('app.newApp.chatbotUserDescription'), - link: 'https://docs.dify.ai/guides/application-orchestrate#application_type', + link: docLink('/guides/application-orchestrate/chatbot-application'), }, 'advanced-chat': { title: t('app.types.advanced'), description: t('app.newApp.advancedUserDescription'), - link: 'https://docs.dify.ai/guides/workflow', + link: docLink('/guides/workflow/README', { + 'zh-Hans': '/guides/workflow/readme', + 'ja-JP': '/guides/workflow/concepts', + }), }, 'agent-chat': { title: t('app.types.agent'), description: t('app.newApp.agentUserDescription'), - link: 'https://docs.dify.ai/guides/application-orchestrate/agent', + link: docLink('/guides/application-orchestrate/agent'), }, 'completion': { title: t('app.newApp.completeApp'), description: t('app.newApp.completionUserDescription'), - link: null, + link: docLink('/guides/application-orchestrate/text-generator', { + 'zh-Hans': '/guides/application-orchestrate/readme', + 'ja-JP': '/guides/application-orchestrate/README', + }), }, 'workflow': { title: t('app.types.workflow'), description: t('app.newApp.workflowUserDescription'), - link: 'https://docs.dify.ai/guides/workflow', + link: docLink('/guides/workflow/README', { + 'zh-Hans': '/guides/workflow/readme', + 'ja-JP': '/guides/workflow/concepts', + }), }, } const previewInfo = modeToPreviewInfoMap[mode] diff --git a/web/app/components/app/create-from-dsl-modal/dsl-confirm-modal.tsx b/web/app/components/app/create-from-dsl-modal/dsl-confirm-modal.tsx new file mode 100644 index 0000000000..e6aadaa326 --- /dev/null +++ b/web/app/components/app/create-from-dsl-modal/dsl-confirm-modal.tsx @@ -0,0 +1,46 @@ +import { useTranslation } from 'react-i18next' +import Modal from '@/app/components/base/modal' +import Button from '@/app/components/base/button' + +type DSLConfirmModalProps = { + versions?: { + importedVersion: string + systemVersion: string + } + onCancel: () => void + onConfirm: () => void + confirmDisabled?: boolean +} +const DSLConfirmModal = ({ + versions = { importedVersion: '', systemVersion: '' }, + onCancel, + onConfirm, + confirmDisabled = false, +}: DSLConfirmModalProps) => { + const { t } = useTranslation() + + return ( + <Modal + isShow + onClose={() => onCancel()} + className='w-[480px]' + > + <div className='flex flex-col items-start gap-2 self-stretch pb-4'> + <div className='title-2xl-semi-bold text-text-primary'>{t('app.newApp.appCreateDSLErrorTitle')}</div> + <div className='system-md-regular flex grow flex-col text-text-secondary'> + <div>{t('app.newApp.appCreateDSLErrorPart1')}</div> + <div>{t('app.newApp.appCreateDSLErrorPart2')}</div> + <br /> + <div>{t('app.newApp.appCreateDSLErrorPart3')}<span className='system-md-medium'>{versions.importedVersion}</span></div> + <div>{t('app.newApp.appCreateDSLErrorPart4')}<span className='system-md-medium'>{versions.systemVersion}</span></div> + </div> + </div> + <div className='flex items-start justify-end gap-2 self-stretch pt-6'> + <Button variant='secondary' onClick={() => onCancel()}>{t('app.newApp.Cancel')}</Button> + <Button variant='primary' destructive onClick={onConfirm} disabled={confirmDisabled}>{t('app.newApp.Confirm')}</Button> + </div> + </Modal> + ) +} + +export default DSLConfirmModal diff --git a/web/app/components/app/create-from-dsl-modal/index.tsx b/web/app/components/app/create-from-dsl-modal/index.tsx index c1df10ed64..9739ac47ea 100644 --- a/web/app/components/app/create-from-dsl-modal/index.tsx +++ b/web/app/components/app/create-from-dsl-modal/index.tsx @@ -262,7 +262,7 @@ const CreateFromDSLModal = ({ show, onSuccess, onClose, activeTab = CreateFromDS { currentTab === CreateFromDSLModalTab.FROM_URL && ( <div> - <div className='system-md-semibold leading6 mb-1'>DSL URL</div> + <div className='system-md-semibold mb-1 text-text-secondary'>DSL URL</div> <Input placeholder={t('app.importFromDSLUrlPlaceholder') || ''} value={dslUrlValue} diff --git a/web/app/components/app/create-from-dsl-modal/uploader.tsx b/web/app/components/app/create-from-dsl-modal/uploader.tsx index fb8334f7b2..6ad4116dd6 100644 --- a/web/app/components/app/create-from-dsl-modal/uploader.tsx +++ b/web/app/components/app/create-from-dsl-modal/uploader.tsx @@ -3,6 +3,7 @@ import type { FC } from 'react' import React, { useEffect, useRef, useState } from 'react' import { RiDeleteBinLine, + RiUploadCloud2Line, } from '@remixicon/react' import { useTranslation } from 'react-i18next' import { useContext } from 'use-context-selector' @@ -10,8 +11,7 @@ import { formatFileSize } from '@/utils/format' import cn from '@/utils/classnames' import { Yaml as YamlIcon } from '@/app/components/base/icons/src/public/files' import { ToastContext } from '@/app/components/base/toast' -import { UploadCloud01 } from '@/app/components/base/icons/src/vender/line/general' -import Button from '@/app/components/base/button' +import ActionButton from '@/app/components/base/action-button' export type Props = { file: File | undefined @@ -102,19 +102,19 @@ const Uploader: FC<Props> = ({ /> <div ref={dropRef}> {!file && ( - <div className={cn('flex h-12 items-center rounded-xl border border-dashed border-gray-200 bg-gray-50 text-sm font-normal', dragging && 'border border-[#B2CCFF] bg-[#F5F8FF]')}> + <div className={cn('flex h-12 items-center rounded-[10px] border border-dashed border-components-dropzone-border bg-components-dropzone-bg text-sm font-normal', dragging && 'border-components-dropzone-border-accent bg-components-dropzone-bg-accent')}> <div className='flex w-full items-center justify-center space-x-2'> - <UploadCloud01 className='mr-2 h-6 w-6' /> - <div className='text-gray-500'> + <RiUploadCloud2Line className='h-6 w-6 text-text-tertiary' /> + <div className='text-text-tertiary'> {t('datasetCreation.stepOne.uploader.button')} - <span className='cursor-pointer pl-1 text-[#155eef]' onClick={selectHandle}>{t('datasetDocuments.list.batchModal.browse')}</span> + <span className='cursor-pointer pl-1 text-text-accent' onClick={selectHandle}>{t('datasetDocuments.list.batchModal.browse')}</span> </div> </div> {dragging && <div ref={dragRef} className='absolute left-0 top-0 h-full w-full' />} </div> )} {file && ( - <div className={cn('group flex items-center rounded-lg border-[0.5px] border-components-panel-border bg-components-panel-on-panel-item-bg shadow-xs', 'hover:border-[#B2CCFF] hover:bg-[#F5F8FF]')}> + <div className={cn('group flex items-center rounded-lg border-[0.5px] border-components-panel-border bg-components-panel-on-panel-item-bg shadow-xs', ' hover:bg-components-panel-on-panel-item-bg-hover')}> <div className='flex items-center justify-center p-3'> <YamlIcon className="h-6 w-6 shrink-0" /> </div> @@ -126,12 +126,10 @@ const Uploader: FC<Props> = ({ <span>{formatFileSize(file.size)}</span> </div> </div> - <div className='hidden items-center group-hover:flex'> - <Button onClick={selectHandle}>{t('datasetCreation.stepOne.uploader.change')}</Button> - <div className='mx-2 h-4 w-px bg-gray-200' /> - <div className='cursor-pointer p-2' onClick={removeFile}> + <div className='hidden items-center pr-3 group-hover:flex'> + <ActionButton onClick={removeFile}> <RiDeleteBinLine className='h-4 w-4 text-text-tertiary' /> - </div> + </ActionButton> </div> </div> )} diff --git a/web/app/components/app/log/list.tsx b/web/app/components/app/log/list.tsx index 056ce84f1e..208fddecd1 100644 --- a/web/app/components/app/log/list.tsx +++ b/web/app/components/app/log/list.tsx @@ -32,7 +32,6 @@ import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints' import TextGeneration from '@/app/components/app/text-generate/item' import { addFileInfos, sortAgentSorts } from '@/app/components/tools/utils' import MessageLogModal from '@/app/components/base/message-log-modal' -import PromptLogModal from '@/app/components/base/prompt-log-modal' import { useStore as useAppStore } from '@/app/components/app/store' import { useAppContext } from '@/context/app-context' import useTimestamp from '@/hooks/use-timestamp' @@ -191,13 +190,11 @@ function DetailPanel({ detail, onFeedback }: IDetailPanel) { const { userProfile: { timezone } } = useAppContext() const { formatTime } = useTimestamp() const { onClose, appDetail } = useContext(DrawerContext) - const { currentLogItem, setCurrentLogItem, showMessageLogModal, setShowMessageLogModal, showPromptLogModal, setShowPromptLogModal, currentLogModalActiveTab } = useAppStore(useShallow(state => ({ + const { currentLogItem, setCurrentLogItem, showMessageLogModal, setShowMessageLogModal, currentLogModalActiveTab } = useAppStore(useShallow(state => ({ currentLogItem: state.currentLogItem, setCurrentLogItem: state.setCurrentLogItem, showMessageLogModal: state.showMessageLogModal, setShowMessageLogModal: state.setShowMessageLogModal, - showPromptLogModal: state.showPromptLogModal, - setShowPromptLogModal: state.setShowPromptLogModal, currentLogModalActiveTab: state.currentLogModalActiveTab, }))) const { t } = useTranslation() @@ -357,7 +354,8 @@ function DetailPanel({ detail, onFeedback }: IDetailPanel) { } useEffect(() => { - adjustModalWidth() + const raf = requestAnimationFrame(adjustModalWidth) + return () => cancelAnimationFrame(raf) }, []) return ( @@ -429,6 +427,7 @@ function DetailPanel({ detail, onFeedback }: IDetailPanel) { text_to_speech: { enabled: true, }, + questionEditEnable: false, supportAnnotation: true, annotation_reply: { enabled: true, @@ -484,6 +483,7 @@ function DetailPanel({ detail, onFeedback }: IDetailPanel) { text_to_speech: { enabled: true, }, + questionEditEnable: false, supportAnnotation: true, annotation_reply: { enabled: true, @@ -516,16 +516,6 @@ function DetailPanel({ detail, onFeedback }: IDetailPanel) { defaultTab={currentLogModalActiveTab} /> )} - {showPromptLogModal && ( - <PromptLogModal - width={width} - currentLogItem={currentLogItem} - onCancel={() => { - setCurrentLogItem() - setShowPromptLogModal(false) - }} - /> - )} </div> ) } diff --git a/web/app/components/app/overview/appCard.tsx b/web/app/components/app/overview/appCard.tsx index 7c12f1edee..9f3b3ac4a6 100644 --- a/web/app/components/app/overview/appCard.tsx +++ b/web/app/components/app/overview/appCard.tsx @@ -1,12 +1,17 @@ 'use client' -import React, { useMemo, useState } from 'react' +import React, { useCallback, useEffect, useMemo, useState } from 'react' import { usePathname, useRouter } from 'next/navigation' import { useTranslation } from 'react-i18next' import { + RiArrowRightSLine, RiBookOpenLine, + RiBuildingLine, RiEqualizer2Line, RiExternalLinkLine, + RiGlobalLine, + RiLockLine, RiPaintBrushLine, + RiVerifiedBadgeLine, RiWindowLine, } from '@remixicon/react' import SettingsModal from './settings' @@ -16,8 +21,9 @@ import style from './style.module.css' import type { ConfigParams } from './settings' import Tooltip from '@/app/components/base/tooltip' import AppBasic from '@/app/components/app-sidebar/basic' -import { asyncRunSafe, randomString } from '@/utils' +import { asyncRunSafe } from '@/utils' import { basePath } from '@/utils/var' +import { useStore as useAppStore } from '@/app/components/app/store' import Button from '@/app/components/base/button' import Switch from '@/app/components/base/switch' import Divider from '@/app/components/base/divider' @@ -29,6 +35,11 @@ import type { AppDetailResponse } from '@/models/app' import { useAppContext } from '@/context/app-context' import type { AppSSO } from '@/types/app' import Indicator from '@/app/components/header/indicator' +import { fetchAppDetail } from '@/service/apps' +import { AccessMode } from '@/models/access-control' +import AccessControl from '../app-access-control' +import { useAppWhiteListSubjects } from '@/service/access-control' +import { useGlobalPublicStore } from '@/context/global-public-context' export type IAppCardProps = { className?: string @@ -54,13 +65,17 @@ function AppCard({ const router = useRouter() const pathname = usePathname() const { isCurrentWorkspaceManager, isCurrentWorkspaceEditor } = useAppContext() + const appDetail = useAppStore(state => state.appDetail) + const setAppDetail = useAppStore(state => state.setAppDetail) const [showSettingsModal, setShowSettingsModal] = useState(false) const [showEmbedded, setShowEmbedded] = useState(false) const [showCustomizeModal, setShowCustomizeModal] = useState(false) const [genLoading, setGenLoading] = useState(false) const [showConfirmDelete, setShowConfirmDelete] = useState(false) - + const [showAccessControl, setShowAccessControl] = useState<boolean>(false) const { t } = useTranslation() + const systemFeatures = useGlobalPublicStore(s => s.systemFeatures) + const { data: appAccessSubjects } = useAppWhiteListSubjects(appDetail?.id, systemFeatures.webapp_auth.enabled && appDetail?.access_mode === AccessMode.SPECIFIC_GROUPS_MEMBERS) const OPERATIONS_MAP = useMemo(() => { const operationsMap = { @@ -128,6 +143,31 @@ function AppCard({ } } + const [isAppAccessSet, setIsAppAccessSet] = useState(true) + useEffect(() => { + if (appDetail && appAccessSubjects) { + if (appDetail.access_mode === AccessMode.SPECIFIC_GROUPS_MEMBERS && appAccessSubjects.groups?.length === 0 && appAccessSubjects.members?.length === 0) + setIsAppAccessSet(false) + else + setIsAppAccessSet(true) + } + else { + setIsAppAccessSet(true) + } + }, [appAccessSubjects, appDetail]) + + const handleClickAccessControl = useCallback(() => { + if (!appDetail) + return + setShowAccessControl(true) + }, [appDetail]) + const handleAccessControlUpdate = useCallback(() => { + fetchAppDetail({ url: '/apps', id: appDetail!.id }).then((res) => { + setAppDetail(res) + setShowAccessControl(false) + }) + }, [appDetail, setAppDetail]) + return ( <div className={ @@ -147,7 +187,7 @@ function AppCard({ : t('appOverview.overview.apiInfo.explanation') } /> - <div className='flex items-center gap-1'> + <div className='flex shrink-0 items-center gap-1'> <Indicator color={runningStatus ? 'green' : 'yellow'} /> <div className={`${runningStatus ? 'text-text-success' : 'text-text-warning'} system-xs-semibold-uppercase`}> {runningStatus @@ -173,7 +213,7 @@ function AppCard({ content={isApp ? appUrl : apiUrl} className={'!size-6'} /> - {isApp && <ShareQRCode content={isApp ? appUrl : apiUrl} className='z-50 !size-6 rounded-md hover:bg-state-base-hover' selectorId={randomString(8)} />} + {isApp && <ShareQRCode content={isApp ? appUrl : apiUrl} />} {isApp && <Divider type="vertical" className="!mx-0.5 !h-3.5 shrink-0" />} {/* button copy link/ button regenerate */} {showConfirmDelete && ( @@ -206,6 +246,41 @@ function AppCard({ )} </div> </div> + {isApp && systemFeatures.webapp_auth.enabled && appDetail && <div className='flex flex-col items-start justify-center self-stretch'> + <div className="system-xs-medium pb-1 text-text-tertiary">{t('app.publishApp.title')}</div> + <div className='flex h-9 w-full cursor-pointer items-center gap-x-0.5 rounded-lg bg-components-input-bg-normal py-1 pl-2.5 pr-2' + onClick={handleClickAccessControl}> + <div className='flex grow items-center gap-x-1.5 pr-1'> + {appDetail?.access_mode === AccessMode.ORGANIZATION + && <> + <RiBuildingLine className='h-4 w-4 shrink-0 text-text-secondary' /> + <p className='system-sm-medium text-text-secondary'>{t('app.accessControlDialog.accessItems.organization')}</p> + </> + } + {appDetail?.access_mode === AccessMode.SPECIFIC_GROUPS_MEMBERS + && <> + <RiLockLine className='h-4 w-4 shrink-0 text-text-secondary' /> + <p className='system-sm-medium text-text-secondary'>{t('app.accessControlDialog.accessItems.specific')}</p> + </> + } + {appDetail?.access_mode === AccessMode.PUBLIC + && <> + <RiGlobalLine className='h-4 w-4 shrink-0 text-text-secondary' /> + <p className='system-sm-medium text-text-secondary'>{t('app.accessControlDialog.accessItems.anyone')}</p> + </> + } + {appDetail?.access_mode === AccessMode.EXTERNAL_MEMBERS + && <> + <RiVerifiedBadgeLine className='h-4 w-4 shrink-0 text-text-secondary' /> + <p className='system-sm-medium text-text-secondary'>{t('app.accessControlDialog.accessItems.external')}</p> + </> + }</div> + {!isAppAccessSet && <p className='system-xs-regular shrink-0 text-text-tertiary'>{t('app.publishApp.notSet')}</p>} + <div className='flex h-4 w-4 shrink-0 items-center justify-center'> + <RiArrowRightSLine className='h-4 w-4 text-text-quaternary' /> + </div> + </div> + </div>} </div> <div className={'flex items-center gap-1 self-stretch p-3'}> {!isApp && <SecretKeyButton appId={appInfo.id} />} @@ -264,6 +339,11 @@ function AppCard({ api_base_url={appInfo.api_base_url} mode={appInfo.mode} /> + { + showAccessControl && <AccessControl app={appDetail!} + onConfirm={handleAccessControlUpdate} + onClose={() => { setShowAccessControl(false) }} /> + } </> ) : null} diff --git a/web/app/components/app/overview/customize/index.tsx b/web/app/components/app/overview/customize/index.tsx index 9e0fadd87d..0fedd76f89 100644 --- a/web/app/components/app/overview/customize/index.tsx +++ b/web/app/components/app/overview/customize/index.tsx @@ -3,13 +3,11 @@ import type { FC } from 'react' import React from 'react' import { ArrowTopRightOnSquareIcon } from '@heroicons/react/24/outline' import { useTranslation } from 'react-i18next' -import { useContext } from 'use-context-selector' +import { useDocLink } from '@/context/i18n' import type { AppMode } from '@/types/app' -import I18n from '@/context/i18n' import Button from '@/app/components/base/button' import Modal from '@/app/components/base/modal' import Tag from '@/app/components/base/tag' -import { LanguagesSupported } from '@/i18n/language' type IShareLinkProps = { isShow: boolean @@ -43,7 +41,7 @@ const CustomizeModal: FC<IShareLinkProps> = ({ mode, }) => { const { t } = useTranslation() - const { locale } = useContext(I18n) + const docLink = useDocLink() const isChatApp = mode === 'chat' || mode === 'advanced-chat' return <Modal @@ -101,10 +99,7 @@ const CustomizeModal: FC<IShareLinkProps> = ({ className='mt-2' onClick={() => window.open( - `https://docs.dify.ai/${locale !== LanguagesSupported[1] - ? 'user-guide/launching-dify-apps/developing-with-apis' - : `v/${locale.toLowerCase()}/guides/application-publishing/developing-with-apis` - }`, + docLink('/guides/application-publishing/developing-with-apis'), '_blank', ) } diff --git a/web/app/components/app/overview/embedded/index.tsx b/web/app/components/app/overview/embedded/index.tsx index 6ebd0fce69..691b727b8e 100644 --- a/web/app/components/app/overview/embedded/index.tsx +++ b/web/app/components/app/overview/embedded/index.tsx @@ -29,7 +29,7 @@ const OPTION_MAP = { iframe: { getContent: (url: string, token: string) => `<iframe - src="${url}${basePath}/chat/${token}" + src="${url}${basePath}/chatbot/${token}" style="width: 100%; height: 100%; min-height: 700px" frameborder="0" allow="microphone"> diff --git a/web/app/components/app/overview/embedded/style.module.css b/web/app/components/app/overview/embedded/style.module.css index bea829b059..f2a4d2d0f4 100644 --- a/web/app/components/app/overview/embedded/style.module.css +++ b/web/app/components/app/overview/embedded/style.module.css @@ -17,4 +17,4 @@ } .pluginInstallIcon { background-image: url(../assets/chromeplugin-install.svg); -} \ No newline at end of file +} diff --git a/web/app/components/app/overview/settings/index.tsx b/web/app/components/app/overview/settings/index.tsx index 679d616e54..524c340a53 100644 --- a/web/app/components/app/overview/settings/index.tsx +++ b/web/app/components/app/overview/settings/index.tsx @@ -4,7 +4,6 @@ import React, { useCallback, useEffect, useState } from 'react' import { RiArrowRightSLine, RiCloseLine } from '@remixicon/react' import Link from 'next/link' import { Trans, useTranslation } from 'react-i18next' -import { useContext, useContextSelector } from 'use-context-selector' import { SparklesSoft } from '@/app/components/base/icons/src/public/common' import Modal from '@/app/components/base/modal' import ActionButton from '@/app/components/base/action-button' @@ -19,15 +18,14 @@ import { SimpleSelect } from '@/app/components/base/select' import type { AppDetailResponse } from '@/models/app' import type { AppIconType, AppSSO, Language } from '@/types/app' import { useToastContext } from '@/app/components/base/toast' -import { LanguagesSupported, languages } from '@/i18n/language' +import { languages } from '@/i18n/language' import Tooltip from '@/app/components/base/tooltip' -import AppContext, { useAppContext } from '@/context/app-context' import { useProviderContext } from '@/context/provider-context' import { useModalContext } from '@/context/modal-context' import type { AppIconSelection } from '@/app/components/base/app-icon-picker' import AppIconPicker from '@/app/components/base/app-icon-picker' -import I18n from '@/context/i18n' import cn from '@/utils/classnames' +import { useDocLink } from '@/context/i18n' export type ISettingsModalProps = { isChat: boolean @@ -65,8 +63,6 @@ const SettingsModal: FC<ISettingsModalProps> = ({ onClose, onSave, }) => { - const systemFeatures = useContextSelector(AppContext, state => state.systemFeatures) - const { isCurrentWorkspaceEditor } = useAppContext() const { notify } = useToastContext() const [isShowMore, setIsShowMore] = useState(false) const { @@ -101,7 +97,7 @@ const SettingsModal: FC<ISettingsModalProps> = ({ const [language, setLanguage] = useState(default_language) const [saveLoading, setSaveLoading] = useState(false) const { t } = useTranslation() - const { locale } = useContext(I18n) + const docLink = useDocLink() const [showAppIconPicker, setShowAppIconPicker] = useState(false) const [appIcon, setAppIcon] = useState<AppIconSelection>( @@ -110,7 +106,7 @@ const SettingsModal: FC<ISettingsModalProps> = ({ : { type: 'emoji', icon, background: icon_background! }, ) - const { enableBilling, plan } = useProviderContext() + const { enableBilling, plan, webappCopyrightEnabled } = useProviderContext() const { setShowPricingModal, setShowAccountSettingModal } = useModalContext() const isFreePlan = plan.type === 'sandbox' const handlePlanClick = useCallback(() => { @@ -138,7 +134,7 @@ const SettingsModal: FC<ISettingsModalProps> = ({ setAppIcon(icon_type === 'image' ? { type: 'image', url: icon_url!, fileId: icon } : { type: 'emoji', icon, background: icon_background! }) - }, [appInfo]) + }, [appInfo, chat_color_theme, chat_color_theme_inverted, copyright, custom_disclaimer, default_language, description, icon, icon_background, icon_type, icon_url, privacy_policy, show_workflow_steps, title, use_icon_as_answer_icon]) const onHide = () => { onClose() @@ -188,7 +184,7 @@ const SettingsModal: FC<ISettingsModalProps> = ({ chat_color_theme: inputInfo.chatColorTheme, chat_color_theme_inverted: inputInfo.chatColorThemeInverted, prompt_public: false, - copyright: isFreePlan + copyright: !webappCopyrightEnabled ? '' : inputInfo.copyrightSwitchValue ? inputInfo.copyright @@ -241,7 +237,10 @@ const SettingsModal: FC<ISettingsModalProps> = ({ </div> <div className='system-xs-regular mt-0.5 text-text-tertiary'> <span>{t(`${prefixSettings}.modalTip`)}</span> - <Link href={`${locale === LanguagesSupported[1] ? 'https://docs.dify.ai/zh-hans/guides/application-publishing/launch-your-webapp-quickly#she-zhi-ni-de-ai-zhan-dian' : 'https://docs.dify.ai/guides/application-publishing/launch-your-webapp-quickly#setting-up-your-ai-site'}`} target='_blank' rel='noopener noreferrer' className='text-text-accent'>{t('common.operation.learnMore')}</Link> + <Link href={docLink('/guides/application-publishing/launch-your-webapp-quickly/README', { + 'zh-Hans': '/guides/application-publishing/launch-your-webapp-quickly/readme', + })} + target='_blank' rel='noopener noreferrer' className='text-text-accent'>{t('common.operation.learnMore')}</Link> </div> </div> {/* form body */} @@ -336,28 +335,6 @@ const SettingsModal: FC<ISettingsModalProps> = ({ </div> <p className='body-xs-regular pb-0.5 text-text-tertiary'>{t(`${prefixSettings}.workflow.showDesc`)}</p> </div> - {/* SSO */} - {systemFeatures.enable_web_sso_switch_component && ( - <> - <Divider className="my-0 h-px" /> - <div className='w-full'> - <p className='system-xs-medium-uppercase mb-1 text-text-tertiary'>{t(`${prefixSettings}.sso.label`)}</p> - <div className='flex items-center justify-between'> - <div className={cn('system-sm-semibold py-1 text-text-secondary')}>{t(`${prefixSettings}.sso.title`)}</div> - <Tooltip - disabled={systemFeatures.sso_enforced_for_web} - popupContent={ - <div className='w-[180px]'>{t(`${prefixSettings}.sso.tooltip`)}</div> - } - asChild={false} - > - <Switch disabled={!systemFeatures.sso_enforced_for_web || !isCurrentWorkspaceEditor} defaultValue={systemFeatures.sso_enforced_for_web && inputInfo.enable_sso} onChange={v => setInputInfo({ ...inputInfo, enable_sso: v })}></Switch> - </Tooltip> - </div> - <p className='body-xs-regular pb-0.5 text-text-tertiary'>{t(`${prefixSettings}.sso.description`)}</p> - </div> - </> - )} {/* more settings switch */} <Divider className="my-0 h-px" /> {!isShowMore && ( @@ -392,14 +369,14 @@ const SettingsModal: FC<ISettingsModalProps> = ({ )} </div> <Tooltip - disabled={!isFreePlan} + disabled={webappCopyrightEnabled} popupContent={ - <div className='w-[260px]'>{t(`${prefixSettings}.more.copyrightTooltip`)}</div> + <div className='w-[180px]'>{t(`${prefixSettings}.more.copyrightTooltip`)}</div> } asChild={false} > <Switch - disabled={isFreePlan} + disabled={!webappCopyrightEnabled} defaultValue={inputInfo.copyrightSwitchValue} onChange={v => setInputInfo({ ...inputInfo, copyrightSwitchValue: v })} /> @@ -450,7 +427,6 @@ const SettingsModal: FC<ISettingsModalProps> = ({ <Button className='mr-2' onClick={onHide}>{t('common.operation.cancel')}</Button> <Button variant='primary' onClick={onClickSave} loading={saveLoading}>{t('common.operation.save')}</Button> </div> - {showAppIconPicker && ( <div onClick={e => e.stopPropagation()}> <AppIconPicker diff --git a/web/app/components/app/type-selector/index.tsx b/web/app/components/app/type-selector/index.tsx index 0accafdf4b..a57bac20db 100644 --- a/web/app/components/app/type-selector/index.tsx +++ b/web/app/components/app/type-selector/index.tsx @@ -15,7 +15,7 @@ export type AppSelectorProps = { onChange: (value: AppSelectorProps['value']) => void } -const allTypes: AppMode[] = ['chat', 'agent-chat', 'completion', 'advanced-chat', 'workflow'] +const allTypes: AppMode[] = ['workflow', 'advanced-chat', 'chat', 'agent-chat', 'completion'] const AppTypeSelector = ({ value, onChange }: AppSelectorProps) => { const [open, setOpen] = useState(false) diff --git a/web/app/components/base/action-button/index.css b/web/app/components/base/action-button/index.css index 2cabe7aecc..3c1a10b86f 100644 --- a/web/app/components/base/action-button/index.css +++ b/web/app/components/base/action-button/index.css @@ -42,4 +42,4 @@ @apply text-text-destructive bg-state-destructive-hover } -} \ No newline at end of file +} diff --git a/web/app/components/base/app-icon-picker/ImageInput.tsx b/web/app/components/base/app-icon-picker/ImageInput.tsx index 9c0a95c021..d42abf867f 100644 --- a/web/app/components/base/app-icon-picker/ImageInput.tsx +++ b/web/app/components/base/app-icon-picker/ImageInput.tsx @@ -94,7 +94,7 @@ const ImageInput: FC<UploaderProps> = ({ <div className={classNames( isDragActive && 'border-primary-600', - 'relative aspect-square bg-gray-50 border-[1.5px] border-gray-200 border-dashed rounded-lg flex flex-col justify-center items-center text-gray-500')} + 'relative aspect-square border-[1.5px] border-dashed rounded-lg flex flex-col justify-center items-center text-gray-500')} onDragEnter={handleDragEnter} onDragOver={handleDragOver} onDragLeave={handleDragLeave} diff --git a/web/app/components/base/app-icon-picker/index.tsx b/web/app/components/base/app-icon-picker/index.tsx index 8304de188c..975f8aeb6c 100644 --- a/web/app/components/base/app-icon-picker/index.tsx +++ b/web/app/components/base/app-icon-picker/index.tsx @@ -115,7 +115,7 @@ const AppIconPicker: FC<AppIconPickerProps> = ({ className={cn(s.container, '!w-[362px] !p-0')} > {!DISABLE_UPLOAD_IMAGE_AS_ICON && <div className="w-full p-2 pb-0"> - <div className='flex items-center justify-center gap-2 rounded-xl bg-background-body p-1'> + <div className='flex items-center justify-center gap-2 rounded-xl bg-background-body p-1 text-text-primary'> {tabs.map(tab => ( <button key={tab.key} diff --git a/web/app/components/base/app-icon-picker/style.module.css b/web/app/components/base/app-icon-picker/style.module.css index 5facb3560a..5ec199a232 100644 --- a/web/app/components/base/app-icon-picker/style.module.css +++ b/web/app/components/base/app-icon-picker/style.module.css @@ -4,9 +4,6 @@ align-items: flex-start; width: 362px; max-height: 552px; - - border: 0.5px solid #EAECF0; box-shadow: 0px 12px 16px -4px rgba(16, 24, 40, 0.08), 0px 4px 6px -2px rgba(16, 24, 40, 0.03); border-radius: 12px; - background: #fff; } diff --git a/web/app/components/base/app-icon/style.module.css b/web/app/components/base/app-icon/style.module.css index 151bc6d3fc..4ee84fb444 100644 --- a/web/app/components/base/app-icon/style.module.css +++ b/web/app/components/base/app-icon/style.module.css @@ -20,4 +20,4 @@ .appIcon.rounded { @apply rounded-full; -} \ No newline at end of file +} diff --git a/web/app/components/base/app-unavailable.tsx b/web/app/components/base/app-unavailable.tsx index 00abb4cfa0..c501d36118 100644 --- a/web/app/components/base/app-unavailable.tsx +++ b/web/app/components/base/app-unavailable.tsx @@ -1,24 +1,27 @@ 'use client' +import classNames from '@/utils/classnames' import type { FC } from 'react' import React from 'react' import { useTranslation } from 'react-i18next' type IAppUnavailableProps = { - code?: number + code?: number | string isUnknownReason?: boolean unknownReason?: string + className?: string } const AppUnavailable: FC<IAppUnavailableProps> = ({ code = 404, isUnknownReason, unknownReason, + className, }) => { const { t } = useTranslation() return ( - <div className='flex h-screen w-screen items-center justify-center'> - <h1 className='mr-5 h-[50px] pr-5 text-[24px] font-medium leading-[50px]' + <div className={classNames('flex h-screen w-screen items-center justify-center', className)}> + <h1 className='mr-5 h-[50px] shrink-0 pr-5 text-[24px] font-medium leading-[50px]' style={{ borderRight: '1px solid rgba(0,0,0,.3)', }}>{code}</h1> diff --git a/web/app/components/base/audio-btn/style.module.css b/web/app/components/base/audio-btn/style.module.css index b8a4da6b68..7e3175aa13 100644 --- a/web/app/components/base/audio-btn/style.module.css +++ b/web/app/components/base/audio-btn/style.module.css @@ -7,4 +7,4 @@ background-image: url(~@/app/components/develop/secret-key/assets/pause.svg); background-position: center; background-repeat: no-repeat; -} \ No newline at end of file +} diff --git a/web/app/components/base/badge/index.css b/web/app/components/base/badge/index.css index 99db573c9c..24c62cdebc 100644 --- a/web/app/components/base/badge/index.css +++ b/web/app/components/base/badge/index.css @@ -25,4 +25,4 @@ .badge.badge-accent { @apply text-text-accent-secondary border border-text-accent-secondary } -} \ No newline at end of file +} diff --git a/web/app/components/base/button/index.css b/web/app/components/base/button/index.css index 5656cb9fdb..47e59142cc 100644 --- a/web/app/components/base/button/index.css +++ b/web/app/components/base/button/index.css @@ -22,7 +22,7 @@ } .btn-primary { - @apply + @apply shadow bg-components-button-primary-bg border-components-button-primary-border @@ -32,7 +32,7 @@ } .btn-primary.btn-destructive { - @apply + @apply bg-components-button-destructive-primary-bg border-components-button-destructive-primary-border hover:bg-components-button-destructive-primary-bg-hover @@ -41,7 +41,7 @@ } .btn-primary.btn-disabled { - @apply + @apply shadow-none bg-components-button-primary-bg-disabled border-components-button-primary-border-disabled @@ -49,7 +49,7 @@ } .btn-primary.btn-destructive.btn-disabled { - @apply + @apply shadow-none bg-components-button-destructive-primary-bg-disabled border-components-button-destructive-primary-border-disabled @@ -57,130 +57,130 @@ } .btn-secondary { - @apply + @apply border-[0.5px] shadow-xs - bg-components-button-secondary-bg - border-components-button-secondary-border - hover:bg-components-button-secondary-bg-hover - hover:border-components-button-secondary-border-hover + bg-components-button-secondary-bg + border-components-button-secondary-border + hover:bg-components-button-secondary-bg-hover + hover:border-components-button-secondary-border-hover text-components-button-secondary-text; } .btn-secondary.btn-disabled { - @apply - bg-components-button-secondary-bg-disabled - border-components-button-secondary-border-disabled + @apply + bg-components-button-secondary-bg-disabled + border-components-button-secondary-border-disabled text-components-button-secondary-text-disabled; } .btn-secondary.btn-destructive { - @apply - bg-components-button-destructive-secondary-bg - border-components-button-destructive-secondary-border - hover:bg-components-button-destructive-secondary-bg-hover - hover:border-components-button-destructive-secondary-border-hover + @apply + bg-components-button-destructive-secondary-bg + border-components-button-destructive-secondary-border + hover:bg-components-button-destructive-secondary-bg-hover + hover:border-components-button-destructive-secondary-border-hover text-components-button-destructive-secondary-text; } .btn-secondary.btn-destructive.btn-disabled { - @apply - bg-components-button-destructive-secondary-bg-disabled - border-components-button-destructive-secondary-border-disabled + @apply + bg-components-button-destructive-secondary-bg-disabled + border-components-button-destructive-secondary-border-disabled text-components-button-destructive-secondary-text-disabled; } - + .btn-secondary-accent { - @apply + @apply border-[0.5px] shadow-xs - bg-components-button-secondary-bg - border-components-button-secondary-border - hover:bg-components-button-secondary-bg-hover - hover:border-components-button-secondary-border-hover + bg-components-button-secondary-bg + border-components-button-secondary-border + hover:bg-components-button-secondary-bg-hover + hover:border-components-button-secondary-border-hover text-components-button-secondary-accent-text; } .btn-secondary-accent.btn-disabled { - @apply - bg-components-button-secondary-bg-disabled - border-components-button-secondary-border-disabled + @apply + bg-components-button-secondary-bg-disabled + border-components-button-secondary-border-disabled text-components-button-secondary-accent-text-disabled; } .btn-warning { - @apply - bg-components-button-destructive-primary-bg - border-components-button-destructive-primary-border - hover:bg-components-button-destructive-primary-bg-hover - hover:border-components-button-destructive-primary-border-hover + @apply + bg-components-button-destructive-primary-bg + border-components-button-destructive-primary-border + hover:bg-components-button-destructive-primary-bg-hover + hover:border-components-button-destructive-primary-border-hover text-components-button-destructive-primary-text; } .btn-warning.btn-disabled { - @apply - bg-components-button-destructive-primary-bg-disabled - border-components-button-destructive-primary-border-disabled + @apply + bg-components-button-destructive-primary-bg-disabled + border-components-button-destructive-primary-border-disabled text-components-button-destructive-primary-text-disabled; } .btn-tertiary { - @apply - bg-components-button-tertiary-bg - hover:bg-components-button-tertiary-bg-hover + @apply + bg-components-button-tertiary-bg + hover:bg-components-button-tertiary-bg-hover text-components-button-tertiary-text; } .btn-tertiary.btn-disabled { - @apply - bg-components-button-tertiary-bg-disabled + @apply + bg-components-button-tertiary-bg-disabled text-components-button-tertiary-text-disabled; } .btn-tertiary.btn-destructive { - @apply - bg-components-button-destructive-tertiary-bg - hover:bg-components-button-destructive-tertiary-bg-hover + @apply + bg-components-button-destructive-tertiary-bg + hover:bg-components-button-destructive-tertiary-bg-hover text-components-button-destructive-tertiary-text; } .btn-tertiary.btn-destructive.btn-disabled { - @apply - bg-components-button-destructive-tertiary-bg-disabled + @apply + bg-components-button-destructive-tertiary-bg-disabled text-components-button-destructive-tertiary-text-disabled; } .btn-ghost { - @apply - hover:bg-components-button-ghost-bg-hover + @apply + hover:bg-components-button-ghost-bg-hover text-components-button-ghost-text; } .btn-ghost.btn-disabled { - @apply + @apply text-components-button-ghost-text-disabled; } .btn-ghost.btn-destructive { - @apply - hover:bg-components-button-destructive-ghost-bg-hover + @apply + hover:bg-components-button-destructive-ghost-bg-hover text-components-button-destructive-ghost-text; } .btn-ghost.btn-destructive.btn-disabled { - @apply + @apply text-components-button-destructive-ghost-text-disabled; } .btn-ghost-accent { - @apply + @apply hover:bg-state-accent-hover text-components-button-secondary-accent-text; } .btn-ghost-accent.btn-disabled { - @apply + @apply text-components-button-secondary-accent-text-disabled; } -} \ No newline at end of file +} diff --git a/web/app/components/base/chat/chat-with-history/chat-wrapper.tsx b/web/app/components/base/chat/chat-with-history/chat-wrapper.tsx index 63de13596f..cbe4826c14 100644 --- a/web/app/components/base/chat/chat-with-history/chat-wrapper.tsx +++ b/web/app/components/base/chat/chat-with-history/chat-wrapper.tsx @@ -47,6 +47,7 @@ const ChatWrapper = () => { clearChatList, setClearChatList, setIsResponding, + allInputsHidden, } = useChatWithHistoryContext() const appConfig = useMemo(() => { const config = appParams || {} @@ -81,6 +82,9 @@ const ChatWrapper = () => { ) const inputsFormValue = currentConversationId ? currentConversationInputs : newConversationInputsRef?.current const inputDisabled = useMemo(() => { + if (allInputsHidden) + return false + let hasEmptyInput = '' let fileIsUploading = false const requiredVars = inputsForms.filter(({ required }) => required) @@ -110,7 +114,7 @@ const ChatWrapper = () => { if (fileIsUploading) return true return false - }, [inputsFormValue, inputsForms]) + }, [inputsFormValue, inputsForms, allInputsHidden]) useEffect(() => { if (currentChatInstanceRef.current) @@ -161,7 +165,7 @@ const ChatWrapper = () => { const [collapsed, setCollapsed] = useState(!!currentConversationId) const chatNode = useMemo(() => { - if (!inputsForms.length) + if (allInputsHidden || !inputsForms.length) return null if (isMobile) { if (!currentConversationId) @@ -171,7 +175,7 @@ const ChatWrapper = () => { else { return <InputsForm collapsed={collapsed} setCollapsed={setCollapsed} /> } - }, [inputsForms.length, isMobile, currentConversationId, collapsed]) + }, [inputsForms.length, isMobile, currentConversationId, collapsed, allInputsHidden]) const welcome = useMemo(() => { const welcomeMessage = chatList.find(item => item.isOpeningStatement) @@ -181,7 +185,7 @@ const ChatWrapper = () => { return null if (!welcomeMessage) return null - if (!collapsed && inputsForms.length > 0) + if (!collapsed && inputsForms.length > 0 && !allInputsHidden) return null if (welcomeMessage.suggestedQuestions && welcomeMessage.suggestedQuestions?.length > 0) { return ( @@ -218,7 +222,7 @@ const ChatWrapper = () => { </div> </div> ) - }, [appData?.site.icon, appData?.site.icon_background, appData?.site.icon_type, appData?.site.icon_url, chatList, collapsed, currentConversationId, inputsForms.length, respondingState]) + }, [appData?.site.icon, appData?.site.icon_background, appData?.site.icon_type, appData?.site.icon_url, chatList, collapsed, currentConversationId, inputsForms.length, respondingState, allInputsHidden]) const answerIcon = (appData?.site && appData.site.use_icon_as_answer_icon) ? <AnswerIcon diff --git a/web/app/components/base/chat/chat-with-history/context.tsx b/web/app/components/base/chat/chat-with-history/context.tsx index 7dd7a78691..5bf1514774 100644 --- a/web/app/components/base/chat/chat-with-history/context.tsx +++ b/web/app/components/base/chat/chat-with-history/context.tsx @@ -22,6 +22,7 @@ export type ChatWithHistoryContextValue = { appInfoLoading?: boolean appMeta?: AppMeta appData?: AppData + userCanAccess?: boolean appParams?: ChatConfig appChatListDataLoading?: boolean currentConversationId: string @@ -57,9 +58,11 @@ export type ChatWithHistoryContextValue = { setIsResponding: (state: boolean) => void, currentConversationInputs: Record<string, any> | null, setCurrentConversationInputs: (v: Record<string, any>) => void, + allInputsHidden: boolean, } export const ChatWithHistoryContext = createContext<ChatWithHistoryContextValue>({ + userCanAccess: false, currentConversationId: '', appPrevChatTree: [], pinnedConversationList: [], @@ -90,5 +93,6 @@ export const ChatWithHistoryContext = createContext<ChatWithHistoryContextValue> setIsResponding: noop, currentConversationInputs: {}, setCurrentConversationInputs: noop, + allInputsHidden: false, }) export const useChatWithHistoryContext = () => useContext(ChatWithHistoryContext) diff --git a/web/app/components/base/chat/chat-with-history/header-in-mobile.tsx b/web/app/components/base/chat/chat-with-history/header-in-mobile.tsx index 6a8292c54f..ec8da7b102 100644 --- a/web/app/components/base/chat/chat-with-history/header-in-mobile.tsx +++ b/web/app/components/base/chat/chat-with-history/header-in-mobile.tsx @@ -27,6 +27,7 @@ const HeaderInMobile = () => { handleDeleteConversation, handleRenameConversation, conversationRenaming, + inputsForms, } = useChatWithHistoryContext() const { t } = useTranslation() const isPin = pinnedConversationList.some(item => item.id === currentConversationId) @@ -99,6 +100,7 @@ const HeaderInMobile = () => { <MobileOperationDropdown handleResetChat={handleNewConversation} handleViewChatSettings={() => setShowChatSettings(true)} + hideViewChatSettings={inputsForms.length < 1} /> </div> {showSidebar && ( diff --git a/web/app/components/base/chat/chat-with-history/header/mobile-operation-dropdown.tsx b/web/app/components/base/chat/chat-with-history/header/mobile-operation-dropdown.tsx index f5acf05b6b..4bb694033d 100644 --- a/web/app/components/base/chat/chat-with-history/header/mobile-operation-dropdown.tsx +++ b/web/app/components/base/chat/chat-with-history/header/mobile-operation-dropdown.tsx @@ -9,11 +9,13 @@ import ActionButton, { ActionButtonState } from '@/app/components/base/action-bu type Props = { handleResetChat: () => void handleViewChatSettings: () => void + hideViewChatSettings?: boolean } const MobileOperationDropdown = ({ handleResetChat, handleViewChatSettings, + hideViewChatSettings = false, }: Props) => { const { t } = useTranslation() const [open, setOpen] = useState(false) @@ -42,9 +44,11 @@ const MobileOperationDropdown = ({ <div className='system-md-regular flex cursor-pointer items-center space-x-1 rounded-lg px-3 py-1.5 text-text-secondary hover:bg-state-base-hover' onClick={handleResetChat}> <span className='grow'>{t('share.chat.resetChat')}</span> </div> - <div className='system-md-regular flex cursor-pointer items-center space-x-1 rounded-lg px-3 py-1.5 text-text-secondary hover:bg-state-base-hover' onClick={handleViewChatSettings}> - <span className='grow'>{t('share.chat.viewChatSettings')}</span> - </div> + {!hideViewChatSettings && ( + <div className='system-md-regular flex cursor-pointer items-center space-x-1 rounded-lg px-3 py-1.5 text-text-secondary hover:bg-state-base-hover' onClick={handleViewChatSettings}> + <span className='grow'>{t('share.chat.viewChatSettings')}</span> + </div> + )} </div> </PortalToFollowElemContent> </PortalToFollowElem> diff --git a/web/app/components/base/chat/chat-with-history/hooks.tsx b/web/app/components/base/chat/chat-with-history/hooks.tsx index 91ceaffd1e..32f74e6457 100644 --- a/web/app/components/base/chat/chat-with-history/hooks.tsx +++ b/web/app/components/base/chat/chat-with-history/hooks.tsx @@ -16,7 +16,7 @@ import type { Feedback, } from '../types' import { CONVERSATION_ID_INFO } from '../constants' -import { buildChatItemTree, getProcessedSystemVariablesFromUrlParams } from '../utils' +import { buildChatItemTree, getProcessedSystemVariablesFromUrlParams, getRawInputsFromUrlParams } from '../utils' import { addFileInfos, sortAgentSorts } from '../../../tools/utils' import { getProcessedFilesFromResponse } from '@/app/components/base/file-uploader/utils' import { @@ -43,6 +43,8 @@ import { useAppFavicon } from '@/hooks/use-app-favicon' import { InputVarType } from '@/app/components/workflow/types' import { TransferMethod } from '@/types/app' import { noop } from 'lodash-es' +import { useGetUserCanAccessApp } from '@/service/access-control' +import { useGlobalPublicStore } from '@/context/global-public-context' function getFormattedChatList(messages: any[]) { const newChatList: ChatItem[] = [] @@ -72,7 +74,13 @@ function getFormattedChatList(messages: any[]) { export const useChatWithHistory = (installedAppInfo?: InstalledApp) => { const isInstalledApp = useMemo(() => !!installedAppInfo, [installedAppInfo]) + const systemFeatures = useGlobalPublicStore(s => s.systemFeatures) const { data: appInfo, isLoading: appInfoLoading, error: appInfoError } = useSWR(installedAppInfo ? null : 'appInfo', fetchAppInfo) + const { isPending: isCheckingPermission, data: userCanAccessResult } = useGetUserCanAccessApp({ + appId: installedAppInfo?.app.id || appInfo?.app_id, + isInstalledApp, + enabled: systemFeatures.webapp_auth.enabled, + }) useAppFavicon({ enable: !installedAppInfo, @@ -181,6 +189,7 @@ export const useChatWithHistory = (installedAppInfo?: InstalledApp) => { const { t } = useTranslation() const newConversationInputsRef = useRef<Record<string, any>>({}) const [newConversationInputs, setNewConversationInputs] = useState<Record<string, any>>({}) + const [initInputs, setInitInputs] = useState<Record<string, any>>({}) const handleNewConversationInputsChange = useCallback((newInputs: Record<string, any>) => { newConversationInputsRef.current = newInputs setNewConversationInputs(newInputs) @@ -188,20 +197,29 @@ export const useChatWithHistory = (installedAppInfo?: InstalledApp) => { const inputsForms = useMemo(() => { return (appParams?.user_input_form || []).filter((item: any) => !item.external_data_tool).map((item: any) => { if (item.paragraph) { + let value = initInputs[item.paragraph.variable] + if (value && item.paragraph.max_length && value.length > item.paragraph.max_length) + value = value.slice(0, item.paragraph.max_length) + return { ...item.paragraph, + default: value || item.default, type: 'paragraph', } } if (item.number) { + const convertedNumber = Number(initInputs[item.number.variable]) ?? undefined return { ...item.number, + default: convertedNumber || item.default, type: 'number', } } if (item.select) { + const isInputInOptions = item.select.options.includes(initInputs[item.select.variable]) return { ...item.select, + default: (isInputInOptions ? initInputs[item.select.variable] : undefined) || item.default, type: 'select', } } @@ -220,12 +238,30 @@ export const useChatWithHistory = (installedAppInfo?: InstalledApp) => { } } + let value = initInputs[item['text-input'].variable] + if (value && item['text-input'].max_length && value.length > item['text-input'].max_length) + value = value.slice(0, item['text-input'].max_length) + return { ...item['text-input'], + default: value || item.default, type: 'text-input', } }) - }, [appParams]) + }, [initInputs, appParams]) + + const allInputsHidden = useMemo(() => { + return inputsForms.length > 0 && inputsForms.every(item => item.hide === true) + }, [inputsForms]) + + useEffect(() => { + // init inputs from url params + (async () => { + const inputs = await getRawInputsFromUrlParams() + setInitInputs(inputs) + })() + }, []) + useEffect(() => { const conversationInputs: Record<string, any> = {} @@ -290,6 +326,9 @@ export const useChatWithHistory = (installedAppInfo?: InstalledApp) => { const { notify } = useToastContext() const checkInputsRequired = useCallback((silent?: boolean) => { + if (allInputsHidden) + return true + let hasEmptyInput = '' let fileIsUploading = false const requiredVars = inputsForms.filter(({ required }) => required) @@ -325,7 +364,7 @@ export const useChatWithHistory = (installedAppInfo?: InstalledApp) => { } return true - }, [inputsForms, notify, t]) + }, [inputsForms, notify, t, allInputsHidden]) const handleStartChat = useCallback((callback: any) => { if (checkInputsRequired()) { setShowNewConversationItemInList(true) @@ -340,11 +379,11 @@ export const useChatWithHistory = (installedAppInfo?: InstalledApp) => { if (conversationId) setClearChatList(false) }, [handleConversationIdInfoChange, setClearChatList]) - const handleNewConversation = useCallback(() => { + const handleNewConversation = useCallback(async () => { currentChatInstanceRef.current.handleStop() setShowNewConversationItemInList(true) handleChangeConversation('') - handleNewConversationInputsChange({}) + handleNewConversationInputsChange(await getRawInputsFromUrlParams()) setClearChatList(true) }, [handleChangeConversation, setShowNewConversationItemInList, handleNewConversationInputsChange, setClearChatList]) const handleUpdateConversationList = useCallback(() => { @@ -447,7 +486,8 @@ export const useChatWithHistory = (installedAppInfo?: InstalledApp) => { return { appInfoError, - appInfoLoading, + appInfoLoading: appInfoLoading || (systemFeatures.webapp_auth.enabled && isCheckingPermission), + userCanAccess: systemFeatures.webapp_auth.enabled ? userCanAccessResult?.result : true, isInstalledApp, appId, currentConversationId, @@ -491,5 +531,6 @@ export const useChatWithHistory = (installedAppInfo?: InstalledApp) => { setIsResponding, currentConversationInputs, setCurrentConversationInputs, + allInputsHidden, } } diff --git a/web/app/components/base/chat/chat-with-history/index.tsx b/web/app/components/base/chat/chat-with-history/index.tsx index dfd7bd21a7..fe8e7b430d 100644 --- a/web/app/components/base/chat/chat-with-history/index.tsx +++ b/web/app/components/base/chat/chat-with-history/index.tsx @@ -1,5 +1,7 @@ +'use client' import type { FC } from 'react' import { + useCallback, useEffect, useState, } from 'react' @@ -17,9 +19,12 @@ import ChatWrapper from './chat-wrapper' import type { InstalledApp } from '@/models/explore' import Loading from '@/app/components/base/loading' import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints' -import { checkOrSetAccessToken } from '@/app/components/share/utils' +import { checkOrSetAccessToken, removeAccessToken } from '@/app/components/share/utils' import AppUnavailable from '@/app/components/base/app-unavailable' import cn from '@/utils/classnames' +import useDocumentTitle from '@/hooks/use-document-title' +import { useTranslation } from 'react-i18next' +import { usePathname, useRouter, useSearchParams } from 'next/navigation' type ChatWithHistoryProps = { className?: string @@ -28,6 +33,7 @@ const ChatWithHistory: FC<ChatWithHistoryProps> = ({ className, }) => { const { + userCanAccess, appInfoError, appData, appInfoLoading, @@ -36,6 +42,7 @@ const ChatWithHistory: FC<ChatWithHistoryProps> = ({ isMobile, themeBuilder, sidebarCollapseState, + isInstalledApp, } = useChatWithHistoryContext() const isSidebarCollapsed = sidebarCollapseState const customConfig = appData?.custom_config @@ -45,19 +52,38 @@ const ChatWithHistory: FC<ChatWithHistoryProps> = ({ useEffect(() => { themeBuilder?.buildTheme(site?.chat_color_theme, site?.chat_color_theme_inverted) - if (site) { - if (customConfig) - document.title = `${site.title}` - else - document.title = `${site.title} - Powered by Dify` - } }, [site, customConfig, themeBuilder]) + useDocumentTitle(site?.title || 'Chat') + + const { t } = useTranslation() + const searchParams = useSearchParams() + const router = useRouter() + const pathname = usePathname() + const getSigninUrl = useCallback(() => { + const params = new URLSearchParams(searchParams) + params.delete('message') + params.set('redirect_url', pathname) + return `/webapp-signin?${params.toString()}` + }, [searchParams, pathname]) + + const backToHome = useCallback(() => { + removeAccessToken() + const url = getSigninUrl() + router.replace(url) + }, [getSigninUrl, router]) + if (appInfoLoading) { return ( <Loading type='app' /> ) } + if (!userCanAccess) { + return <div className='flex h-full flex-col items-center justify-center gap-y-2'> + <AppUnavailable className='h-auto w-auto' code={403} unknownReason='no permission.' /> + {!isInstalledApp && <span className='system-sm-regular cursor-pointer text-text-tertiary' onClick={backToHome}>{t('common.userProfile.logout')}</span>} + </div> + } if (appInfoError) { return ( @@ -124,6 +150,7 @@ const ChatWithHistoryWrap: FC<ChatWithHistoryWrapProps> = ({ const { appInfoError, appInfoLoading, + userCanAccess, appData, appParams, appMeta, @@ -159,6 +186,7 @@ const ChatWithHistoryWrap: FC<ChatWithHistoryWrapProps> = ({ setIsResponding, currentConversationInputs, setCurrentConversationInputs, + allInputsHidden, } = useChatWithHistory(installedAppInfo) return ( @@ -166,6 +194,7 @@ const ChatWithHistoryWrap: FC<ChatWithHistoryWrapProps> = ({ appInfoError, appInfoLoading, appData, + userCanAccess, appParams, appMeta, appChatListDataLoading, @@ -202,6 +231,7 @@ const ChatWithHistoryWrap: FC<ChatWithHistoryWrapProps> = ({ setIsResponding, currentConversationInputs, setCurrentConversationInputs, + allInputsHidden, }}> <ChatWithHistory className={className} /> </ChatWithHistoryContext.Provider> diff --git a/web/app/components/base/chat/chat-with-history/inputs-form/content.tsx b/web/app/components/base/chat/chat-with-history/inputs-form/content.tsx index d33dac492d..73a1f07b69 100644 --- a/web/app/components/base/chat/chat-with-history/inputs-form/content.tsx +++ b/web/app/components/base/chat/chat-with-history/inputs-form/content.tsx @@ -1,4 +1,4 @@ -import React, { useCallback } from 'react' +import React, { memo, useCallback } from 'react' import { useTranslation } from 'react-i18next' import { useChatWithHistoryContext } from '../context' import Input from '@/app/components/base/input' @@ -36,9 +36,11 @@ const InputsFormContent = ({ showTip }: Props) => { }) }, [newConversationInputsRef, handleNewConversationInputsChange, currentConversationInputs, setCurrentConversationInputs]) + const visibleInputsForms = inputsForms.filter(form => form.hide !== true) + return ( <div className='space-y-4'> - {inputsForms.map(form => ( + {visibleInputsForms.map(form => ( <div key={form.variable} className='space-y-1'> <div className='flex h-6 items-center gap-1'> <div className='system-md-semibold text-text-secondary'>{form.label}</div> @@ -112,4 +114,4 @@ const InputsFormContent = ({ showTip }: Props) => { ) } -export default InputsFormContent +export default memo(InputsFormContent) diff --git a/web/app/components/base/chat/chat-with-history/inputs-form/index.tsx b/web/app/components/base/chat/chat-with-history/inputs-form/index.tsx index 30ec11c729..3a1b92089c 100644 --- a/web/app/components/base/chat/chat-with-history/inputs-form/index.tsx +++ b/web/app/components/base/chat/chat-with-history/inputs-form/index.tsx @@ -21,9 +21,14 @@ const InputsFormNode = ({ isMobile, currentConversationId, handleStartChat, + allInputsHidden, themeBuilder, + inputsForms, } = useChatWithHistoryContext() + if (allInputsHidden || inputsForms.length === 0) + return null + return ( <div className={cn('flex flex-col items-center px-4 pt-6', isMobile && 'pt-4')}> <div className={cn( diff --git a/web/app/components/base/chat/chat-with-history/sidebar/index.tsx b/web/app/components/base/chat/chat-with-history/sidebar/index.tsx index c563b5dec3..4e50c1cb79 100644 --- a/web/app/components/base/chat/chat-with-history/sidebar/index.tsx +++ b/web/app/components/base/chat/chat-with-history/sidebar/index.tsx @@ -16,9 +16,10 @@ import List from '@/app/components/base/chat/chat-with-history/sidebar/list' import MenuDropdown from '@/app/components/share/text-generation/menu-dropdown' import Confirm from '@/app/components/base/confirm' import RenameModal from '@/app/components/base/chat/chat-with-history/sidebar/rename-modal' -import LogoSite from '@/app/components/base/logo/logo-site' +import DifyLogo from '@/app/components/base/logo/dify-logo' import type { ConversationItem } from '@/models/share' import cn from '@/utils/classnames' +import { useGlobalPublicStore } from '@/context/global-public-context' type Props = { isPanel?: boolean @@ -27,6 +28,7 @@ type Props = { const Sidebar = ({ isPanel }: Props) => { const { t } = useTranslation() const { + isInstalledApp, appData, handleNewConversation, pinnedConversationList, @@ -44,7 +46,7 @@ const Sidebar = ({ isPanel }: Props) => { isResponding, } = useChatWithHistoryContext() const isSidebarCollapsed = sidebarCollapseState - + const systemFeatures = useGlobalPublicStore(s => s.systemFeatures) const [showConfirm, setShowConfirm] = useState<ConversationItem | null>(null) const [showRename, setShowRename] = useState<ConversationItem | null>(null) @@ -136,42 +138,43 @@ const Sidebar = ({ isPanel }: Props) => { )} </div> <div className='flex shrink-0 items-center justify-between p-3'> - <MenuDropdown placement='top-start' data={appData?.site} /> + <MenuDropdown hideLogout={isInstalledApp} placement='top-start' data={appData?.site} /> {/* powered by */} <div className='shrink-0'> {!appData?.custom_config?.remove_webapp_brand && ( <div className={cn( - 'flex shrink-0 items-center gap-1.5 px-2', + 'flex shrink-0 items-center gap-1.5 px-1', )}> <div className='system-2xs-medium-uppercase text-text-tertiary'>{t('share.chat.poweredBy')}</div> - {appData?.custom_config?.replace_webapp_logo && ( - <img src={appData?.custom_config?.replace_webapp_logo} alt='logo' className='block h-5 w-auto' /> - )} - {!appData?.custom_config?.replace_webapp_logo && ( - <LogoSite className='!h-5' /> - )} + { + systemFeatures.branding.enabled && systemFeatures.branding.workspace_logo + ? <img src={systemFeatures.branding.workspace_logo} alt='logo' className='block h-5 w-auto' /> + : appData?.custom_config?.replace_webapp_logo + ? <img src={`${appData?.custom_config?.replace_webapp_logo}`} alt='logo' className='block h-5 w-auto' /> + : <DifyLogo size='small' /> + } </div> )} </div> + {!!showConfirm && ( + <Confirm + title={t('share.chat.deleteConversation.title')} + content={t('share.chat.deleteConversation.content') || ''} + isShow + onCancel={handleCancelConfirm} + onConfirm={handleDelete} + /> + )} + {showRename && ( + <RenameModal + isShow + onClose={handleCancelRename} + saveLoading={conversationRenaming} + name={showRename?.name || ''} + onSave={handleRename} + /> + )} </div> - {!!showConfirm && ( - <Confirm - title={t('share.chat.deleteConversation.title')} - content={t('share.chat.deleteConversation.content') || ''} - isShow - onCancel={handleCancelConfirm} - onConfirm={handleDelete} - /> - )} - {showRename && ( - <RenameModal - isShow - onClose={handleCancelRename} - saveLoading={conversationRenaming} - name={showRename?.name || ''} - onSave={handleRename} - /> - )} </div> ) } diff --git a/web/app/components/base/chat/chat/answer/__mocks__/markdownContentSVG.ts b/web/app/components/base/chat/chat/answer/__mocks__/markdownContentSVG.ts index 67029cd163..51995a4af5 100644 --- a/web/app/components/base/chat/chat/answer/__mocks__/markdownContentSVG.ts +++ b/web/app/components/base/chat/chat/answer/__mocks__/markdownContentSVG.ts @@ -2,25 +2,25 @@ export const markdownContentSVG = ` \`\`\`svg <svg width="400" height="600" xmlns="http://www.w3.org/2000/svg"> <rect width="100%" height="100%" fill="#F0F8FF"/> - - <text x="50%" y="60" font-family="楷体" font-size="32" fill="#4682B4" text-anchor="middle">创意Logo设计</text> - + + <text x="50%" y="60" font-family="楷体" font-size="32" fill="#4682B4" text-anchor="middle">创意 Logo 设计</text> + <line x1="50" y1="80" x2="350" y2="80" stroke="#B0C4DE" stroke-width="2"/> - + <text x="50%" y="120" font-family="Arial" font-size="24" fill="#708090" text-anchor="middle">科研</text> <text x="50%" y="150" font-family="MS Mincho" font-size="20" fill="#778899" text-anchor="middle">科学研究</text> - + <text x="50%" y="200" font-family="汇文明朝体" font-size="18" fill="#696969" text-anchor="middle"> <tspan x="50%" dy="25">探索未知的灯塔,</tspan> <tspan x="50%" dy="25">照亮人类前进的道路。</tspan> <tspan x="50%" dy="25">科研,是永不熄灭的好奇心,</tspan> <tspan x="50%" dy="25">也是推动世界进步的引擎。</tspan> </text> - + <circle cx="200" cy="400" r="80" fill="none" stroke="#4169E1" stroke-width="3"/> <line x1="200" y1="320" x2="200" y2="480" stroke="#4169E1" stroke-width="3"/> <line x1="120" y1="400" x2="280" y2="400" stroke="#4169E1" stroke-width="3"/> - + <text x="50%" y="550" font-family="微软雅黑" font-size="16" fill="#1E90FF" text-anchor="middle">探索 • 创新 • 进步</text> </svg> \`\`\` diff --git a/web/app/components/base/chat/chat/answer/index.tsx b/web/app/components/base/chat/chat/answer/index.tsx index a0a9323729..3722556931 100644 --- a/web/app/components/base/chat/chat/answer/index.tsx +++ b/web/app/components/base/chat/chat/answer/index.tsx @@ -234,6 +234,4 @@ const Answer: FC<AnswerProps> = ({ ) } -export default memo(Answer, (prevProps, nextProps) => - prevProps.responding === false && nextProps.responding === false, -) +export default memo(Answer) diff --git a/web/app/components/base/chat/chat/chat-input-area/index.tsx b/web/app/components/base/chat/chat/chat-input-area/index.tsx index 14d8185f99..cbfa3168e9 100644 --- a/web/app/components/base/chat/chat/chat-input-area/index.tsx +++ b/web/app/components/base/chat/chat/chat-input-area/index.tsx @@ -29,6 +29,7 @@ import type { FileUpload } from '@/app/components/base/features/types' import { TransferMethod } from '@/types/app' type ChatInputAreaProps = { + botName?: string showFeatureBar?: boolean showFileUpload?: boolean featureBarDisabled?: boolean @@ -43,6 +44,7 @@ type ChatInputAreaProps = { disabled?: boolean } const ChatInputArea = ({ + botName, showFeatureBar, showFileUpload, featureBarDisabled, @@ -190,9 +192,9 @@ const ChatInputArea = ({ <Textarea ref={ref => textareaRef.current = ref as any} className={cn( - 'body-lg-regular w-full resize-none bg-transparent p-1 leading-6 text-text-tertiary outline-none', + 'body-lg-regular w-full resize-none bg-transparent p-1 leading-6 text-text-primary outline-none', )} - placeholder={t('common.chat.inputPlaceholder') || ''} + placeholder={t('common.chat.inputPlaceholder', { botName }) || ''} autoFocus minRows={1} onResize={handleTextareaResize} diff --git a/web/app/components/base/chat/chat/hooks.ts b/web/app/components/base/chat/chat/hooks.ts index bff222ea38..10fb455d33 100644 --- a/web/app/components/base/chat/chat/hooks.ts +++ b/web/app/components/base/chat/chat/hooks.ts @@ -366,8 +366,9 @@ export const useChat = ( if (!newResponseItem) return + const isUseAgentThought = newResponseItem.agent_thoughts?.length > 0 && newResponseItem.agent_thoughts[newResponseItem.agent_thoughts?.length - 1].thought === newResponseItem.answer updateChatTreeNode(responseItem.id, { - content: newResponseItem.answer, + content: isUseAgentThought ? '' : newResponseItem.answer, log: [ ...newResponseItem.message, ...(newResponseItem.message[newResponseItem.message.length - 1].role !== 'assistant' @@ -424,6 +425,8 @@ export const useChat = ( const response = responseItem as any if (thought.message_id && !hasSetResponseId) response.id = thought.message_id + if (thought.conversation_id) + response.conversationId = thought.conversation_id if (response.agent_thoughts.length === 0) { response.agent_thoughts.push(thought) diff --git a/web/app/components/base/chat/chat/index.tsx b/web/app/components/base/chat/chat/index.tsx index 27952fe468..801daa6589 100644 --- a/web/app/components/base/chat/chat/index.tsx +++ b/web/app/components/base/chat/chat/index.tsx @@ -265,6 +265,7 @@ const Chat: FC<ChatProps> = ({ item={item} questionIcon={questionIcon} theme={themeBuilder?.theme} + enableEdit={config?.questionEditEnable} switchSibling={switchSibling} /> ) @@ -302,6 +303,7 @@ const Chat: FC<ChatProps> = ({ { !noChatInput && ( <ChatInputArea + botName={appData?.site.title || 'Bot'} disabled={inputDisabled} showFeatureBar={showFeatureBar} showFileUpload={showFileUpload} diff --git a/web/app/components/base/chat/chat/loading-anim/style.module.css b/web/app/components/base/chat/chat/loading-anim/style.module.css index 5a764db13c..b1371ec82a 100644 --- a/web/app/components/base/chat/chat/loading-anim/style.module.css +++ b/web/app/components/base/chat/chat/loading-anim/style.module.css @@ -79,4 +79,4 @@ .avatar::after { left: 5px; -} \ No newline at end of file +} diff --git a/web/app/components/base/chat/chat/question.tsx b/web/app/components/base/chat/chat/question.tsx index af4d64964c..30077125f9 100644 --- a/web/app/components/base/chat/chat/question.tsx +++ b/web/app/components/base/chat/chat/question.tsx @@ -5,6 +5,8 @@ import type { import { memo, useCallback, + useEffect, + useRef, useState, } from 'react' import type { ChatItem } from '../types' @@ -28,6 +30,7 @@ type QuestionProps = { item: ChatItem questionIcon?: ReactNode theme: Theme | null | undefined + enableEdit?: boolean switchSibling?: (siblingMessageId: string) => void } @@ -35,6 +38,7 @@ const Question: FC<QuestionProps> = ({ item, questionIcon, theme, + enableEdit = true, switchSibling, }) => { const { t } = useTranslation() @@ -50,6 +54,8 @@ const Question: FC<QuestionProps> = ({ const [isEditing, setIsEditing] = useState(false) const [editedContent, setEditedContent] = useState(content) + const [contentWidth, setContentWidth] = useState(0) + const contentRef = useRef<HTMLDivElement>(null) const handleEdit = useCallback(() => { setIsEditing(true) @@ -73,27 +79,45 @@ const Question: FC<QuestionProps> = ({ item.nextSibling && switchSibling?.(item.nextSibling) }, [switchSibling, item.prevSibling, item.nextSibling]) + const getContentWidth = () => { + if (contentRef.current) + setContentWidth(contentRef.current?.clientWidth) + } + + useEffect(() => { + if (!contentRef.current) + return + const resizeObserver = new ResizeObserver(() => { + getContentWidth() + }) + resizeObserver.observe(contentRef.current) + return () => { + resizeObserver.disconnect() + } + }, []) + return ( - <div className='mb-2 flex justify-end pl-14 last:mb-0'> - <div className={cn('group relative mr-4 flex max-w-full items-start', isEditing && 'flex-1')}> + <div className='mb-2 flex justify-end last:mb-0'> + <div className={cn('group relative mr-4 flex max-w-full items-start pl-14', isEditing && 'flex-1')}> <div className={cn('mr-2 gap-1', isEditing ? 'hidden' : 'flex')}> - <div className=" - absolutegap-0.5 hidden rounded-[10px] border-[0.5px] border-components-actionbar-border - bg-components-actionbar-bg p-0.5 shadow-md backdrop-blur-sm group-hover:flex - "> + <div + className="absolute hidden gap-0.5 rounded-[10px] border-[0.5px] border-components-actionbar-border bg-components-actionbar-bg p-0.5 shadow-md backdrop-blur-sm group-hover:flex" + style={{ right: contentWidth + 8 }} + > <ActionButton onClick={() => { copy(content) Toast.notify({ type: 'success', message: t('common.actionMsg.copySuccessfully') }) }}> <RiClipboardLine className='h-4 w-4' /> </ActionButton> - <ActionButton onClick={handleEdit}> + {enableEdit && <ActionButton onClick={handleEdit}> <RiEditLine className='h-4 w-4' /> - </ActionButton> + </ActionButton>} </div> </div> <div - className='w-full rounded-2xl bg-[#D1E9FF]/50 px-4 py-3 text-sm text-gray-900' + ref={contentRef} + className='w-full rounded-2xl bg-background-gradient-bg-fill-chat-bubble-bg-3 px-4 py-3 text-sm text-text-primary' style={theme?.chatBubbleColorStyle ? CssTransform(theme.chatBubbleColorStyle) : {}} > { diff --git a/web/app/components/base/chat/chat/type.ts b/web/app/components/base/chat/chat/type.ts index 7f22ba05b7..a9e2ada262 100644 --- a/web/app/components/base/chat/chat/type.ts +++ b/web/app/components/base/chat/chat/type.ts @@ -41,6 +41,7 @@ export type ThoughtItem = { tool_input: string tool_labels?: { [key: string]: TypeWithI18N } message_id: string + conversation_id: string observation: string position: number files?: string[] @@ -142,5 +143,6 @@ export type InputForm = { label: string variable: any required: boolean + hide: boolean [key: string]: any } diff --git a/web/app/components/base/chat/embedded-chatbot/chat-wrapper.tsx b/web/app/components/base/chat/embedded-chatbot/chat-wrapper.tsx index a06930c48f..d81d8af2f8 100644 --- a/web/app/components/base/chat/embedded-chatbot/chat-wrapper.tsx +++ b/web/app/components/base/chat/embedded-chatbot/chat-wrapper.tsx @@ -48,6 +48,7 @@ const ChatWrapper = () => { clearChatList, setClearChatList, setIsResponding, + allInputsHidden, } = useEmbeddedChatbotContext() const appConfig = useMemo(() => { const config = appParams || {} @@ -82,6 +83,9 @@ const ChatWrapper = () => { ) const inputsFormValue = currentConversationId ? currentConversationInputs : newConversationInputsRef?.current const inputDisabled = useMemo(() => { + if (allInputsHidden) + return false + let hasEmptyInput = '' let fileIsUploading = false const requiredVars = inputsForms.filter(({ required }) => required) @@ -111,7 +115,7 @@ const ChatWrapper = () => { if (fileIsUploading) return true return false - }, [inputsFormValue, inputsForms]) + }, [inputsFormValue, inputsForms, allInputsHidden]) useEffect(() => { if (currentChatInstanceRef.current) @@ -160,7 +164,7 @@ const ChatWrapper = () => { const [collapsed, setCollapsed] = useState(!!currentConversationId) const chatNode = useMemo(() => { - if (!inputsForms.length) + if (allInputsHidden || !inputsForms.length) return null if (isMobile) { if (!currentConversationId) @@ -170,7 +174,7 @@ const ChatWrapper = () => { else { return <InputsForm collapsed={collapsed} setCollapsed={setCollapsed} /> } - }, [inputsForms.length, isMobile, currentConversationId, collapsed]) + }, [inputsForms.length, isMobile, currentConversationId, collapsed, allInputsHidden]) const welcome = useMemo(() => { const welcomeMessage = chatList.find(item => item.isOpeningStatement) @@ -180,7 +184,7 @@ const ChatWrapper = () => { return null if (!welcomeMessage) return null - if (!collapsed && inputsForms.length > 0) + if (!collapsed && inputsForms.length > 0 && !allInputsHidden) return null if (welcomeMessage.suggestedQuestions && welcomeMessage.suggestedQuestions?.length > 0) { return ( @@ -215,7 +219,7 @@ const ChatWrapper = () => { </div> </div> ) - }, [appData?.site.icon, appData?.site.icon_background, appData?.site.icon_type, appData?.site.icon_url, chatList, collapsed, currentConversationId, inputsForms.length, respondingState]) + }, [appData?.site.icon, appData?.site.icon_background, appData?.site.icon_type, appData?.site.icon_url, chatList, collapsed, currentConversationId, inputsForms.length, respondingState, allInputsHidden]) const answerIcon = isDify() ? <LogoAvatar className='relative shrink-0' /> diff --git a/web/app/components/base/chat/embedded-chatbot/context.tsx b/web/app/components/base/chat/embedded-chatbot/context.tsx index eb5e9d697f..d24265ed9e 100644 --- a/web/app/components/base/chat/embedded-chatbot/context.tsx +++ b/web/app/components/base/chat/embedded-chatbot/context.tsx @@ -17,6 +17,7 @@ import type { import { noop } from 'lodash-es' export type EmbeddedChatbotContextValue = { + userCanAccess?: boolean appInfoError?: any appInfoLoading?: boolean appMeta?: AppMeta @@ -39,6 +40,7 @@ export type EmbeddedChatbotContextValue = { chatShouldReloadKey: string isMobile: boolean isInstalledApp: boolean + allowResetChat: boolean appId?: string handleFeedback: (messageId: string, feedback: Feedback) => void currentChatInstanceRef: RefObject<{ handleStop: () => void }> @@ -49,9 +51,11 @@ export type EmbeddedChatbotContextValue = { setIsResponding: (state: boolean) => void, currentConversationInputs: Record<string, any> | null, setCurrentConversationInputs: (v: Record<string, any>) => void, + allInputsHidden: boolean } export const EmbeddedChatbotContext = createContext<EmbeddedChatbotContextValue>({ + userCanAccess: false, currentConversationId: '', appPrevChatList: [], pinnedConversationList: [], @@ -67,6 +71,7 @@ export const EmbeddedChatbotContext = createContext<EmbeddedChatbotContextValue> chatShouldReloadKey: '', isMobile: false, isInstalledApp: false, + allowResetChat: true, handleFeedback: noop, currentChatInstanceRef: { current: { handleStop: noop } }, clearChatList: false, @@ -75,5 +80,6 @@ export const EmbeddedChatbotContext = createContext<EmbeddedChatbotContextValue> setIsResponding: noop, currentConversationInputs: {}, setCurrentConversationInputs: noop, + allInputsHidden: false, }) export const useEmbeddedChatbotContext = () => useContext(EmbeddedChatbotContext) diff --git a/web/app/components/base/chat/embedded-chatbot/header/index.tsx b/web/app/components/base/chat/embedded-chatbot/header/index.tsx index d05e7a650c..95975e29e7 100644 --- a/web/app/components/base/chat/embedded-chatbot/header/index.tsx +++ b/web/app/components/base/chat/embedded-chatbot/header/index.tsx @@ -1,6 +1,6 @@ import type { FC } from 'react' -import React from 'react' -import { RiResetLeftLine } from '@remixicon/react' +import React, { useCallback, useEffect, useState } from 'react' +import { RiCollapseDiagonal2Line, RiExpandDiagonal2Line, RiResetLeftLine } from '@remixicon/react' import { useTranslation } from 'react-i18next' import type { Theme } from '../theme/theme-context' import { CssTransform } from '../theme/utils' @@ -11,11 +11,13 @@ import Tooltip from '@/app/components/base/tooltip' import ActionButton from '@/app/components/base/action-button' import Divider from '@/app/components/base/divider' import ViewFormDropdown from '@/app/components/base/chat/embedded-chatbot/inputs-form/view-form-dropdown' -import LogoSite from '@/app/components/base/logo/logo-site' +import DifyLogo from '@/app/components/base/logo/dify-logo' import cn from '@/utils/classnames' +import { useGlobalPublicStore } from '@/context/global-public-context' export type IHeaderProps = { isMobile?: boolean + allowResetChat?: boolean customerIcon?: React.ReactNode title: string theme?: Theme @@ -23,6 +25,7 @@ export type IHeaderProps = { } const Header: FC<IHeaderProps> = ({ isMobile, + allowResetChat, customerIcon, title, theme, @@ -34,6 +37,45 @@ const Header: FC<IHeaderProps> = ({ currentConversationId, inputsForms, } = useEmbeddedChatbotContext() + + const isClient = typeof window !== 'undefined' + const isIframe = isClient ? window.self !== window.top : false + const [parentOrigin, setParentOrigin] = useState('') + const [showToggleExpandButton, setShowToggleExpandButton] = useState(false) + const [expanded, setExpanded] = useState(false) + const systemFeatures = useGlobalPublicStore(s => s.systemFeatures) + + const handleMessageReceived = useCallback((event: MessageEvent) => { + let currentParentOrigin = parentOrigin + if (!currentParentOrigin && event.data.type === 'dify-chatbot-config') { + currentParentOrigin = event.origin + setParentOrigin(event.origin) + } + if (event.origin !== currentParentOrigin) + return + if (event.data.type === 'dify-chatbot-config') + setShowToggleExpandButton(event.data.payload.isToggledByButton && !event.data.payload.isDraggable) + }, [parentOrigin]) + + useEffect(() => { + if (!isIframe) return + + const listener = (event: MessageEvent) => handleMessageReceived(event) + window.addEventListener('message', listener) + + window.parent.postMessage({ type: 'dify-chatbot-iframe-ready' }, '*') + + return () => window.removeEventListener('message', listener) + }, [isIframe, handleMessageReceived]) + + const handleToggleExpand = useCallback(() => { + if (!isIframe || !showToggleExpandButton) return + setExpanded(!expanded) + window.parent.postMessage({ + type: 'dify-chatbot-expand-change', + }, parentOrigin) + }, [isIframe, parentOrigin, showToggleExpandButton, expanded]) + if (!isMobile) { return ( <div className='flex h-14 shrink-0 items-center justify-end p-3'> @@ -45,19 +87,35 @@ const Header: FC<IHeaderProps> = ({ 'flex shrink-0 items-center gap-1.5 px-2', )}> <div className='system-2xs-medium-uppercase text-text-tertiary'>{t('share.chat.poweredBy')}</div> - {appData?.custom_config?.replace_webapp_logo && ( - <img src={appData?.custom_config?.replace_webapp_logo} alt='logo' className='block h-5 w-auto' /> - )} - {!appData?.custom_config?.replace_webapp_logo && ( - <LogoSite className='!h-5' /> - )} + { + systemFeatures.branding.enabled && systemFeatures.branding.workspace_logo + ? <img src={systemFeatures.branding.workspace_logo} alt='logo' className='block h-5 w-auto' /> + : appData?.custom_config?.replace_webapp_logo + ? <img src={`${appData?.custom_config?.replace_webapp_logo}`} alt='logo' className='block h-5 w-auto' /> + : <DifyLogo size='small' /> + } </div> )} </div> {currentConversationId && ( <Divider type='vertical' className='h-3.5' /> )} - {currentConversationId && ( + { + showToggleExpandButton && ( + <Tooltip + popupContent={expanded ? t('share.chat.collapse') : t('share.chat.expand')} + > + <ActionButton size='l' onClick={handleToggleExpand}> + { + expanded + ? <RiCollapseDiagonal2Line className='h-[18px] w-[18px]' /> + : <RiExpandDiagonal2Line className='h-[18px] w-[18px]' /> + } + </ActionButton> + </Tooltip> + ) + } + {currentConversationId && allowResetChat && ( <Tooltip popupContent={t('share.chat.resetChat')} > @@ -77,7 +135,7 @@ const Header: FC<IHeaderProps> = ({ return ( <div className={cn('flex h-14 shrink-0 items-center justify-between rounded-t-2xl px-3')} - style={Object.assign({}, CssTransform(theme?.backgroundHeaderColorStyle ?? ''), CssTransform(theme?.headerBorderBottomStyle ?? '')) } + style={Object.assign({}, CssTransform(theme?.backgroundHeaderColorStyle ?? ''), CssTransform(theme?.headerBorderBottomStyle ?? ''))} > <div className="flex grow items-center space-x-3"> {customerIcon} @@ -89,7 +147,22 @@ const Header: FC<IHeaderProps> = ({ </div> </div> <div className='flex items-center gap-1'> - {currentConversationId && ( + { + showToggleExpandButton && ( + <Tooltip + popupContent={expanded ? t('share.chat.collapse') : t('share.chat.expand')} + > + <ActionButton size='l' onClick={handleToggleExpand}> + { + expanded + ? <RiCollapseDiagonal2Line className={cn('h-[18px] w-[18px]', theme?.colorPathOnHeader)} /> + : <RiExpandDiagonal2Line className={cn('h-[18px] w-[18px]', theme?.colorPathOnHeader)} /> + } + </ActionButton> + </Tooltip> + ) + } + {currentConversationId && allowResetChat && ( <Tooltip popupContent={t('share.chat.resetChat')} > diff --git a/web/app/components/base/chat/embedded-chatbot/hooks.tsx b/web/app/components/base/chat/embedded-chatbot/hooks.tsx index cfd2acb17f..0158e8d041 100644 --- a/web/app/components/base/chat/embedded-chatbot/hooks.tsx +++ b/web/app/components/base/chat/embedded-chatbot/hooks.tsx @@ -36,6 +36,8 @@ import { InputVarType } from '@/app/components/workflow/types' import { TransferMethod } from '@/types/app' import { addFileInfos, sortAgentSorts } from '@/app/components/tools/utils' import { noop } from 'lodash-es' +import { useGetUserCanAccessApp } from '@/service/access-control' +import { useGlobalPublicStore } from '@/context/global-public-context' function getFormattedChatList(messages: any[]) { const newChatList: ChatItem[] = [] @@ -65,7 +67,13 @@ function getFormattedChatList(messages: any[]) { export const useEmbeddedChatbot = () => { const isInstalledApp = false + const systemFeatures = useGlobalPublicStore(s => s.systemFeatures) const { data: appInfo, isLoading: appInfoLoading, error: appInfoError } = useSWR('appInfo', fetchAppInfo) + const { isPending: isCheckingPermission, data: userCanAccessResult } = useGetUserCanAccessApp({ + appId: appInfo?.app_id, + isInstalledApp, + enabled: systemFeatures.webapp_auth.enabled, + }) const appData = useMemo(() => { return appInfo @@ -111,6 +119,7 @@ export const useEmbeddedChatbot = () => { const [conversationIdInfo, setConversationIdInfo] = useLocalStorageState<Record<string, Record<string, string>>>(CONVERSATION_ID_INFO, { defaultValue: {}, }) + const allowResetChat = !conversationId const currentConversationId = useMemo(() => conversationIdInfo?.[appId || '']?.[userId || 'DEFAULT'] || conversationId || '', [appId, conversationIdInfo, userId, conversationId]) const handleConversationIdInfoChange = useCallback((changeConversationId: string) => { @@ -220,6 +229,10 @@ export const useEmbeddedChatbot = () => { }) }, [initInputs, appParams]) + const allInputsHidden = useMemo(() => { + return inputsForms.length > 0 && inputsForms.every(item => item.hide === true) + }, [inputsForms]) + useEffect(() => { // init inputs from url params (async () => { @@ -291,6 +304,9 @@ export const useEmbeddedChatbot = () => { const { notify } = useToastContext() const checkInputsRequired = useCallback((silent?: boolean) => { + if (allInputsHidden) + return true + let hasEmptyInput = '' let fileIsUploading = false const requiredVars = inputsForms.filter(({ required }) => required) @@ -326,7 +342,7 @@ export const useEmbeddedChatbot = () => { } return true - }, [inputsForms, notify, t]) + }, [inputsForms, notify, t, allInputsHidden]) const handleStartChat = useCallback((callback?: any) => { if (checkInputsRequired()) { setShowNewConversationItemInList(true) @@ -363,8 +379,10 @@ export const useEmbeddedChatbot = () => { return { appInfoError, - appInfoLoading, + appInfoLoading: appInfoLoading || (systemFeatures.webapp_auth.enabled && isCheckingPermission), + userCanAccess: systemFeatures.webapp_auth.enabled ? userCanAccessResult?.result : true, isInstalledApp, + allowResetChat, appId, currentConversationId, currentConversationItem, @@ -399,5 +417,6 @@ export const useEmbeddedChatbot = () => { setIsResponding, currentConversationInputs, setCurrentConversationInputs, + allInputsHidden, } } diff --git a/web/app/components/base/chat/embedded-chatbot/index.tsx b/web/app/components/base/chat/embedded-chatbot/index.tsx index aaf59ca4e4..c54afd78ea 100644 --- a/web/app/components/base/chat/embedded-chatbot/index.tsx +++ b/web/app/components/base/chat/embedded-chatbot/index.tsx @@ -1,4 +1,6 @@ +'use client' import { + useCallback, useEffect, useState, } from 'react' @@ -12,19 +14,24 @@ import { useEmbeddedChatbot } from './hooks' import { isDify } from './utils' import { useThemeContext } from './theme/theme-context' import { CssTransform } from './theme/utils' -import { checkOrSetAccessToken } from '@/app/components/share/utils' +import { checkOrSetAccessToken, removeAccessToken } from '@/app/components/share/utils' import AppUnavailable from '@/app/components/base/app-unavailable' import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints' import Loading from '@/app/components/base/loading' import LogoHeader from '@/app/components/base/logo/logo-embedded-chat-header' import Header from '@/app/components/base/chat/embedded-chatbot/header' import ChatWrapper from '@/app/components/base/chat/embedded-chatbot/chat-wrapper' -import LogoSite from '@/app/components/base/logo/logo-site' +import DifyLogo from '@/app/components/base/logo/dify-logo' import cn from '@/utils/classnames' +import useDocumentTitle from '@/hooks/use-document-title' +import { useGlobalPublicStore } from '@/context/global-public-context' +import { usePathname, useRouter, useSearchParams } from 'next/navigation' const Chatbot = () => { const { + userCanAccess, isMobile, + allowResetChat, appInfoError, appInfoLoading, appData, @@ -32,8 +39,10 @@ const Chatbot = () => { chatShouldReloadKey, handleNewConversation, themeBuilder, + isInstalledApp, } = useEmbeddedChatbotContext() const { t } = useTranslation() + const systemFeatures = useGlobalPublicStore(s => s.systemFeatures) const customConfig = appData?.custom_config const site = appData?.site @@ -42,14 +51,26 @@ const Chatbot = () => { useEffect(() => { themeBuilder?.buildTheme(site?.chat_color_theme, site?.chat_color_theme_inverted) - if (site) { - if (customConfig) - document.title = `${site.title}` - else - document.title = `${site.title} - Powered by Dify` - } }, [site, customConfig, themeBuilder]) + useDocumentTitle(site?.title || 'Chat') + + const searchParams = useSearchParams() + const router = useRouter() + const pathname = usePathname() + const getSigninUrl = useCallback(() => { + const params = new URLSearchParams(searchParams) + params.delete('message') + params.set('redirect_url', pathname) + return `/webapp-signin?${params.toString()}` + }, [searchParams, pathname]) + + const backToHome = useCallback(() => { + removeAccessToken() + const url = getSigninUrl() + router.replace(url) + }, [getSigninUrl, router]) + if (appInfoLoading) { return ( <> @@ -65,6 +86,13 @@ const Chatbot = () => { ) } + if (!userCanAccess) { + return <div className='flex h-full flex-col items-center justify-center gap-y-2'> + <AppUnavailable className='h-auto w-auto' code={403} unknownReason='no permission.' /> + {!isInstalledApp && <span className='system-sm-regular cursor-pointer text-text-tertiary' onClick={backToHome}>{t('common.userProfile.logout')}</span>} + </div> + } + if (appInfoError) { return ( <> @@ -90,6 +118,7 @@ const Chatbot = () => { > <Header isMobile={isMobile} + allowResetChat={allowResetChat} title={site?.title || ''} customerIcon={isDify() ? difyIcon : ''} theme={themeBuilder?.theme} @@ -112,12 +141,13 @@ const Chatbot = () => { 'flex shrink-0 items-center gap-1.5 px-2', )}> <div className='system-2xs-medium-uppercase text-text-tertiary'>{t('share.chat.poweredBy')}</div> - {appData?.custom_config?.replace_webapp_logo && ( - <img src={appData?.custom_config?.replace_webapp_logo} alt='logo' className='block h-5 w-auto' /> - )} - {!appData?.custom_config?.replace_webapp_logo && ( - <LogoSite className='!h-5' /> - )} + { + systemFeatures.branding.enabled && systemFeatures.branding.workspace_logo + ? <img src={systemFeatures.branding.workspace_logo} alt='logo' className='block h-5 w-auto' /> + : appData?.custom_config?.replace_webapp_logo + ? <img src={`${appData?.custom_config?.replace_webapp_logo}`} alt='logo' className='block h-5 w-auto' /> + : <DifyLogo size='small' /> + } </div> )} </div> @@ -135,6 +165,7 @@ const EmbeddedChatbotWrapper = () => { appInfoError, appInfoLoading, appData, + userCanAccess, appParams, appMeta, appChatListDataLoading, @@ -153,6 +184,7 @@ const EmbeddedChatbotWrapper = () => { handleNewConversationCompleted, chatShouldReloadKey, isInstalledApp, + allowResetChat, appId, handleFeedback, currentChatInstanceRef, @@ -162,9 +194,11 @@ const EmbeddedChatbotWrapper = () => { setIsResponding, currentConversationInputs, setCurrentConversationInputs, + allInputsHidden, } = useEmbeddedChatbot() return <EmbeddedChatbotContext.Provider value={{ + userCanAccess, appInfoError, appInfoLoading, appData, @@ -187,6 +221,7 @@ const EmbeddedChatbotWrapper = () => { chatShouldReloadKey, isMobile, isInstalledApp, + allowResetChat, appId, handleFeedback, currentChatInstanceRef, @@ -197,6 +232,7 @@ const EmbeddedChatbotWrapper = () => { setIsResponding, currentConversationInputs, setCurrentConversationInputs, + allInputsHidden, }}> <Chatbot /> </EmbeddedChatbotContext.Provider> diff --git a/web/app/components/base/chat/embedded-chatbot/inputs-form/content.tsx b/web/app/components/base/chat/embedded-chatbot/inputs-form/content.tsx index 1a9a84ed92..c5f39718f1 100644 --- a/web/app/components/base/chat/embedded-chatbot/inputs-form/content.tsx +++ b/web/app/components/base/chat/embedded-chatbot/inputs-form/content.tsx @@ -1,4 +1,4 @@ -import React, { useCallback } from 'react' +import React, { memo, useCallback } from 'react' import { useTranslation } from 'react-i18next' import { useEmbeddedChatbotContext } from '../context' import Input from '@/app/components/base/input' @@ -36,9 +36,11 @@ const InputsFormContent = ({ showTip }: Props) => { }) }, [newConversationInputsRef, handleNewConversationInputsChange, currentConversationInputs, setCurrentConversationInputs]) + const visibleInputsForms = inputsForms.filter(form => form.hide !== true) + return ( <div className='space-y-4'> - {inputsForms.map(form => ( + {visibleInputsForms.map(form => ( <div key={form.variable} className='space-y-1'> <div className='flex h-6 items-center gap-1'> <div className='system-md-semibold text-text-secondary'>{form.label}</div> @@ -112,4 +114,4 @@ const InputsFormContent = ({ showTip }: Props) => { ) } -export default InputsFormContent +export default memo(InputsFormContent) diff --git a/web/app/components/base/chat/embedded-chatbot/inputs-form/index.tsx b/web/app/components/base/chat/embedded-chatbot/inputs-form/index.tsx index 4ac4aaa16b..88472b5d8f 100644 --- a/web/app/components/base/chat/embedded-chatbot/inputs-form/index.tsx +++ b/web/app/components/base/chat/embedded-chatbot/inputs-form/index.tsx @@ -22,8 +22,13 @@ const InputsFormNode = ({ currentConversationId, themeBuilder, handleStartChat, + allInputsHidden, + inputsForms, } = useEmbeddedChatbotContext() + if (allInputsHidden || inputsForms.length === 0) + return null + return ( <div className={cn('mb-6 flex flex-col items-center px-4 pt-6', isMobile && 'mb-4 pt-4')}> <div className={cn( diff --git a/web/app/components/base/chat/embedded-chatbot/theme/theme-context.ts b/web/app/components/base/chat/embedded-chatbot/theme/theme-context.ts index d4d617d4b7..321997ab1d 100644 --- a/web/app/components/base/chat/embedded-chatbot/theme/theme-context.ts +++ b/web/app/components/base/chat/embedded-chatbot/theme/theme-context.ts @@ -12,8 +12,7 @@ export class Theme { public colorPathOnHeader = 'text-text-primary-on-surface' public backgroundButtonDefaultColorStyle = 'backgroundColor: #1C64F2' public roundedBackgroundColorStyle = 'backgroundColor: rgb(245 248 255)' - public chatBubbleColorStyle = 'backgroundColor: rgb(225 239 254)' - public chatBubbleColor = 'rgb(225 239 254)' + public chatBubbleColorStyle = '' constructor(chatColorTheme: string | null = null, chatColorThemeInverted = false) { this.chatColorTheme = chatColorTheme @@ -29,7 +28,6 @@ export class Theme { this.backgroundButtonDefaultColorStyle = `backgroundColor: ${this.primaryColor}; color: ${this.colorFontOnHeaderStyle};` this.roundedBackgroundColorStyle = `backgroundColor: ${hexToRGBA(this.primaryColor, 0.05)}` this.chatBubbleColorStyle = `backgroundColor: ${hexToRGBA(this.primaryColor, 0.15)}` - this.chatBubbleColor = `${hexToRGBA(this.primaryColor, 0.15)}` } } diff --git a/web/app/components/base/chat/types.ts b/web/app/components/base/chat/types.ts index 95e52f084e..91f9bc976b 100644 --- a/web/app/components/base/chat/types.ts +++ b/web/app/components/base/chat/types.ts @@ -46,6 +46,7 @@ export type EnableType = { export type ChatConfig = Omit<ModelConfig, 'model'> & { supportAnnotation?: boolean appId?: string + questionEditEnable?: boolean supportFeedback?: boolean supportCitationHitInfo?: boolean } diff --git a/web/app/components/base/chat/utils.ts b/web/app/components/base/chat/utils.ts index bdac5998fe..8e044375d3 100644 --- a/web/app/components/base/chat/utils.ts +++ b/web/app/components/base/chat/utils.ts @@ -15,6 +15,17 @@ async function decodeBase64AndDecompress(base64String: string) { } } +async function getRawInputsFromUrlParams(): Promise<Record<string, any>> { + const urlParams = new URLSearchParams(window.location.search) + const inputs: Record<string, any> = {} + const entriesArray = Array.from(urlParams.entries()) + entriesArray.forEach(([key, value]) => { + if (!key.startsWith('sys.')) + inputs[key] = decodeURIComponent(value) + }) + return inputs +} + async function getProcessedInputsFromUrlParams(): Promise<Record<string, any>> { const urlParams = new URLSearchParams(window.location.search) const inputs: Record<string, any> = {} @@ -184,6 +195,7 @@ function getThreadMessages(tree: ChatItemInTree[], targetMessageId?: string): Ch } export { + getRawInputsFromUrlParams, getProcessedInputsFromUrlParams, getProcessedSystemVariablesFromUrlParams, isValidGeneratedAnswer, diff --git a/web/app/components/base/copy-btn/style.module.css b/web/app/components/base/copy-btn/style.module.css index 56c756025b..83625d6189 100644 --- a/web/app/components/base/copy-btn/style.module.css +++ b/web/app/components/base/copy-btn/style.module.css @@ -12,4 +12,4 @@ .copyIcon.copied { background-image: url(~@/app/components/develop/secret-key/assets/copied.svg); -} \ No newline at end of file +} diff --git a/web/app/components/base/features/new-feature-panel/annotation-reply/score-slider/base-slider/style.module.css b/web/app/components/base/features/new-feature-panel/annotation-reply/score-slider/base-slider/style.module.css index 4e93b39563..8ef23b54b5 100644 --- a/web/app/components/base/features/new-feature-panel/annotation-reply/score-slider/base-slider/style.module.css +++ b/web/app/components/base/features/new-feature-panel/annotation-reply/score-slider/base-slider/style.module.css @@ -17,4 +17,4 @@ .slider-track-1 { background-color: #E5E7EB; -} \ No newline at end of file +} diff --git a/web/app/components/base/features/new-feature-panel/index.tsx b/web/app/components/base/features/new-feature-panel/index.tsx index eee680596b..d00cd9038a 100644 --- a/web/app/components/base/features/new-feature-panel/index.tsx +++ b/web/app/components/base/features/new-feature-panel/index.tsx @@ -1,6 +1,5 @@ import React from 'react' import { useTranslation } from 'react-i18next' -import { useContext } from 'use-context-selector' import { RiCloseLine, RiInformation2Fill } from '@remixicon/react' import DialogWrapper from '@/app/components/base/features/new-feature-panel/dialog-wrapper' import { useDefaultModel } from '@/app/components/header/account-setting/model-provider-page/hooks' @@ -19,8 +18,7 @@ import Moderation from '@/app/components/base/features/new-feature-panel/moderat import AnnotationReply from '@/app/components/base/features/new-feature-panel/annotation-reply' import type { PromptVariable } from '@/models/debug' import type { InputVar } from '@/app/components/workflow/types' -import I18n from '@/context/i18n' -import { LanguagesSupported } from '@/i18n/language' +import { useDocLink } from '@/context/i18n' type Props = { show: boolean @@ -48,7 +46,7 @@ const NewFeaturePanel = ({ onAutoAddPromptVariable, }: Props) => { const { t } = useTranslation() - const { locale } = useContext(I18n) + const docLink = useDocLink() const { data: speech2textDefaultModel } = useDefaultModel(ModelTypeEnum.speech2text) const { data: text2speechDefaultModel } = useDefaultModel(ModelTypeEnum.tts) @@ -80,7 +78,7 @@ const NewFeaturePanel = ({ <span>{isChatMode ? t('workflow.common.fileUploadTip') : t('workflow.common.ImageUploadLegacyTip')}</span> <a className='text-text-accent' - href={`https://docs.dify.ai/${locale === LanguagesSupported[1] ? 'v/zh-hans/' : ''}guides/workflow/bulletin`} + href={docLink('/guides/workflow/bulletin')} target='_blank' rel='noopener noreferrer' >{t('workflow.common.featuresDocLink')}</a> </div> diff --git a/web/app/components/base/file-uploader/file-uploader-in-chat-input/file-image-item.tsx b/web/app/components/base/file-uploader/file-uploader-in-chat-input/file-image-item.tsx index df2e03db2d..5160348002 100644 --- a/web/app/components/base/file-uploader/file-uploader-in-chat-input/file-image-item.tsx +++ b/web/app/components/base/file-uploader/file-uploader-in-chat-input/file-image-item.tsx @@ -32,6 +32,7 @@ const FileImageItem = ({ }: FileImageItemProps) => { const { id, progress, base64Url, url, name } = file const [imagePreviewUrl, setImagePreviewUrl] = useState('') + const download_url = url ? `${url}&as_attachment=true` : base64Url return ( <> @@ -84,7 +85,7 @@ const FileImageItem = ({ className='absolute bottom-0.5 right-0.5 flex h-6 w-6 items-center justify-center rounded-lg bg-components-actionbar-bg shadow-md' onClick={(e) => { e.stopPropagation() - downloadFile(url || base64Url || '', name) + downloadFile(download_url || '', name) }} > <RiDownloadLine className='h-4 w-4 text-text-tertiary' /> diff --git a/web/app/components/base/file-uploader/file-uploader-in-chat-input/file-item.tsx b/web/app/components/base/file-uploader/file-uploader-in-chat-input/file-item.tsx index a098406160..667bf7cc15 100644 --- a/web/app/components/base/file-uploader/file-uploader-in-chat-input/file-item.tsx +++ b/web/app/components/base/file-uploader/file-uploader-in-chat-input/file-item.tsx @@ -45,6 +45,7 @@ const FileItem = ({ let tmp_preview_url = url || base64Url if (!tmp_preview_url && file?.originalFile) tmp_preview_url = URL.createObjectURL(file.originalFile.slice()).toString() + const download_url = url ? `${url}&as_attachment=true` : base64Url return ( <> @@ -93,13 +94,13 @@ const FileItem = ({ } </div> { - showDownloadAction && tmp_preview_url && ( + showDownloadAction && download_url && ( <ActionButton size='m' className='absolute -right-1 -top-1 hidden group-hover/file-item:flex' onClick={(e) => { e.stopPropagation() - downloadFile(tmp_preview_url || '', name) + downloadFile(download_url || '', name) }} > <RiDownloadLine className='h-3.5 w-3.5 text-text-tertiary' /> diff --git a/web/app/components/base/file-uploader/hooks.ts b/web/app/components/base/file-uploader/hooks.ts index 66d5b46ba7..8e1b2148c5 100644 --- a/web/app/components/base/file-uploader/hooks.ts +++ b/web/app/components/base/file-uploader/hooks.ts @@ -231,7 +231,7 @@ export const useFile = (fileConfig: FileUpload) => { url: res.url, } if (!isAllowedFileExtension(res.name, res.mime_type, fileConfig.allowed_file_types || [], fileConfig.allowed_file_extensions || [])) { - notify({ type: 'error', message: t('common.fileUploader.fileExtensionNotSupport') }) + notify({ type: 'error', message: `${t('common.fileUploader.fileExtensionNotSupport')} ${file.type}` }) handleRemoveFile(uploadingFile.id) } if (!checkSizeLimit(newFile.supportFileType, newFile.size)) @@ -257,7 +257,7 @@ export const useFile = (fileConfig: FileUpload) => { const handleLocalFileUpload = useCallback((file: File) => { if (!isAllowedFileExtension(file.name, file.type, fileConfig.allowed_file_types || [], fileConfig.allowed_file_extensions || [])) { - notify({ type: 'error', message: t('common.fileUploader.fileExtensionNotSupport') }) + notify({ type: 'error', message: `${t('common.fileUploader.fileExtensionNotSupport')} ${file.type}` }) return } const allowedFileTypes = fileConfig.allowed_file_types diff --git a/web/app/components/base/file-uploader/utils.spec.ts b/web/app/components/base/file-uploader/utils.spec.ts index c8cf9fbe74..4a3408ef00 100644 --- a/web/app/components/base/file-uploader/utils.spec.ts +++ b/web/app/components/base/file-uploader/utils.spec.ts @@ -22,7 +22,7 @@ import { FILE_EXTS } from '../prompt-editor/constants' jest.mock('mime', () => ({ __esModule: true, default: { - getExtension: jest.fn(), + getAllExtensions: jest.fn(), }, })) @@ -58,12 +58,27 @@ describe('file-uploader utils', () => { describe('getFileExtension', () => { it('should get extension from mimetype', () => { - jest.mocked(mime.getExtension).mockReturnValue('pdf') + jest.mocked(mime.getAllExtensions).mockReturnValue(new Set(['pdf'])) expect(getFileExtension('file', 'application/pdf')).toBe('pdf') }) + it('should get extension from mimetype and file name 1', () => { + jest.mocked(mime.getAllExtensions).mockReturnValue(new Set(['pdf'])) + expect(getFileExtension('file.pdf', 'application/pdf')).toBe('pdf') + }) + + it('should get extension from mimetype with multiple ext candidates with filename hint', () => { + jest.mocked(mime.getAllExtensions).mockReturnValue(new Set(['der', 'crt', 'pem'])) + expect(getFileExtension('file.pem', 'application/x-x509-ca-cert')).toBe('pem') + }) + + it('should get extension from mimetype with multiple ext candidates without filename hint', () => { + jest.mocked(mime.getAllExtensions).mockReturnValue(new Set(['der', 'crt', 'pem'])) + expect(getFileExtension('file', 'application/x-x509-ca-cert')).toBe('der') + }) + it('should get extension from filename if mimetype fails', () => { - jest.mocked(mime.getExtension).mockReturnValue(null) + jest.mocked(mime.getAllExtensions).mockReturnValue(null) expect(getFileExtension('file.txt', '')).toBe('txt') expect(getFileExtension('file.txt.docx', '')).toBe('docx') expect(getFileExtension('file', '')).toBe('') @@ -76,157 +91,157 @@ describe('file-uploader utils', () => { describe('getFileAppearanceType', () => { it('should identify gif files', () => { - jest.mocked(mime.getExtension).mockReturnValue('gif') + jest.mocked(mime.getAllExtensions).mockReturnValue(new Set(['gif'])) expect(getFileAppearanceType('image.gif', 'image/gif')) .toBe(FileAppearanceTypeEnum.gif) }) it('should identify image files', () => { - jest.mocked(mime.getExtension).mockReturnValue('jpg') + jest.mocked(mime.getAllExtensions).mockReturnValue(new Set(['jpg'])) expect(getFileAppearanceType('image.jpg', 'image/jpeg')) .toBe(FileAppearanceTypeEnum.image) - jest.mocked(mime.getExtension).mockReturnValue('jpeg') + jest.mocked(mime.getAllExtensions).mockReturnValue(new Set(['jpeg'])) expect(getFileAppearanceType('image.jpeg', 'image/jpeg')) .toBe(FileAppearanceTypeEnum.image) - jest.mocked(mime.getExtension).mockReturnValue('png') + jest.mocked(mime.getAllExtensions).mockReturnValue(new Set(['png'])) expect(getFileAppearanceType('image.png', 'image/png')) .toBe(FileAppearanceTypeEnum.image) - jest.mocked(mime.getExtension).mockReturnValue('webp') + jest.mocked(mime.getAllExtensions).mockReturnValue(new Set(['webp'])) expect(getFileAppearanceType('image.webp', 'image/webp')) .toBe(FileAppearanceTypeEnum.image) - jest.mocked(mime.getExtension).mockReturnValue('svg') + jest.mocked(mime.getAllExtensions).mockReturnValue(new Set(['svg'])) expect(getFileAppearanceType('image.svg', 'image/svgxml')) .toBe(FileAppearanceTypeEnum.image) }) it('should identify video files', () => { - jest.mocked(mime.getExtension).mockReturnValue('mp4') + jest.mocked(mime.getAllExtensions).mockReturnValue(new Set(['mp4'])) expect(getFileAppearanceType('video.mp4', 'video/mp4')) .toBe(FileAppearanceTypeEnum.video) - jest.mocked(mime.getExtension).mockReturnValue('mov') + jest.mocked(mime.getAllExtensions).mockReturnValue(new Set(['mov'])) expect(getFileAppearanceType('video.mov', 'video/quicktime')) .toBe(FileAppearanceTypeEnum.video) - jest.mocked(mime.getExtension).mockReturnValue('mpeg') + jest.mocked(mime.getAllExtensions).mockReturnValue(new Set(['mpeg'])) expect(getFileAppearanceType('video.mpeg', 'video/mpeg')) .toBe(FileAppearanceTypeEnum.video) - jest.mocked(mime.getExtension).mockReturnValue('webm') + jest.mocked(mime.getAllExtensions).mockReturnValue(new Set(['webm'])) expect(getFileAppearanceType('video.web', 'video/webm')) .toBe(FileAppearanceTypeEnum.video) }) it('should identify audio files', () => { - jest.mocked(mime.getExtension).mockReturnValue('mp3') + jest.mocked(mime.getAllExtensions).mockReturnValue(new Set(['mp3'])) expect(getFileAppearanceType('audio.mp3', 'audio/mpeg')) .toBe(FileAppearanceTypeEnum.audio) - jest.mocked(mime.getExtension).mockReturnValue('m4a') + jest.mocked(mime.getAllExtensions).mockReturnValue(new Set(['m4a'])) expect(getFileAppearanceType('audio.m4a', 'audio/mp4')) .toBe(FileAppearanceTypeEnum.audio) - jest.mocked(mime.getExtension).mockReturnValue('wav') + jest.mocked(mime.getAllExtensions).mockReturnValue(new Set(['wav'])) expect(getFileAppearanceType('audio.wav', 'audio/vnd.wav')) .toBe(FileAppearanceTypeEnum.audio) - jest.mocked(mime.getExtension).mockReturnValue('amr') + jest.mocked(mime.getAllExtensions).mockReturnValue(new Set(['amr'])) expect(getFileAppearanceType('audio.amr', 'audio/AMR')) .toBe(FileAppearanceTypeEnum.audio) - jest.mocked(mime.getExtension).mockReturnValue('mpga') + jest.mocked(mime.getAllExtensions).mockReturnValue(new Set(['mpga'])) expect(getFileAppearanceType('audio.mpga', 'audio/mpeg')) .toBe(FileAppearanceTypeEnum.audio) }) it('should identify code files', () => { - jest.mocked(mime.getExtension).mockReturnValue('html') + jest.mocked(mime.getAllExtensions).mockReturnValue(new Set(['html'])) expect(getFileAppearanceType('index.html', 'text/html')) .toBe(FileAppearanceTypeEnum.code) }) it('should identify PDF files', () => { - jest.mocked(mime.getExtension).mockReturnValue('pdf') + jest.mocked(mime.getAllExtensions).mockReturnValue(new Set(['pdf'])) expect(getFileAppearanceType('doc.pdf', 'application/pdf')) .toBe(FileAppearanceTypeEnum.pdf) }) it('should identify markdown files', () => { - jest.mocked(mime.getExtension).mockReturnValue('md') + jest.mocked(mime.getAllExtensions).mockReturnValue(new Set(['md'])) expect(getFileAppearanceType('file.md', 'text/markdown')) .toBe(FileAppearanceTypeEnum.markdown) - jest.mocked(mime.getExtension).mockReturnValue('markdown') + jest.mocked(mime.getAllExtensions).mockReturnValue(new Set(['markdown'])) expect(getFileAppearanceType('file.markdown', 'text/markdown')) .toBe(FileAppearanceTypeEnum.markdown) - jest.mocked(mime.getExtension).mockReturnValue('mdx') + jest.mocked(mime.getAllExtensions).mockReturnValue(new Set(['mdx'])) expect(getFileAppearanceType('file.mdx', 'text/mdx')) .toBe(FileAppearanceTypeEnum.markdown) }) it('should identify excel files', () => { - jest.mocked(mime.getExtension).mockReturnValue('xlsx') + jest.mocked(mime.getAllExtensions).mockReturnValue(new Set(['xlsx'])) expect(getFileAppearanceType('doc.xlsx', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet')) .toBe(FileAppearanceTypeEnum.excel) - jest.mocked(mime.getExtension).mockReturnValue('xls') + jest.mocked(mime.getAllExtensions).mockReturnValue(new Set(['xls'])) expect(getFileAppearanceType('doc.xls', 'application/vnd.ms-excel')) .toBe(FileAppearanceTypeEnum.excel) }) it('should identify word files', () => { - jest.mocked(mime.getExtension).mockReturnValue('doc') + jest.mocked(mime.getAllExtensions).mockReturnValue(new Set(['doc'])) expect(getFileAppearanceType('doc.doc', 'application/msword')) .toBe(FileAppearanceTypeEnum.word) - jest.mocked(mime.getExtension).mockReturnValue('docx') + jest.mocked(mime.getAllExtensions).mockReturnValue(new Set(['docx'])) expect(getFileAppearanceType('doc.docx', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document')) .toBe(FileAppearanceTypeEnum.word) }) it('should identify word files', () => { - jest.mocked(mime.getExtension).mockReturnValue('ppt') + jest.mocked(mime.getAllExtensions).mockReturnValue(new Set(['ppt'])) expect(getFileAppearanceType('doc.ppt', 'application/vnd.ms-powerpoint')) .toBe(FileAppearanceTypeEnum.ppt) - jest.mocked(mime.getExtension).mockReturnValue('pptx') + jest.mocked(mime.getAllExtensions).mockReturnValue(new Set(['pptx'])) expect(getFileAppearanceType('doc.pptx', 'application/vnd.openxmlformats-officedocument.presentationml.presentation')) .toBe(FileAppearanceTypeEnum.ppt) }) it('should identify document files', () => { - jest.mocked(mime.getExtension).mockReturnValue('txt') + jest.mocked(mime.getAllExtensions).mockReturnValue(new Set(['txt'])) expect(getFileAppearanceType('file.txt', 'text/plain')) .toBe(FileAppearanceTypeEnum.document) - jest.mocked(mime.getExtension).mockReturnValue('csv') + jest.mocked(mime.getAllExtensions).mockReturnValue(new Set(['csv'])) expect(getFileAppearanceType('file.csv', 'text/csv')) .toBe(FileAppearanceTypeEnum.document) - jest.mocked(mime.getExtension).mockReturnValue('msg') + jest.mocked(mime.getAllExtensions).mockReturnValue(new Set(['msg'])) expect(getFileAppearanceType('file.msg', 'application/vnd.ms-outlook')) .toBe(FileAppearanceTypeEnum.document) - jest.mocked(mime.getExtension).mockReturnValue('eml') + jest.mocked(mime.getAllExtensions).mockReturnValue(new Set(['eml'])) expect(getFileAppearanceType('file.eml', 'message/rfc822')) .toBe(FileAppearanceTypeEnum.document) - jest.mocked(mime.getExtension).mockReturnValue('xml') + jest.mocked(mime.getAllExtensions).mockReturnValue(new Set(['xml'])) expect(getFileAppearanceType('file.xml', 'application/rssxml')) .toBe(FileAppearanceTypeEnum.document) - jest.mocked(mime.getExtension).mockReturnValue('epub') + jest.mocked(mime.getAllExtensions).mockReturnValue(new Set(['epub'])) expect(getFileAppearanceType('file.epub', 'application/epubzip')) .toBe(FileAppearanceTypeEnum.document) }) it('should handle null mime extension', () => { - jest.mocked(mime.getExtension).mockReturnValue(null) + jest.mocked(mime.getAllExtensions).mockReturnValue(null) expect(getFileAppearanceType('file.txt', 'text/plain')) .toBe(FileAppearanceTypeEnum.document) }) @@ -360,7 +375,7 @@ describe('file-uploader utils', () => { describe('isAllowedFileExtension', () => { it('should validate allowed file extensions', () => { - jest.mocked(mime.getExtension).mockReturnValue('pdf') + jest.mocked(mime.getAllExtensions).mockReturnValue(new Set(['pdf'])) expect(isAllowedFileExtension( 'test.pdf', 'application/pdf', diff --git a/web/app/components/base/file-uploader/utils.ts b/web/app/components/base/file-uploader/utils.ts index e05c0b2087..9b5a449481 100644 --- a/web/app/components/base/file-uploader/utils.ts +++ b/web/app/components/base/file-uploader/utils.ts @@ -42,19 +42,38 @@ export const fileUpload: FileUpload = ({ }) } +const additionalExtensionMap = new Map<string, string[]>([ + ['text/x-markdown', ['md']], +]) + export const getFileExtension = (fileName: string, fileMimetype: string, isRemote?: boolean) => { let extension = '' - if (fileMimetype) - extension = mime.getExtension(fileMimetype) || '' + let extensions = new Set<string>() + if (fileMimetype) { + const extensionsFromMimeType = mime.getAllExtensions(fileMimetype) || new Set<string>() + const additionalExtensions = additionalExtensionMap.get(fileMimetype) || [] + extensions = new Set<string>([ + ...extensionsFromMimeType, + ...additionalExtensions, + ]) + } - if (fileName && !extension) { + let extensionInFileName = '' + if (fileName) { const fileNamePair = fileName.split('.') const fileNamePairLength = fileNamePair.length - if (fileNamePairLength > 1) - extension = fileNamePair[fileNamePairLength - 1] + if (fileNamePairLength > 1) { + extensionInFileName = fileNamePair[fileNamePairLength - 1].toLowerCase() + if (extensions.has(extensionInFileName)) + extension = extensionInFileName + } + } + if (!extension) { + if (extensions.size > 0) + extension = extensions.values().next().value.toLowerCase() else - extension = '' + extension = extensionInFileName } if (isRemote) diff --git a/web/app/components/base/grid-mask/style.module.css b/web/app/components/base/grid-mask/style.module.css index 4d135b3cfc..e051271fab 100644 --- a/web/app/components/base/grid-mask/style.module.css +++ b/web/app/components/base/grid-mask/style.module.css @@ -2,4 +2,4 @@ background-image: url(./Grid.svg); background-repeat: repeat; background-position: 0 0; -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/assets/public/tracing/weave-icon-big.svg b/web/app/components/base/icons/assets/public/tracing/weave-icon-big.svg new file mode 100644 index 0000000000..9b1f9a8f16 --- /dev/null +++ b/web/app/components/base/icons/assets/public/tracing/weave-icon-big.svg @@ -0,0 +1,33 @@ +<?xml version="1.0" encoding="UTF-8"?> +<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="124px" height="16px" viewBox="0 0 120 16" version="1.1"> +<g id="surface1"> +<path style=" stroke:none;fill-rule:nonzero;fill:rgb(10.196079%,11.372549%,14.117648%);fill-opacity:1;" d="M 20.847656 3.292969 C 20.875 3.292969 20.902344 3.292969 20.933594 3.292969 C 20.949219 3.292969 20.964844 3.292969 20.980469 3.292969 C 21.035156 3.292969 21.089844 3.292969 21.140625 3.292969 C 21.179688 3.292969 21.21875 3.292969 21.253906 3.292969 C 21.359375 3.292969 21.464844 3.292969 21.566406 3.292969 C 21.675781 3.292969 21.78125 3.292969 21.890625 3.292969 C 22.097656 3.292969 22.300781 3.292969 22.507812 3.292969 C 22.738281 3.292969 22.972656 3.292969 23.207031 3.296875 C 23.6875 3.296875 24.167969 3.296875 24.648438 3.296875 C 24.648438 3.519531 24.648438 3.742188 24.648438 3.96875 C 24.113281 4.042969 24.113281 4.042969 23.566406 4.113281 C 23.667969 4.496094 23.769531 4.882812 23.867188 5.265625 C 23.878906 5.308594 23.878906 5.308594 23.890625 5.351562 C 24.128906 6.269531 24.371094 7.183594 24.609375 8.097656 C 24.675781 8.339844 24.738281 8.582031 24.800781 8.824219 C 24.816406 8.878906 24.832031 8.933594 24.84375 8.992188 C 24.867188 9.078125 24.890625 9.167969 24.914062 9.257812 C 24.921875 9.289062 24.933594 9.320312 24.941406 9.355469 C 24.953125 9.398438 24.964844 9.441406 24.976562 9.484375 C 24.984375 9.523438 24.984375 9.523438 24.996094 9.558594 C 25.007812 9.625 25.007812 9.625 25.007812 9.71875 C 25.023438 9.71875 25.039062 9.71875 25.054688 9.71875 C 25.058594 9.707031 25.058594 9.695312 25.0625 9.679688 C 25.097656 9.492188 25.152344 9.3125 25.210938 9.128906 C 25.222656 9.097656 25.234375 9.0625 25.246094 9.027344 C 25.269531 8.953125 25.292969 8.882812 25.316406 8.808594 C 25.355469 8.691406 25.390625 8.574219 25.429688 8.457031 C 25.464844 8.339844 25.503906 8.21875 25.542969 8.097656 C 25.660156 7.738281 25.773438 7.375 25.890625 7.011719 C 25.902344 6.96875 25.917969 6.921875 25.933594 6.875 C 26.226562 5.945312 26.519531 5.019531 26.808594 4.089844 C 26.785156 4.089844 26.765625 4.089844 26.742188 4.085938 C 26.507812 4.074219 26.273438 4.046875 26.042969 4.015625 C 26.007812 4.011719 25.972656 4.007812 25.933594 4.003906 C 25.851562 3.992188 25.765625 3.980469 25.679688 3.96875 C 25.679688 3.746094 25.679688 3.523438 25.679688 3.296875 C 26.175781 3.296875 26.667969 3.296875 27.160156 3.296875 C 27.390625 3.292969 27.621094 3.292969 27.851562 3.292969 C 28.050781 3.292969 28.25 3.292969 28.449219 3.292969 C 28.554688 3.292969 28.660156 3.292969 28.765625 3.292969 C 28.867188 3.292969 28.964844 3.292969 29.066406 3.292969 C 29.101562 3.292969 29.140625 3.292969 29.175781 3.292969 C 29.226562 3.292969 29.273438 3.292969 29.324219 3.292969 C 29.367188 3.292969 29.367188 3.292969 29.410156 3.292969 C 29.472656 3.296875 29.472656 3.296875 29.496094 3.320312 C 29.5 3.367188 29.5 3.417969 29.5 3.464844 C 29.5 3.492188 29.5 3.515625 29.5 3.542969 C 29.496094 3.59375 29.496094 3.59375 29.496094 3.648438 C 29.496094 3.753906 29.496094 3.859375 29.496094 3.96875 C 29.09375 4.015625 28.6875 4.066406 28.273438 4.113281 C 28.679688 5.460938 28.679688 5.460938 29.089844 6.808594 C 29.105469 6.859375 29.121094 6.910156 29.136719 6.960938 C 29.234375 7.292969 29.335938 7.625 29.4375 7.960938 C 29.484375 8.113281 29.53125 8.265625 29.578125 8.417969 C 29.605469 8.507812 29.632812 8.597656 29.660156 8.691406 C 29.878906 9.40625 29.878906 9.40625 29.976562 9.746094 C 30.027344 9.664062 30.046875 9.601562 30.070312 9.507812 C 30.078125 9.484375 30.078125 9.484375 30.085938 9.457031 C 30.101562 9.402344 30.117188 9.34375 30.132812 9.289062 C 30.144531 9.25 30.152344 9.207031 30.164062 9.167969 C 30.1875 9.082031 30.214844 8.992188 30.238281 8.90625 C 30.292969 8.691406 30.351562 8.480469 30.410156 8.269531 C 30.433594 8.191406 30.453125 8.117188 30.472656 8.042969 C 30.621094 7.5 30.769531 6.960938 30.921875 6.421875 C 30.949219 6.324219 30.976562 6.226562 31 6.128906 C 31.066406 5.902344 31.128906 5.675781 31.191406 5.449219 C 31.230469 5.308594 31.269531 5.164062 31.308594 5.023438 C 31.335938 4.925781 31.363281 4.828125 31.390625 4.734375 C 31.402344 4.6875 31.414062 4.640625 31.429688 4.59375 C 31.445312 4.53125 31.464844 4.46875 31.480469 4.40625 C 31.488281 4.386719 31.492188 4.367188 31.496094 4.347656 C 31.515625 4.277344 31.535156 4.207031 31.558594 4.136719 C 31.210938 4.074219 30.855469 4.023438 30.503906 3.96875 C 30.503906 3.746094 30.503906 3.523438 30.503906 3.296875 C 30.878906 3.296875 31.253906 3.296875 31.628906 3.296875 C 31.804688 3.292969 31.976562 3.292969 32.152344 3.292969 C 32.304688 3.292969 32.457031 3.292969 32.605469 3.292969 C 32.6875 3.292969 32.769531 3.292969 32.847656 3.292969 C 32.9375 3.292969 33.027344 3.292969 33.117188 3.292969 C 33.144531 3.292969 33.171875 3.292969 33.199219 3.292969 C 33.222656 3.292969 33.246094 3.292969 33.273438 3.292969 C 33.304688 3.292969 33.304688 3.292969 33.335938 3.292969 C 33.382812 3.296875 33.382812 3.296875 33.40625 3.320312 C 33.410156 3.367188 33.410156 3.414062 33.410156 3.460938 C 33.410156 3.488281 33.410156 3.515625 33.410156 3.542969 C 33.410156 3.574219 33.410156 3.605469 33.410156 3.632812 C 33.410156 3.664062 33.410156 3.695312 33.410156 3.726562 C 33.410156 3.796875 33.410156 3.871094 33.40625 3.945312 C 33.292969 3.964844 33.175781 3.984375 33.0625 4.007812 C 33.023438 4.011719 32.984375 4.019531 32.945312 4.027344 C 32.738281 4.0625 32.535156 4.097656 32.328125 4.113281 C 32.320312 4.144531 32.320312 4.144531 32.3125 4.179688 C 32.238281 4.480469 32.15625 4.78125 32.070312 5.082031 C 32.058594 5.128906 32.042969 5.171875 32.03125 5.21875 C 31.875 5.78125 31.714844 6.347656 31.550781 6.910156 C 31.375 7.535156 31.195312 8.160156 31.019531 8.785156 C 30.992188 8.871094 30.96875 8.957031 30.945312 9.042969 C 30.835938 9.433594 30.722656 9.820312 30.613281 10.210938 C 30.566406 10.378906 30.519531 10.542969 30.472656 10.707031 C 30.445312 10.804688 30.417969 10.902344 30.390625 11 C 30.277344 11.390625 30.167969 11.785156 30.046875 12.175781 C 29.730469 12.175781 29.414062 12.175781 29.089844 12.175781 C 29.03125 12.003906 29.03125 12.003906 28.976562 11.832031 C 28.925781 11.675781 28.878906 11.523438 28.828125 11.367188 C 28.820312 11.347656 28.8125 11.328125 28.808594 11.304688 C 28.632812 10.769531 28.460938 10.230469 28.285156 9.695312 C 28.144531 9.273438 28.007812 8.847656 27.875 8.425781 C 27.695312 7.867188 27.515625 7.308594 27.332031 6.753906 C 27.304688 6.679688 27.28125 6.605469 27.257812 6.53125 C 27.238281 6.476562 27.222656 6.425781 27.207031 6.375 C 27.046875 5.894531 27.046875 5.894531 27.046875 5.796875 C 27.03125 5.796875 27.015625 5.796875 27 5.796875 C 26.996094 5.8125 26.996094 5.828125 26.992188 5.84375 C 26.964844 5.988281 26.925781 6.132812 26.882812 6.273438 C 26.875 6.296875 26.867188 6.316406 26.859375 6.339844 C 26.84375 6.390625 26.828125 6.4375 26.8125 6.488281 C 26.769531 6.625 26.726562 6.761719 26.683594 6.898438 C 26.675781 6.929688 26.664062 6.957031 26.65625 6.988281 C 26.546875 7.328125 26.445312 7.667969 26.339844 8.007812 C 26.316406 8.078125 26.296875 8.144531 26.273438 8.214844 C 26.230469 8.355469 26.1875 8.496094 26.144531 8.636719 C 26.074219 8.863281 26.007812 9.089844 25.9375 9.3125 C 25.933594 9.328125 25.925781 9.347656 25.921875 9.363281 C 25.894531 9.449219 25.871094 9.535156 25.84375 9.617188 C 25.796875 9.769531 25.75 9.921875 25.703125 10.074219 C 25.675781 10.15625 25.652344 10.242188 25.625 10.328125 C 25.613281 10.363281 25.605469 10.394531 25.59375 10.429688 C 25.414062 11.011719 25.234375 11.59375 25.054688 12.175781 C 24.738281 12.175781 24.421875 12.175781 24.097656 12.175781 C 23.816406 11.230469 23.535156 10.285156 23.261719 9.339844 C 23.253906 9.320312 23.25 9.304688 23.246094 9.285156 C 23.195312 9.117188 23.144531 8.949219 23.097656 8.78125 C 22.960938 8.3125 22.824219 7.84375 22.6875 7.375 C 22.664062 7.304688 22.644531 7.234375 22.625 7.164062 C 22.414062 6.449219 22.207031 5.738281 22 5.027344 C 21.976562 4.953125 21.953125 4.878906 21.933594 4.804688 C 21.898438 4.683594 21.859375 4.5625 21.824219 4.441406 C 21.820312 4.421875 21.8125 4.402344 21.808594 4.382812 C 21.796875 4.347656 21.785156 4.3125 21.777344 4.28125 C 21.753906 4.203125 21.742188 4.148438 21.742188 4.066406 C 21.726562 4.066406 21.710938 4.0625 21.691406 4.0625 C 21.382812 4.042969 21.070312 4.003906 20.761719 3.96875 C 20.757812 3.863281 20.757812 3.753906 20.757812 3.648438 C 20.757812 3.617188 20.757812 3.585938 20.757812 3.554688 C 20.757812 3.523438 20.757812 3.496094 20.757812 3.464844 C 20.757812 3.4375 20.757812 3.410156 20.757812 3.382812 C 20.761719 3.296875 20.761719 3.296875 20.847656 3.292969 Z M 20.847656 3.292969 "/> +<path style=" stroke:none;fill-rule:nonzero;fill:rgb(10.196079%,11.372549%,14.117648%);fill-opacity:1;" d="M 82.488281 3.25 C 83.046875 3.246094 83.605469 3.246094 84.167969 3.246094 C 84.425781 3.246094 84.6875 3.246094 84.945312 3.246094 C 85.171875 3.242188 85.398438 3.242188 85.625 3.242188 C 85.746094 3.242188 85.867188 3.242188 85.984375 3.242188 C 88.15625 3.238281 88.15625 3.238281 88.894531 3.898438 C 88.914062 3.914062 88.9375 3.929688 88.957031 3.945312 C 89.191406 4.144531 89.363281 4.402344 89.472656 4.691406 C 89.480469 4.714844 89.492188 4.742188 89.5 4.765625 C 89.65625 5.25 89.601562 5.785156 89.382812 6.234375 C 89.117188 6.753906 88.695312 7.078125 88.152344 7.265625 C 87.984375 7.320312 87.816406 7.367188 87.648438 7.410156 C 87.664062 7.414062 87.679688 7.417969 87.699219 7.421875 C 88.523438 7.605469 89.300781 7.851562 89.78125 8.597656 C 90.0625 9.0625 90.125 9.636719 90.003906 10.164062 C 89.808594 10.804688 89.363281 11.304688 88.78125 11.621094 C 88.324219 11.863281 87.820312 11.988281 87.3125 12.054688 C 87.28125 12.058594 87.253906 12.0625 87.222656 12.066406 C 86.777344 12.121094 86.332031 12.109375 85.882812 12.105469 C 85.765625 12.105469 85.644531 12.105469 85.523438 12.105469 C 85.300781 12.105469 85.074219 12.105469 84.847656 12.105469 C 84.589844 12.105469 84.332031 12.105469 84.074219 12.105469 C 83.546875 12.105469 83.015625 12.101562 82.488281 12.101562 C 82.488281 11.878906 82.488281 11.65625 82.488281 11.429688 C 82.859375 11.390625 83.234375 11.347656 83.617188 11.308594 C 83.617188 8.910156 83.617188 6.511719 83.617188 4.042969 C 83.488281 4.035156 83.363281 4.027344 83.230469 4.019531 C 83.117188 4.007812 83.003906 3.996094 82.890625 3.980469 C 82.863281 3.980469 82.832031 3.976562 82.804688 3.972656 C 82.695312 3.960938 82.59375 3.949219 82.488281 3.921875 C 82.488281 3.699219 82.488281 3.476562 82.488281 3.25 Z M 85.390625 3.96875 C 85.390625 4.242188 85.386719 4.515625 85.382812 4.785156 C 85.382812 4.914062 85.378906 5.039062 85.378906 5.164062 C 85.371094 5.824219 85.367188 6.484375 85.367188 7.144531 C 86.488281 7.183594 86.488281 7.183594 87.457031 6.691406 C 87.796875 6.320312 87.859375 5.832031 87.847656 5.351562 C 87.832031 4.992188 87.71875 4.644531 87.460938 4.378906 C 87 3.96875 86.363281 3.964844 85.78125 3.96875 C 85.742188 3.96875 85.703125 3.96875 85.667969 3.96875 C 85.574219 3.96875 85.484375 3.96875 85.390625 3.96875 Z M 85.390625 7.84375 C 85.390625 9.003906 85.390625 10.160156 85.390625 11.355469 C 86.28125 11.386719 86.28125 11.386719 87.152344 11.21875 C 87.171875 11.214844 87.1875 11.207031 87.207031 11.199219 C 87.578125 11.066406 87.886719 10.824219 88.066406 10.46875 C 88.28125 9.988281 88.289062 9.417969 88.125 8.921875 C 87.960938 8.492188 87.664062 8.234375 87.257812 8.046875 C 86.664062 7.804688 86.023438 7.84375 85.390625 7.84375 Z M 85.390625 7.84375 "/> +<path style=" stroke:none;fill-rule:nonzero;fill:rgb(10.196079%,11.372549%,14.117648%);fill-opacity:1;" d="M 76.167969 3.476562 C 76.367188 3.671875 76.507812 3.917969 76.585938 4.1875 C 76.589844 4.203125 76.59375 4.222656 76.601562 4.242188 C 76.707031 4.675781 76.621094 5.144531 76.414062 5.53125 C 76.34375 5.644531 76.265625 5.746094 76.175781 5.847656 C 76.15625 5.867188 76.136719 5.886719 76.117188 5.910156 C 75.71875 6.332031 75.199219 6.617188 74.6875 6.882812 C 74.707031 6.902344 74.726562 6.921875 74.746094 6.941406 C 74.972656 7.191406 74.972656 7.191406 75.066406 7.296875 C 75.140625 7.382812 75.21875 7.464844 75.300781 7.542969 C 75.351562 7.59375 75.394531 7.640625 75.4375 7.695312 C 75.527344 7.796875 75.621094 7.894531 75.714844 7.992188 C 76.089844 8.394531 76.089844 8.394531 76.253906 8.585938 C 76.351562 8.695312 76.449219 8.800781 76.546875 8.90625 C 76.621094 8.980469 76.691406 9.058594 76.761719 9.136719 C 76.773438 9.152344 76.789062 9.164062 76.800781 9.179688 C 76.824219 9.207031 76.851562 9.234375 76.875 9.261719 C 76.933594 9.324219 76.992188 9.382812 77.0625 9.429688 C 77.070312 9.410156 77.070312 9.410156 77.082031 9.386719 C 77.113281 9.304688 77.152344 9.230469 77.195312 9.15625 C 77.5625 8.476562 77.800781 7.753906 77.976562 7 C 77.953125 7 77.933594 6.996094 77.910156 6.996094 C 77.707031 6.96875 77.5 6.9375 77.296875 6.902344 C 77.273438 6.898438 77.25 6.894531 77.222656 6.890625 C 77.050781 6.859375 77.050781 6.859375 76.96875 6.832031 C 76.960938 6.328125 76.960938 6.328125 77.015625 6.160156 C 77.949219 6.160156 78.886719 6.160156 79.847656 6.160156 C 79.847656 6.367188 79.847656 6.574219 79.847656 6.785156 C 79.53125 6.839844 79.214844 6.894531 78.886719 6.953125 C 78.859375 7.046875 78.832031 7.140625 78.804688 7.234375 C 78.539062 8.09375 78.164062 9.035156 77.601562 9.746094 C 77.5625 9.792969 77.5625 9.792969 77.566406 9.851562 C 77.601562 9.933594 77.648438 9.980469 77.714844 10.039062 C 77.792969 10.113281 77.867188 10.1875 77.9375 10.269531 C 78.027344 10.375 78.125 10.46875 78.222656 10.566406 C 78.308594 10.65625 78.390625 10.742188 78.472656 10.839844 C 78.539062 10.914062 78.601562 10.933594 78.695312 10.949219 C 78.71875 10.953125 78.746094 10.957031 78.769531 10.960938 C 78.796875 10.964844 78.824219 10.96875 78.851562 10.972656 C 78.875 10.980469 78.902344 10.984375 78.933594 10.988281 C 79.019531 11.003906 79.105469 11.019531 79.191406 11.03125 C 79.277344 11.046875 79.363281 11.0625 79.449219 11.078125 C 79.503906 11.085938 79.558594 11.097656 79.613281 11.105469 C 79.648438 11.113281 79.648438 11.113281 79.6875 11.117188 C 79.707031 11.121094 79.730469 11.125 79.75 11.128906 C 79.800781 11.140625 79.800781 11.140625 79.824219 11.164062 C 79.820312 11.421875 79.785156 11.679688 79.753906 11.933594 C 79.691406 11.949219 79.632812 11.964844 79.570312 11.980469 C 79.546875 11.984375 79.546875 11.984375 79.519531 11.992188 C 79.214844 12.066406 78.910156 12.085938 78.597656 12.085938 C 78.539062 12.085938 78.484375 12.085938 78.425781 12.085938 C 77.847656 12.089844 77.332031 11.917969 76.894531 11.523438 C 76.855469 11.484375 76.816406 11.445312 76.777344 11.40625 C 76.71875 11.347656 76.660156 11.296875 76.601562 11.242188 C 76.578125 11.21875 76.578125 11.21875 76.554688 11.195312 C 76.515625 11.160156 76.476562 11.125 76.441406 11.089844 C 76.429688 11.101562 76.417969 11.109375 76.410156 11.117188 C 76.140625 11.351562 75.859375 11.554688 75.542969 11.71875 C 75.511719 11.738281 75.476562 11.757812 75.445312 11.777344 C 75.3125 11.847656 75.179688 11.894531 75.039062 11.9375 C 75.011719 11.945312 75.011719 11.945312 74.984375 11.953125 C 74.632812 12.058594 74.269531 12.089844 73.90625 12.085938 C 73.84375 12.085938 73.785156 12.085938 73.722656 12.089844 C 72.941406 12.089844 72.222656 11.824219 71.652344 11.28125 C 71.203125 10.820312 71.023438 10.246094 71.03125 9.609375 C 71.042969 9.058594 71.230469 8.546875 71.59375 8.132812 C 71.609375 8.113281 71.625 8.09375 71.644531 8.070312 C 71.980469 7.683594 72.398438 7.421875 72.839844 7.171875 C 72.871094 7.152344 72.902344 7.132812 72.9375 7.113281 C 72.960938 7.101562 72.984375 7.085938 73.007812 7.074219 C 72.996094 7.0625 72.988281 7.050781 72.976562 7.042969 C 72.398438 6.425781 72.09375 5.613281 72.113281 4.773438 C 72.128906 4.371094 72.257812 3.988281 72.527344 3.679688 C 72.542969 3.660156 72.558594 3.644531 72.570312 3.625 C 72.917969 3.210938 73.496094 2.996094 74.015625 2.933594 C 74.050781 2.929688 74.050781 2.929688 74.082031 2.925781 C 74.804688 2.847656 75.621094 2.964844 76.167969 3.476562 Z M 73.671875 3.796875 C 73.433594 4.113281 73.414062 4.4375 73.457031 4.820312 C 73.550781 5.460938 73.921875 5.9375 74.328125 6.425781 C 74.398438 6.390625 74.449219 6.355469 74.503906 6.300781 C 74.527344 6.28125 74.527344 6.28125 74.550781 6.257812 C 74.566406 6.242188 74.582031 6.226562 74.597656 6.210938 C 74.613281 6.191406 74.628906 6.175781 74.644531 6.160156 C 74.773438 6.03125 74.890625 5.894531 75 5.75 C 75.019531 5.726562 75.035156 5.699219 75.054688 5.675781 C 75.335938 5.292969 75.5 4.859375 75.457031 4.378906 C 75.40625 4.078125 75.289062 3.820312 75.035156 3.636719 C 74.59375 3.363281 74.03125 3.410156 73.671875 3.796875 Z M 73.046875 7.828125 C 72.664062 8.226562 72.519531 8.789062 72.519531 9.332031 C 72.53125 9.800781 72.71875 10.257812 73.039062 10.601562 C 73.46875 10.996094 73.980469 11.140625 74.550781 11.125 C 74.960938 11.105469 75.339844 11.003906 75.703125 10.8125 C 75.71875 10.804688 75.738281 10.796875 75.753906 10.785156 C 75.84375 10.738281 75.90625 10.699219 75.960938 10.609375 C 75.949219 10.601562 75.941406 10.589844 75.933594 10.582031 C 75.460938 10.074219 75.460938 10.074219 75.25 9.8125 C 75.1875 9.738281 75.125 9.664062 75.0625 9.59375 C 74.972656 9.484375 74.882812 9.375 74.796875 9.265625 C 74.695312 9.132812 74.589844 9.003906 74.480469 8.878906 C 74.390625 8.773438 74.304688 8.667969 74.214844 8.5625 C 74.152344 8.484375 74.085938 8.40625 74.019531 8.328125 C 73.921875 8.214844 73.828125 8.101562 73.734375 7.984375 C 73.726562 7.96875 73.714844 7.957031 73.703125 7.941406 C 73.683594 7.914062 73.660156 7.886719 73.640625 7.859375 C 73.589844 7.792969 73.539062 7.730469 73.488281 7.667969 C 73.460938 7.632812 73.460938 7.632812 73.433594 7.601562 C 73.414062 7.578125 73.414062 7.578125 73.390625 7.554688 C 73.265625 7.554688 73.132812 7.742188 73.046875 7.828125 Z M 73.046875 7.828125 "/> +<path style=" stroke:none;fill-rule:nonzero;fill:rgb(10.196079%,11.372549%,14.117648%);fill-opacity:1;" d="M 49.992188 5.535156 C 50.101562 5.609375 50.101562 5.609375 50.136719 5.679688 C 50.136719 5.746094 50.140625 5.8125 50.140625 5.882812 C 50.140625 5.914062 50.140625 5.914062 50.140625 5.941406 C 50.140625 5.984375 50.140625 6.027344 50.140625 6.070312 C 50.140625 6.136719 50.140625 6.203125 50.140625 6.269531 C 50.140625 6.308594 50.140625 6.351562 50.140625 6.390625 C 50.140625 6.410156 50.140625 6.429688 50.140625 6.453125 C 50.140625 6.589844 50.140625 6.589844 50.113281 6.617188 C 50.074219 6.617188 50.035156 6.621094 49.996094 6.621094 C 49.972656 6.621094 49.949219 6.621094 49.921875 6.621094 C 49.894531 6.621094 49.871094 6.617188 49.84375 6.617188 C 49.816406 6.617188 49.789062 6.617188 49.757812 6.617188 C 49.671875 6.617188 49.585938 6.617188 49.5 6.617188 C 49.441406 6.617188 49.378906 6.617188 49.320312 6.617188 C 49.175781 6.617188 49.03125 6.617188 48.886719 6.617188 C 48.898438 6.640625 48.90625 6.660156 48.917969 6.683594 C 48.929688 6.714844 48.945312 6.746094 48.957031 6.773438 C 48.964844 6.789062 48.96875 6.804688 48.976562 6.820312 C 49.203125 7.339844 49.195312 8 48.988281 8.523438 C 48.75 9.0625 48.355469 9.457031 47.804688 9.671875 C 47.066406 9.941406 46.210938 9.941406 45.457031 9.746094 C 45.277344 10.003906 45.214844 10.273438 45.238281 10.585938 C 45.269531 10.699219 45.316406 10.761719 45.402344 10.835938 C 45.617188 10.945312 45.851562 10.949219 46.089844 10.949219 C 46.113281 10.953125 46.132812 10.953125 46.15625 10.953125 C 46.203125 10.953125 46.25 10.953125 46.292969 10.953125 C 46.367188 10.953125 46.441406 10.953125 46.515625 10.953125 C 46.726562 10.953125 46.9375 10.957031 47.144531 10.957031 C 47.273438 10.957031 47.402344 10.957031 47.53125 10.960938 C 47.582031 10.960938 47.628906 10.960938 47.675781 10.960938 C 48.324219 10.960938 49.039062 11.019531 49.53125 11.492188 C 49.546875 11.511719 49.566406 11.53125 49.585938 11.550781 C 49.601562 11.566406 49.617188 11.582031 49.636719 11.597656 C 49.957031 11.929688 50.0625 12.394531 50.066406 12.84375 C 50.054688 13.351562 49.847656 13.800781 49.511719 14.171875 C 49.496094 14.191406 49.480469 14.207031 49.460938 14.226562 C 48.8125 14.921875 47.769531 15.179688 46.855469 15.210938 C 45.890625 15.234375 44.761719 15.230469 44.015625 14.523438 C 43.738281 14.222656 43.660156 13.886719 43.671875 13.488281 C 43.679688 13.363281 43.699219 13.253906 43.753906 13.136719 C 43.761719 13.117188 43.769531 13.09375 43.78125 13.074219 C 43.996094 12.644531 44.386719 12.410156 44.785156 12.175781 C 44.765625 12.167969 44.746094 12.160156 44.730469 12.152344 C 44.398438 11.996094 44.222656 11.808594 44.089844 11.476562 C 43.988281 11.136719 44.070312 10.757812 44.222656 10.453125 C 44.421875 10.109375 44.695312 9.824219 44.976562 9.550781 C 44.960938 9.542969 44.945312 9.53125 44.925781 9.523438 C 44.757812 9.417969 44.613281 9.304688 44.472656 9.167969 C 44.457031 9.152344 44.441406 9.136719 44.425781 9.121094 C 44.214844 8.902344 44.085938 8.597656 44.015625 8.300781 C 44.011719 8.28125 44.003906 8.257812 44 8.238281 C 43.882812 7.675781 43.964844 7.042969 44.277344 6.558594 C 44.621094 6.070312 45.09375 5.773438 45.671875 5.628906 C 45.6875 5.625 45.703125 5.621094 45.71875 5.617188 C 46.25 5.492188 46.917969 5.496094 47.449219 5.628906 C 47.464844 5.632812 47.480469 5.636719 47.496094 5.640625 C 47.6875 5.691406 47.867188 5.761719 48.046875 5.84375 C 48.0625 5.851562 48.078125 5.859375 48.09375 5.867188 C 48.164062 5.902344 48.226562 5.933594 48.289062 5.980469 C 48.390625 6.066406 48.390625 6.066406 48.515625 6.082031 C 48.582031 6.0625 48.644531 6.035156 48.707031 6.003906 C 48.730469 5.996094 48.753906 5.984375 48.78125 5.976562 C 48.855469 5.941406 48.929688 5.910156 49.003906 5.875 C 49.054688 5.851562 49.101562 5.832031 49.152344 5.808594 C 49.320312 5.738281 49.488281 5.664062 49.652344 5.585938 C 49.679688 5.574219 49.703125 5.566406 49.730469 5.554688 C 49.75 5.542969 49.769531 5.535156 49.789062 5.523438 C 49.867188 5.503906 49.917969 5.515625 49.992188 5.535156 Z M 45.835938 6.507812 C 45.472656 6.984375 45.421875 7.597656 45.492188 8.175781 C 45.550781 8.542969 45.6875 8.890625 45.980469 9.132812 C 46.207031 9.285156 46.46875 9.3125 46.734375 9.277344 C 47.015625 9.21875 47.210938 9.089844 47.375 8.855469 C 47.683594 8.375 47.742188 7.746094 47.640625 7.191406 C 47.5625 6.859375 47.402344 6.507812 47.117188 6.308594 C 46.703125 6.074219 46.152344 6.148438 45.835938 6.507812 Z M 45.238281 12.367188 C 44.957031 12.734375 44.867188 13.113281 44.902344 13.570312 C 44.957031 13.84375 45.09375 14.058594 45.316406 14.226562 C 45.613281 14.417969 46.015625 14.496094 46.367188 14.507812 C 46.394531 14.507812 46.394531 14.507812 46.417969 14.507812 C 47.132812 14.527344 47.90625 14.457031 48.453125 13.945312 C 48.652344 13.738281 48.710938 13.515625 48.703125 13.230469 C 48.683594 12.992188 48.570312 12.800781 48.394531 12.644531 C 48.113281 12.441406 47.726562 12.449219 47.398438 12.449219 C 47.355469 12.449219 47.3125 12.449219 47.269531 12.449219 C 47.15625 12.449219 47.046875 12.449219 46.933594 12.449219 C 46.753906 12.449219 46.574219 12.445312 46.394531 12.445312 C 46.332031 12.445312 46.269531 12.445312 46.210938 12.445312 C 45.882812 12.445312 45.5625 12.414062 45.238281 12.367188 Z M 45.238281 12.367188 "/> +<path style=" stroke:none;fill-rule:nonzero;fill:rgb(10.196079%,11.372549%,14.117648%);fill-opacity:1;" d="M 53.039062 2.382812 C 53.0625 2.390625 53.0625 2.390625 53.085938 2.398438 C 53.140625 2.429688 53.171875 2.453125 53.207031 2.503906 C 53.230469 2.617188 53.21875 2.730469 53.214844 2.84375 C 53.210938 2.878906 53.210938 2.914062 53.210938 2.953125 C 53.207031 3.027344 53.203125 3.105469 53.199219 3.183594 C 53.191406 3.371094 53.183594 3.558594 53.179688 3.746094 C 53.175781 3.792969 53.175781 3.835938 53.175781 3.882812 C 53.15625 4.441406 53.15625 5 53.15625 5.5625 C 53.15625 5.640625 53.15625 5.71875 53.15625 5.792969 C 53.15625 6.085938 53.160156 6.375 53.160156 6.664062 C 53.179688 6.644531 53.195312 6.625 53.214844 6.605469 C 53.238281 6.578125 53.261719 6.550781 53.285156 6.523438 C 53.296875 6.511719 53.3125 6.5 53.324219 6.484375 C 53.78125 5.984375 54.445312 5.601562 55.128906 5.550781 C 55.640625 5.535156 56.128906 5.578125 56.527344 5.929688 C 56.566406 5.964844 56.601562 6 56.640625 6.039062 C 56.664062 6.0625 56.664062 6.0625 56.691406 6.089844 C 57.246094 6.660156 57.203125 7.570312 57.203125 8.304688 C 57.203125 8.414062 57.203125 8.523438 57.207031 8.632812 C 57.207031 8.839844 57.207031 9.042969 57.207031 9.25 C 57.207031 9.484375 57.210938 9.722656 57.210938 9.957031 C 57.210938 10.4375 57.214844 10.921875 57.214844 11.40625 C 57.246094 11.410156 57.246094 11.410156 57.28125 11.414062 C 57.308594 11.417969 57.335938 11.421875 57.363281 11.425781 C 57.40625 11.433594 57.40625 11.433594 57.445312 11.441406 C 57.558594 11.457031 57.671875 11.480469 57.785156 11.503906 C 57.808594 11.507812 57.828125 11.511719 57.851562 11.515625 C 57.878906 11.519531 57.878906 11.519531 57.910156 11.527344 C 57.929688 11.53125 57.949219 11.535156 57.964844 11.539062 C 58.007812 11.550781 58.007812 11.550781 58.03125 11.574219 C 58.035156 11.613281 58.035156 11.65625 58.035156 11.695312 C 58.035156 11.71875 58.035156 11.746094 58.035156 11.769531 C 58.035156 11.796875 58.035156 11.824219 58.035156 11.851562 C 58.035156 11.875 58.035156 11.902344 58.035156 11.929688 C 58.035156 11.964844 58.035156 11.964844 58.035156 12.003906 C 58.035156 12.027344 58.035156 12.050781 58.035156 12.074219 C 58.03125 12.125 58.03125 12.125 58.007812 12.148438 C 57.964844 12.152344 57.921875 12.152344 57.882812 12.152344 C 57.839844 12.152344 57.839844 12.152344 57.796875 12.152344 C 57.769531 12.152344 57.738281 12.152344 57.707031 12.152344 C 57.675781 12.152344 57.640625 12.152344 57.609375 12.152344 C 57.523438 12.152344 57.433594 12.152344 57.347656 12.152344 C 57.257812 12.152344 57.164062 12.152344 57.074219 12.152344 C 56.917969 12.152344 56.765625 12.152344 56.613281 12.152344 C 56.433594 12.152344 56.257812 12.152344 56.082031 12.152344 C 55.929688 12.152344 55.777344 12.152344 55.625 12.152344 C 55.53125 12.152344 55.441406 12.152344 55.351562 12.152344 C 55.265625 12.152344 55.179688 12.152344 55.09375 12.152344 C 55.046875 12.152344 55 12.152344 54.953125 12.152344 C 54.925781 12.152344 54.898438 12.152344 54.871094 12.152344 C 54.847656 12.152344 54.824219 12.152344 54.796875 12.152344 C 54.742188 12.148438 54.742188 12.148438 54.71875 12.125 C 54.71875 12.085938 54.71875 12.042969 54.71875 12.003906 C 54.71875 11.976562 54.71875 11.953125 54.71875 11.925781 C 54.71875 11.902344 54.71875 11.875 54.71875 11.847656 C 54.71875 11.820312 54.71875 11.796875 54.71875 11.769531 C 54.71875 11.703125 54.71875 11.636719 54.71875 11.574219 C 54.8125 11.53125 54.902344 11.507812 55.003906 11.488281 C 55.03125 11.480469 55.0625 11.476562 55.09375 11.46875 C 55.113281 11.464844 55.128906 11.460938 55.144531 11.460938 C 55.191406 11.449219 55.242188 11.441406 55.289062 11.429688 C 55.527344 11.378906 55.527344 11.378906 55.585938 11.378906 C 55.582031 10.90625 55.582031 10.433594 55.578125 9.960938 C 55.578125 9.742188 55.578125 9.523438 55.574219 9.304688 C 55.574219 9.109375 55.574219 8.917969 55.574219 8.726562 C 55.574219 8.625 55.570312 8.527344 55.570312 8.425781 C 55.570312 8.328125 55.570312 8.234375 55.570312 8.136719 C 55.570312 8.101562 55.570312 8.066406 55.570312 8.03125 C 55.570312 7.664062 55.554688 7.199219 55.289062 6.917969 C 55.054688 6.722656 54.742188 6.746094 54.457031 6.761719 C 54.101562 6.800781 53.738281 7.007812 53.464844 7.234375 C 53.425781 7.265625 53.425781 7.265625 53.371094 7.300781 C 53.273438 7.371094 53.214844 7.421875 53.1875 7.539062 C 53.179688 7.640625 53.179688 7.742188 53.183594 7.84375 C 53.183594 7.882812 53.183594 7.921875 53.183594 7.960938 C 53.183594 8.066406 53.183594 8.167969 53.1875 8.273438 C 53.1875 8.386719 53.1875 8.496094 53.1875 8.605469 C 53.1875 8.8125 53.191406 9.023438 53.191406 9.230469 C 53.195312 9.46875 53.195312 9.703125 53.195312 9.941406 C 53.199219 10.429688 53.203125 10.917969 53.207031 11.40625 C 53.238281 11.410156 53.238281 11.410156 53.265625 11.414062 C 53.351562 11.429688 53.4375 11.445312 53.523438 11.464844 C 53.554688 11.46875 53.585938 11.472656 53.613281 11.480469 C 53.644531 11.484375 53.671875 11.492188 53.703125 11.496094 C 53.730469 11.5 53.753906 11.507812 53.78125 11.511719 C 53.847656 11.523438 53.910156 11.535156 53.976562 11.550781 C 53.976562 11.746094 53.976562 11.945312 53.976562 12.148438 C 52.890625 12.148438 51.804688 12.148438 50.6875 12.148438 C 50.6875 11.953125 50.6875 11.753906 50.6875 11.550781 C 50.964844 11.492188 51.242188 11.4375 51.527344 11.378906 C 51.542969 11.160156 51.554688 10.945312 51.554688 10.722656 C 51.554688 10.691406 51.554688 10.660156 51.554688 10.628906 C 51.554688 10.546875 51.558594 10.464844 51.558594 10.378906 C 51.558594 10.289062 51.558594 10.199219 51.558594 10.109375 C 51.558594 9.953125 51.558594 9.796875 51.558594 9.640625 C 51.558594 9.414062 51.558594 9.1875 51.5625 8.960938 C 51.5625 8.59375 51.5625 8.230469 51.5625 7.863281 C 51.566406 7.507812 51.566406 7.152344 51.566406 6.792969 C 51.566406 6.773438 51.566406 6.75 51.566406 6.726562 C 51.566406 6.617188 51.566406 6.507812 51.566406 6.398438 C 51.570312 5.484375 51.574219 4.570312 51.574219 3.65625 C 51.554688 3.65625 51.535156 3.652344 51.515625 3.652344 C 51.476562 3.648438 51.476562 3.648438 51.4375 3.644531 C 51.414062 3.644531 51.386719 3.640625 51.359375 3.640625 C 51.277344 3.632812 51.195312 3.621094 51.113281 3.609375 C 51.082031 3.605469 51.054688 3.601562 51.023438 3.597656 C 50.996094 3.59375 50.964844 3.589844 50.933594 3.582031 C 50.902344 3.578125 50.871094 3.574219 50.839844 3.570312 C 50.765625 3.558594 50.691406 3.546875 50.617188 3.535156 C 50.617188 3.347656 50.617188 3.15625 50.617188 2.960938 C 50.910156 2.875 51.207031 2.796875 51.5 2.722656 C 51.523438 2.714844 51.542969 2.710938 51.5625 2.707031 C 51.671875 2.679688 51.777344 2.652344 51.886719 2.625 C 51.972656 2.601562 52.0625 2.578125 52.152344 2.554688 C 52.257812 2.527344 52.367188 2.5 52.472656 2.472656 C 52.515625 2.460938 52.554688 2.453125 52.597656 2.441406 C 52.652344 2.425781 52.710938 2.410156 52.765625 2.398438 C 52.78125 2.394531 52.800781 2.386719 52.816406 2.382812 C 52.898438 2.363281 52.960938 2.351562 53.039062 2.382812 Z M 53.039062 2.382812 "/> +<path style=" stroke:none;fill-rule:nonzero;fill:rgb(10.196079%,11.372549%,14.117648%);fill-opacity:1;" d="M 99.691406 6.011719 C 100.109375 6.40625 100.234375 7.003906 100.273438 7.554688 C 100.277344 7.667969 100.277344 7.785156 100.277344 7.898438 C 100.277344 7.933594 100.277344 7.964844 100.277344 8 C 100.277344 8.074219 100.277344 8.144531 100.277344 8.21875 C 100.277344 8.332031 100.277344 8.445312 100.277344 8.558594 C 100.28125 8.886719 100.28125 9.210938 100.28125 9.535156 C 100.28125 9.714844 100.28125 9.894531 100.285156 10.074219 C 100.285156 10.171875 100.285156 10.265625 100.285156 10.359375 C 100.285156 10.449219 100.285156 10.539062 100.285156 10.628906 C 100.285156 10.675781 100.285156 10.726562 100.285156 10.773438 C 100.289062 10.964844 100.292969 11.167969 100.417969 11.320312 C 100.53125 11.378906 100.636719 11.398438 100.757812 11.367188 C 100.898438 11.308594 101.003906 11.21875 101.113281 11.117188 C 101.226562 11.199219 101.339844 11.289062 101.449219 11.378906 C 101.394531 11.527344 101.300781 11.640625 101.207031 11.765625 C 101.179688 11.804688 101.179688 11.804688 101.152344 11.84375 C 100.859375 12.152344 100.476562 12.265625 100.058594 12.277344 C 99.699219 12.269531 99.332031 12.164062 99.066406 11.910156 C 98.933594 11.757812 98.761719 11.519531 98.761719 11.308594 C 98.582031 11.449219 98.582031 11.449219 98.417969 11.605469 C 98.289062 11.738281 98.140625 11.847656 97.992188 11.957031 C 97.96875 11.972656 97.949219 11.992188 97.925781 12.007812 C 97.488281 12.3125 96.855469 12.339844 96.34375 12.25 C 95.917969 12.15625 95.527344 11.929688 95.28125 11.558594 C 95.035156 11.136719 94.964844 10.617188 95.082031 10.140625 C 95.289062 9.527344 95.746094 9.175781 96.300781 8.894531 C 96.941406 8.582031 97.644531 8.375 98.328125 8.179688 C 98.34375 8.175781 98.359375 8.171875 98.375 8.167969 C 98.472656 8.140625 98.566406 8.113281 98.664062 8.085938 C 98.695312 7.195312 98.695312 7.195312 98.328125 6.425781 C 98.121094 6.230469 97.828125 6.203125 97.558594 6.203125 C 97.53125 6.203125 97.53125 6.203125 97.503906 6.203125 C 97.339844 6.207031 97.171875 6.21875 97.007812 6.230469 C 97.003906 6.257812 97.003906 6.289062 97 6.316406 C 96.984375 6.46875 96.957031 6.617188 96.925781 6.765625 C 96.917969 6.816406 96.910156 6.863281 96.902344 6.910156 C 96.832031 7.273438 96.738281 7.585938 96.425781 7.808594 C 96.191406 7.933594 95.945312 7.933594 95.6875 7.867188 C 95.515625 7.808594 95.40625 7.707031 95.320312 7.546875 C 95.234375 7.347656 95.238281 7.183594 95.308594 6.980469 C 95.316406 6.957031 95.316406 6.957031 95.324219 6.929688 C 95.421875 6.644531 95.574219 6.441406 95.785156 6.230469 C 95.800781 6.214844 95.816406 6.195312 95.835938 6.179688 C 96.734375 5.308594 98.742188 5.21875 99.691406 6.011719 Z M 98.394531 8.742188 C 98.292969 8.777344 98.1875 8.8125 98.082031 8.847656 C 97.574219 9.007812 97.011719 9.230469 96.746094 9.722656 C 96.582031 10.042969 96.554688 10.355469 96.648438 10.703125 C 96.71875 10.914062 96.816406 11.042969 97.011719 11.152344 C 97.296875 11.292969 97.609375 11.304688 97.910156 11.199219 C 98.058594 11.132812 98.207031 11.050781 98.34375 10.960938 C 98.398438 10.921875 98.398438 10.921875 98.46875 10.890625 C 98.558594 10.839844 98.644531 10.789062 98.679688 10.683594 C 98.703125 10.542969 98.695312 10.402344 98.691406 10.257812 C 98.6875 10.214844 98.6875 10.167969 98.6875 10.121094 C 98.6875 10 98.683594 9.878906 98.683594 9.757812 C 98.679688 9.636719 98.679688 9.511719 98.675781 9.386719 C 98.675781 9.144531 98.667969 8.902344 98.664062 8.660156 C 98.578125 8.660156 98.476562 8.714844 98.394531 8.742188 Z M 98.394531 8.742188 "/> +<path style=" stroke:none;fill-rule:nonzero;fill:rgb(10.196079%,11.372549%,14.117648%);fill-opacity:1;" d="M 38.035156 6.242188 C 38.207031 6.40625 38.332031 6.578125 38.449219 6.785156 C 38.460938 6.808594 38.460938 6.808594 38.472656 6.832031 C 38.835938 7.472656 38.875 8.257812 38.761719 8.972656 C 38.734375 9 38.734375 9 38.671875 9 C 38.625 9 38.625 9 38.578125 9 C 38.5625 9 38.546875 9 38.53125 9 C 38.472656 9 38.417969 9 38.363281 9 C 38.324219 9 38.285156 9 38.242188 9 C 38.136719 9 38.027344 9 37.917969 9 C 37.804688 9 37.695312 9 37.582031 9 C 37.367188 9 37.152344 9 36.9375 9 C 36.695312 9 36.453125 9 36.207031 9 C 35.707031 9 35.207031 9 34.703125 9 C 34.714844 9.089844 34.726562 9.183594 34.738281 9.273438 C 34.742188 9.300781 34.742188 9.328125 34.746094 9.351562 C 34.8125 9.898438 35.007812 10.441406 35.4375 10.808594 C 35.933594 11.1875 36.476562 11.269531 37.089844 11.203125 C 37.539062 11.128906 37.90625 10.847656 38.222656 10.535156 C 38.347656 10.417969 38.347656 10.417969 38.425781 10.417969 C 38.464844 10.445312 38.464844 10.445312 38.503906 10.488281 C 38.589844 10.585938 38.683594 10.671875 38.785156 10.753906 C 38.679688 11.046875 38.46875 11.28125 38.257812 11.5 C 38.242188 11.519531 38.222656 11.535156 38.207031 11.554688 C 37.792969 12 37.171875 12.246094 36.574219 12.320312 C 36.554688 12.320312 36.535156 12.324219 36.515625 12.328125 C 35.640625 12.425781 34.773438 12.210938 34.074219 11.671875 C 33.421875 11.125 33.078125 10.363281 32.976562 9.527344 C 32.972656 9.496094 32.972656 9.496094 32.96875 9.460938 C 32.871094 8.5 33.074219 7.515625 33.675781 6.746094 C 33.707031 6.710938 33.738281 6.675781 33.769531 6.640625 C 33.78125 6.621094 33.796875 6.605469 33.8125 6.585938 C 34.316406 5.988281 35.136719 5.640625 35.902344 5.566406 C 36.699219 5.511719 37.429688 5.699219 38.035156 6.242188 Z M 35.226562 6.652344 C 34.949219 7.007812 34.820312 7.386719 34.746094 7.824219 C 34.742188 7.851562 34.738281 7.875 34.734375 7.898438 C 34.730469 7.921875 34.726562 7.949219 34.722656 7.972656 C 34.71875 7.992188 34.714844 8.015625 34.710938 8.035156 C 34.703125 8.097656 34.703125 8.15625 34.703125 8.222656 C 34.703125 8.242188 34.703125 8.261719 34.703125 8.28125 C 34.703125 8.292969 34.703125 8.308594 34.703125 8.324219 C 34.972656 8.328125 35.242188 8.328125 35.507812 8.328125 C 35.632812 8.328125 35.757812 8.328125 35.882812 8.332031 C 36.003906 8.332031 36.125 8.332031 36.246094 8.332031 C 36.289062 8.332031 36.335938 8.332031 36.382812 8.332031 C 36.445312 8.332031 36.511719 8.332031 36.574219 8.332031 C 36.59375 8.332031 36.613281 8.332031 36.632812 8.332031 C 36.800781 8.332031 36.964844 8.304688 37.085938 8.183594 C 37.273438 7.9375 37.277344 7.609375 37.246094 7.3125 C 37.195312 6.96875 37.015625 6.636719 36.730469 6.425781 C 36.226562 6.113281 35.617188 6.195312 35.226562 6.652344 Z M 35.226562 6.652344 "/> +<path style=" stroke:none;fill-rule:nonzero;fill:rgb(10.196079%,11.372549%,14.117648%);fill-opacity:1;" d="M 112.726562 6.085938 C 112.90625 6.242188 113.058594 6.414062 113.183594 6.617188 C 113.199219 6.636719 113.210938 6.65625 113.226562 6.679688 C 113.628906 7.320312 113.699219 8.167969 113.566406 8.902344 C 113.523438 8.945312 113.449219 8.929688 113.386719 8.929688 C 113.371094 8.929688 113.355469 8.929688 113.339844 8.929688 C 113.28125 8.929688 113.226562 8.929688 113.171875 8.929688 C 113.132812 8.929688 113.089844 8.933594 113.050781 8.933594 C 112.945312 8.933594 112.835938 8.933594 112.726562 8.933594 C 112.613281 8.933594 112.5 8.933594 112.386719 8.933594 C 112.175781 8.9375 111.960938 8.9375 111.746094 8.9375 C 111.503906 8.941406 111.257812 8.941406 111.015625 8.941406 C 110.515625 8.945312 110.011719 8.949219 109.511719 8.949219 C 109.519531 9.023438 109.527344 9.097656 109.535156 9.171875 C 109.535156 9.191406 109.539062 9.210938 109.539062 9.230469 C 109.554688 9.378906 109.578125 9.523438 109.613281 9.667969 C 109.621094 9.6875 109.625 9.710938 109.628906 9.730469 C 109.703125 10.011719 109.808594 10.261719 109.992188 10.488281 C 110.003906 10.507812 110.019531 10.527344 110.035156 10.542969 C 110.320312 10.898438 110.765625 11.117188 111.214844 11.164062 C 111.839844 11.203125 112.339844 11.078125 112.820312 10.671875 C 112.9375 10.570312 113.050781 10.457031 113.160156 10.347656 C 113.230469 10.378906 113.28125 10.414062 113.339844 10.46875 C 113.355469 10.480469 113.367188 10.496094 113.382812 10.507812 C 113.398438 10.523438 113.414062 10.539062 113.429688 10.554688 C 113.445312 10.566406 113.460938 10.582031 113.476562 10.597656 C 113.515625 10.632812 113.554688 10.671875 113.59375 10.707031 C 113.324219 11.316406 112.769531 11.816406 112.15625 12.066406 C 112.054688 12.105469 111.953125 12.136719 111.847656 12.171875 C 111.832031 12.175781 111.816406 12.179688 111.800781 12.183594 C 111.507812 12.265625 111.214844 12.28125 110.914062 12.28125 C 110.898438 12.28125 110.878906 12.28125 110.859375 12.28125 C 110.554688 12.277344 110.261719 12.257812 109.96875 12.175781 C 109.941406 12.167969 109.941406 12.167969 109.914062 12.160156 C 109.203125 11.953125 108.628906 11.503906 108.238281 10.875 C 108.230469 10.859375 108.222656 10.847656 108.210938 10.832031 C 107.699219 9.980469 107.648438 8.855469 107.878906 7.90625 C 108.074219 7.136719 108.570312 6.417969 109.253906 6 C 110.304688 5.378906 111.75 5.261719 112.726562 6.085938 Z M 110.105469 6.496094 C 109.710938 6.9375 109.507812 7.546875 109.511719 8.136719 C 109.511719 8.160156 109.511719 8.179688 109.511719 8.203125 C 109.511719 8.21875 109.511719 8.234375 109.511719 8.253906 C 109.78125 8.253906 110.050781 8.253906 110.316406 8.257812 C 110.441406 8.257812 110.566406 8.257812 110.691406 8.257812 C 110.8125 8.257812 110.933594 8.257812 111.054688 8.257812 C 111.097656 8.257812 111.144531 8.261719 111.191406 8.261719 C 111.253906 8.261719 111.320312 8.261719 111.382812 8.261719 C 111.402344 8.261719 111.421875 8.261719 111.441406 8.261719 C 111.59375 8.261719 111.746094 8.234375 111.871094 8.140625 C 112.082031 7.886719 112.078125 7.605469 112.054688 7.289062 C 112.011719 6.949219 111.867188 6.6875 111.625 6.449219 C 111.609375 6.433594 111.59375 6.417969 111.582031 6.40625 C 111.164062 6.015625 110.496094 6.121094 110.105469 6.496094 Z M 110.105469 6.496094 "/> +<path style=" stroke:none;fill-rule:nonzero;fill:rgb(10.196079%,11.372549%,14.117648%);fill-opacity:1;" d="M 119.207031 6.039062 C 119.210938 6.308594 119.203125 6.578125 119.1875 6.847656 C 119.183594 6.910156 119.183594 6.972656 119.179688 7.035156 C 119.175781 7.078125 119.175781 7.117188 119.171875 7.160156 C 119.171875 7.175781 119.171875 7.195312 119.171875 7.214844 C 119.164062 7.308594 119.152344 7.390625 119.136719 7.484375 C 118.835938 7.484375 118.535156 7.484375 118.222656 7.484375 C 118.207031 7.40625 118.191406 7.328125 118.171875 7.246094 C 118.15625 7.171875 118.140625 7.097656 118.121094 7.023438 C 118.109375 6.96875 118.101562 6.917969 118.089844 6.867188 C 118.070312 6.792969 118.054688 6.714844 118.039062 6.640625 C 118.035156 6.617188 118.027344 6.59375 118.023438 6.570312 C 118.019531 6.550781 118.015625 6.527344 118.007812 6.503906 C 118.003906 6.484375 118 6.464844 117.996094 6.445312 C 117.984375 6.398438 117.984375 6.398438 117.960938 6.351562 C 117.902344 6.332031 117.847656 6.316406 117.789062 6.300781 C 117.765625 6.296875 117.765625 6.296875 117.738281 6.289062 C 117.339844 6.1875 116.835938 6.167969 116.464844 6.375 C 116.296875 6.480469 116.203125 6.609375 116.128906 6.789062 C 116.082031 7.042969 116.105469 7.261719 116.234375 7.484375 C 116.496094 7.828125 117.082031 7.953125 117.46875 8.070312 C 118.183594 8.289062 118.960938 8.597656 119.359375 9.277344 C 119.578125 9.714844 119.621094 10.257812 119.496094 10.734375 C 119.316406 11.277344 118.957031 11.703125 118.449219 11.964844 C 117.460938 12.445312 116.246094 12.394531 115.222656 12.054688 C 115.007812 11.972656 114.804688 11.867188 114.601562 11.765625 C 114.570312 11.234375 114.574219 10.707031 114.574219 10.175781 C 114.886719 10.175781 115.195312 10.175781 115.511719 10.175781 C 115.539062 10.304688 115.539062 10.304688 115.5625 10.433594 C 115.582031 10.515625 115.597656 10.597656 115.613281 10.679688 C 115.625 10.734375 115.636719 10.792969 115.648438 10.847656 C 115.664062 10.929688 115.679688 11.011719 115.695312 11.09375 C 115.703125 11.121094 115.707031 11.144531 115.710938 11.171875 C 115.71875 11.195312 115.722656 11.21875 115.726562 11.242188 C 115.730469 11.261719 115.734375 11.285156 115.738281 11.304688 C 115.75 11.355469 115.75 11.355469 115.777344 11.40625 C 116.324219 11.617188 117.03125 11.667969 117.578125 11.4375 C 117.800781 11.332031 117.949219 11.207031 118.039062 10.972656 C 118.089844 10.761719 118.082031 10.527344 117.992188 10.328125 C 117.910156 10.191406 117.820312 10.105469 117.6875 10.023438 C 117.664062 10.003906 117.636719 9.988281 117.609375 9.972656 C 117.265625 9.769531 116.875 9.65625 116.496094 9.527344 C 116.066406 9.386719 115.683594 9.222656 115.320312 8.949219 C 115.296875 8.933594 115.273438 8.917969 115.25 8.898438 C 115.226562 8.875 115.226562 8.875 115.199219 8.855469 C 115.199219 8.839844 115.199219 8.824219 115.199219 8.804688 C 115.1875 8.800781 115.171875 8.796875 115.160156 8.789062 C 114.933594 8.65625 114.792969 8.285156 114.726562 8.046875 C 114.605469 7.507812 114.6875 6.964844 114.984375 6.496094 C 115.347656 5.957031 115.902344 5.671875 116.523438 5.542969 C 117.460938 5.367188 118.386719 5.574219 119.207031 6.039062 Z M 119.207031 6.039062 "/> +<path style=" stroke:none;fill-rule:nonzero;fill:rgb(10.196079%,11.372549%,14.117648%);fill-opacity:1;" d="M 67.945312 6.109375 C 67.96875 6.136719 67.96875 6.136719 67.96875 6.1875 C 67.96875 6.210938 67.96875 6.234375 67.964844 6.257812 C 67.964844 6.285156 67.964844 6.3125 67.964844 6.339844 C 67.960938 6.367188 67.960938 6.398438 67.960938 6.425781 C 67.960938 6.457031 67.957031 6.484375 67.957031 6.515625 C 67.941406 6.863281 67.917969 7.207031 67.894531 7.554688 C 67.59375 7.554688 67.292969 7.554688 66.984375 7.554688 C 66.921875 7.3125 66.863281 7.070312 66.808594 6.828125 C 66.804688 6.796875 66.796875 6.769531 66.789062 6.742188 C 66.785156 6.714844 66.777344 6.6875 66.773438 6.660156 C 66.765625 6.632812 66.761719 6.609375 66.753906 6.585938 C 66.742188 6.519531 66.742188 6.519531 66.742188 6.425781 C 66.707031 6.414062 66.667969 6.402344 66.628906 6.390625 C 66.605469 6.386719 66.585938 6.378906 66.5625 6.371094 C 66.355469 6.320312 66.15625 6.296875 65.941406 6.296875 C 65.917969 6.296875 65.894531 6.296875 65.871094 6.296875 C 65.5625 6.300781 65.296875 6.351562 65.0625 6.566406 C 64.894531 6.742188 64.859375 6.90625 64.863281 7.148438 C 64.867188 7.285156 64.890625 7.390625 64.96875 7.507812 C 64.976562 7.519531 64.984375 7.535156 64.996094 7.550781 C 65.261719 7.921875 65.914062 8.0625 66.328125 8.179688 C 67.054688 8.390625 67.703125 8.726562 68.089844 9.40625 C 68.214844 9.648438 68.289062 9.90625 68.304688 10.175781 C 68.304688 10.199219 68.304688 10.21875 68.308594 10.242188 C 68.324219 10.753906 68.15625 11.1875 67.824219 11.574219 C 67.808594 11.589844 67.796875 11.605469 67.78125 11.621094 C 67.28125 12.164062 66.515625 12.347656 65.808594 12.390625 C 65.144531 12.414062 64.535156 12.34375 63.910156 12.117188 C 63.886719 12.109375 63.886719 12.109375 63.859375 12.097656 C 63.675781 12.03125 63.507812 11.929688 63.335938 11.835938 C 63.335938 11.632812 63.335938 11.429688 63.335938 11.226562 C 63.335938 11.132812 63.335938 11.039062 63.335938 10.945312 C 63.332031 10.851562 63.332031 10.761719 63.332031 10.671875 C 63.332031 10.636719 63.332031 10.601562 63.332031 10.566406 C 63.332031 10.515625 63.332031 10.46875 63.332031 10.421875 C 63.332031 10.390625 63.332031 10.363281 63.332031 10.335938 C 63.335938 10.273438 63.335938 10.273438 63.359375 10.25 C 63.421875 10.246094 63.484375 10.246094 63.550781 10.246094 C 63.570312 10.246094 63.585938 10.246094 63.605469 10.246094 C 63.648438 10.246094 63.6875 10.246094 63.726562 10.246094 C 63.789062 10.246094 63.851562 10.246094 63.914062 10.246094 C 63.953125 10.246094 63.992188 10.246094 64.03125 10.246094 C 64.050781 10.246094 64.066406 10.246094 64.085938 10.246094 C 64.21875 10.246094 64.21875 10.246094 64.273438 10.273438 C 64.285156 10.316406 64.285156 10.316406 64.296875 10.375 C 64.300781 10.394531 64.304688 10.414062 64.308594 10.4375 C 64.316406 10.460938 64.320312 10.484375 64.324219 10.507812 C 64.328125 10.53125 64.332031 10.554688 64.339844 10.578125 C 64.347656 10.628906 64.359375 10.679688 64.367188 10.730469 C 64.382812 10.808594 64.398438 10.882812 64.414062 10.960938 C 64.421875 11.007812 64.433594 11.058594 64.441406 11.105469 C 64.445312 11.128906 64.453125 11.152344 64.457031 11.175781 C 64.476562 11.285156 64.492188 11.386719 64.488281 11.5 C 64.53125 11.511719 64.53125 11.511719 64.578125 11.519531 C 64.691406 11.546875 64.804688 11.574219 64.921875 11.605469 C 65.117188 11.648438 65.308594 11.648438 65.507812 11.652344 C 65.539062 11.652344 65.570312 11.652344 65.601562 11.652344 C 65.964844 11.652344 66.320312 11.59375 66.605469 11.359375 C 66.761719 11.199219 66.820312 11.003906 66.828125 10.789062 C 66.820312 10.566406 66.761719 10.382812 66.601562 10.226562 C 66.214844 9.933594 65.765625 9.789062 65.3125 9.640625 C 64.621094 9.414062 63.949219 9.125 63.59375 8.445312 C 63.378906 8 63.375 7.464844 63.53125 6.996094 C 63.761719 6.410156 64.183594 6.027344 64.753906 5.773438 C 65.792969 5.351562 66.988281 5.59375 67.945312 6.109375 Z M 67.945312 6.109375 "/> +<path style=" stroke:none;fill-rule:nonzero;fill:rgb(10.196079%,11.372549%,14.117648%);fill-opacity:1;" d="M 105.941406 5.769531 C 105.960938 5.777344 105.976562 5.785156 105.996094 5.792969 C 106.1875 5.867188 106.351562 5.964844 106.535156 6.0625 C 106.511719 6.539062 106.488281 7.015625 106.464844 7.507812 C 106.164062 7.507812 105.863281 7.507812 105.550781 7.507812 C 105.445312 7.101562 105.445312 7.101562 105.410156 6.941406 C 105.40625 6.925781 105.402344 6.910156 105.398438 6.890625 C 105.386719 6.839844 105.375 6.789062 105.367188 6.738281 C 105.359375 6.703125 105.351562 6.667969 105.34375 6.632812 C 105.324219 6.546875 105.304688 6.460938 105.289062 6.375 C 105.234375 6.359375 105.183594 6.34375 105.128906 6.328125 C 105.097656 6.320312 105.070312 6.3125 105.039062 6.304688 C 104.859375 6.253906 104.6875 6.25 104.503906 6.25 C 104.464844 6.25 104.464844 6.25 104.421875 6.25 C 104.136719 6.246094 103.90625 6.3125 103.664062 6.464844 C 103.507812 6.621094 103.417969 6.816406 103.414062 7.042969 C 103.425781 7.25 103.492188 7.433594 103.632812 7.585938 C 103.976562 7.886719 104.484375 8.003906 104.910156 8.132812 C 105.628906 8.351562 106.285156 8.667969 106.667969 9.355469 C 106.878906 9.800781 106.9375 10.371094 106.785156 10.84375 C 106.554688 11.417969 106.144531 11.8125 105.585938 12.070312 C 104.601562 12.488281 103.335938 12.402344 102.359375 12.007812 C 102.203125 11.9375 102.046875 11.859375 101.902344 11.765625 C 101.894531 11.699219 101.894531 11.699219 101.894531 11.617188 C 101.894531 11.585938 101.894531 11.554688 101.890625 11.523438 C 101.890625 11.488281 101.890625 11.453125 101.890625 11.417969 C 101.890625 11.382812 101.890625 11.347656 101.890625 11.3125 C 101.890625 11.222656 101.886719 11.128906 101.886719 11.039062 C 101.886719 10.925781 101.886719 10.816406 101.882812 10.707031 C 101.882812 10.539062 101.882812 10.371094 101.878906 10.203125 C 102.1875 10.203125 102.496094 10.203125 102.816406 10.203125 C 102.871094 10.363281 102.871094 10.363281 102.886719 10.449219 C 102.890625 10.46875 102.894531 10.488281 102.898438 10.507812 C 102.902344 10.527344 102.90625 10.546875 102.910156 10.566406 C 102.914062 10.585938 102.917969 10.609375 102.921875 10.628906 C 102.933594 10.671875 102.941406 10.71875 102.949219 10.761719 C 102.964844 10.828125 102.976562 10.894531 102.988281 10.960938 C 103 11.003906 103.007812 11.046875 103.015625 11.089844 C 103.019531 11.109375 103.023438 11.132812 103.027344 11.152344 C 103.046875 11.246094 103.0625 11.332031 103.054688 11.429688 C 103.699219 11.59375 104.421875 11.726562 105.03125 11.386719 C 105.21875 11.265625 105.316406 11.125 105.375 10.914062 C 105.402344 10.691406 105.378906 10.496094 105.273438 10.292969 C 104.921875 9.867188 104.363281 9.730469 103.859375 9.5625 C 103.1875 9.339844 102.511719 9.058594 102.167969 8.398438 C 101.949219 7.929688 101.9375 7.414062 102.097656 6.929688 C 102.101562 6.90625 102.109375 6.886719 102.117188 6.863281 C 102.269531 6.417969 102.628906 6.066406 103.03125 5.847656 C 103.054688 5.832031 103.078125 5.820312 103.101562 5.808594 C 103.382812 5.65625 103.699219 5.574219 104.015625 5.535156 C 104.035156 5.53125 104.054688 5.527344 104.074219 5.527344 C 104.714844 5.449219 105.34375 5.535156 105.941406 5.769531 Z M 105.941406 5.769531 "/> +<path style=" stroke:none;fill-rule:nonzero;fill:rgb(10.196079%,11.372549%,14.117648%);fill-opacity:1;" d="M 59.953125 3.921875 C 60.316406 3.921875 60.679688 3.921875 61.054688 3.921875 C 61.058594 4.292969 61.054688 4.667969 61.046875 5.039062 C 61.042969 5.066406 61.042969 5.066406 61.042969 5.09375 C 61.042969 5.144531 61.042969 5.195312 61.039062 5.246094 C 61.039062 5.277344 61.039062 5.304688 61.035156 5.335938 C 61.03125 5.472656 61.019531 5.613281 61.007812 5.75 C 61.53125 5.75 62.054688 5.75 62.59375 5.75 C 62.59375 6.035156 62.59375 6.320312 62.59375 6.617188 C 62.0625 6.617188 61.53125 6.617188 60.984375 6.617188 C 60.984375 7.128906 60.988281 7.644531 60.988281 8.160156 C 60.992188 8.398438 60.992188 8.636719 60.992188 8.875 C 60.992188 9.082031 60.992188 9.292969 60.996094 9.5 C 60.996094 9.609375 60.996094 9.71875 60.996094 9.832031 C 60.996094 9.933594 60.996094 10.039062 60.996094 10.140625 C 60.996094 10.179688 60.996094 10.21875 60.996094 10.253906 C 60.992188 10.765625 60.992188 10.765625 61.199219 11.210938 C 61.34375 11.347656 61.507812 11.40625 61.703125 11.40625 C 61.941406 11.371094 62.144531 11.289062 62.355469 11.175781 C 62.457031 11.125 62.457031 11.125 62.519531 11.117188 C 62.558594 11.140625 62.558594 11.140625 62.597656 11.183594 C 62.613281 11.195312 62.625 11.210938 62.640625 11.226562 C 62.652344 11.238281 62.667969 11.253906 62.683594 11.269531 C 62.695312 11.285156 62.710938 11.300781 62.726562 11.316406 C 62.761719 11.351562 62.796875 11.390625 62.832031 11.429688 C 62.785156 11.5625 62.707031 11.65625 62.617188 11.765625 C 62.605469 11.78125 62.59375 11.792969 62.578125 11.808594 C 62.375 12.03125 62.085938 12.183594 61.800781 12.269531 C 61.777344 12.277344 61.75 12.285156 61.726562 12.292969 C 61.511719 12.347656 61.296875 12.351562 61.078125 12.351562 C 61.046875 12.351562 61.019531 12.351562 60.988281 12.351562 C 60.523438 12.351562 60.085938 12.210938 59.730469 11.898438 C 59.542969 11.699219 59.425781 11.40625 59.375 11.140625 C 59.371094 11.117188 59.367188 11.097656 59.363281 11.074219 C 59.351562 10.988281 59.347656 10.902344 59.347656 10.8125 C 59.347656 10.792969 59.347656 10.777344 59.347656 10.757812 C 59.347656 10.695312 59.347656 10.636719 59.347656 10.578125 C 59.347656 10.535156 59.347656 10.492188 59.347656 10.449219 C 59.347656 10.332031 59.347656 10.214844 59.347656 10.097656 C 59.351562 9.972656 59.351562 9.851562 59.351562 9.730469 C 59.351562 9.496094 59.351562 9.265625 59.351562 9.035156 C 59.351562 8.769531 59.351562 8.507812 59.351562 8.242188 C 59.351562 7.703125 59.351562 7.160156 59.351562 6.617188 C 59.035156 6.617188 58.71875 6.617188 58.390625 6.617188 C 58.390625 6.371094 58.390625 6.125 58.390625 5.871094 C 58.914062 5.800781 58.914062 5.800781 59.449219 5.726562 C 59.480469 5.601562 59.515625 5.476562 59.550781 5.351562 C 59.574219 5.269531 59.59375 5.191406 59.617188 5.113281 C 59.652344 4.988281 59.6875 4.863281 59.722656 4.738281 C 59.75 4.640625 59.777344 4.539062 59.804688 4.4375 C 59.816406 4.398438 59.824219 4.359375 59.835938 4.324219 C 59.851562 4.269531 59.867188 4.214844 59.882812 4.160156 C 59.886719 4.144531 59.890625 4.128906 59.894531 4.113281 C 59.914062 4.046875 59.929688 3.984375 59.953125 3.921875 Z M 59.953125 3.921875 "/> +<path style=" stroke:none;fill-rule:nonzero;fill:rgb(10.196079%,11.372549%,14.117648%);fill-opacity:1;" d="M 93.351562 5.554688 C 93.40625 5.59375 93.429688 5.617188 93.445312 5.679688 C 93.449219 5.75 93.445312 5.8125 93.441406 5.878906 C 93.441406 5.90625 93.441406 5.929688 93.441406 5.957031 C 93.4375 6.015625 93.4375 6.070312 93.433594 6.128906 C 93.429688 6.273438 93.425781 6.417969 93.421875 6.5625 C 93.417969 6.617188 93.417969 6.671875 93.417969 6.726562 C 93.402344 7.175781 93.40625 7.625 93.40625 8.074219 C 93.40625 8.195312 93.40625 8.3125 93.40625 8.429688 C 93.40625 8.644531 93.40625 8.859375 93.40625 9.074219 C 93.40625 9.320312 93.40625 9.570312 93.40625 9.816406 C 93.40625 10.320312 93.40625 10.828125 93.40625 11.332031 C 93.425781 11.335938 93.449219 11.339844 93.46875 11.34375 C 93.542969 11.355469 93.613281 11.371094 93.6875 11.382812 C 93.734375 11.390625 93.785156 11.398438 93.832031 11.40625 C 93.859375 11.414062 93.890625 11.417969 93.921875 11.425781 C 93.949219 11.429688 93.976562 11.433594 94.003906 11.4375 C 94.070312 11.449219 94.136719 11.464844 94.199219 11.476562 C 94.199219 11.675781 94.199219 11.875 94.199219 12.078125 C 93.105469 12.078125 92.015625 12.078125 90.886719 12.078125 C 90.886719 11.878906 90.886719 11.679688 90.886719 11.476562 C 90.988281 11.457031 91.085938 11.433594 91.1875 11.414062 C 91.222656 11.40625 91.253906 11.402344 91.289062 11.394531 C 91.339844 11.382812 91.386719 11.375 91.4375 11.363281 C 91.480469 11.355469 91.480469 11.355469 91.527344 11.34375 C 91.601562 11.332031 91.675781 11.332031 91.753906 11.332031 C 91.753906 10.894531 91.753906 10.457031 91.753906 10.023438 C 91.753906 9.820312 91.753906 9.617188 91.753906 9.414062 C 91.753906 9.234375 91.753906 9.058594 91.753906 8.882812 C 91.753906 8.789062 91.753906 8.695312 91.753906 8.601562 C 91.753906 8.066406 91.746094 7.535156 91.726562 7 C 91.683594 6.996094 91.683594 6.996094 91.636719 6.988281 C 91.539062 6.976562 91.4375 6.964844 91.339844 6.953125 C 91.296875 6.945312 91.25 6.941406 91.207031 6.933594 C 91.144531 6.925781 91.082031 6.917969 91.019531 6.910156 C 90.988281 6.90625 90.988281 6.90625 90.957031 6.902344 C 90.820312 6.882812 90.820312 6.882812 90.792969 6.855469 C 90.789062 6.816406 90.789062 6.777344 90.789062 6.738281 C 90.789062 6.714844 90.789062 6.691406 90.789062 6.667969 C 90.789062 6.640625 90.789062 6.617188 90.789062 6.589844 C 90.789062 6.566406 90.789062 6.539062 90.789062 6.515625 C 90.792969 6.453125 90.792969 6.390625 90.792969 6.328125 C 90.96875 6.253906 91.148438 6.1875 91.328125 6.125 C 91.355469 6.117188 91.382812 6.105469 91.414062 6.097656 C 91.503906 6.066406 91.59375 6.03125 91.683594 6 C 91.808594 5.957031 91.929688 5.914062 92.054688 5.871094 C 92.070312 5.867188 92.085938 5.859375 92.101562 5.855469 C 92.285156 5.792969 92.46875 5.726562 92.652344 5.660156 C 92.667969 5.652344 92.6875 5.644531 92.703125 5.640625 C 92.78125 5.609375 92.859375 5.582031 92.9375 5.554688 C 92.976562 5.539062 92.976562 5.539062 93.019531 5.523438 C 93.050781 5.511719 93.050781 5.511719 93.082031 5.5 C 93.1875 5.476562 93.261719 5.503906 93.351562 5.554688 Z M 93.351562 5.554688 "/> +<path style=" stroke:none;fill-rule:nonzero;fill:rgb(10.196079%,11.372549%,14.117648%);fill-opacity:1;" d="M 42.214844 5.652344 C 42.214844 7.542969 42.214844 9.433594 42.214844 11.378906 C 42.535156 11.441406 42.535156 11.441406 42.863281 11.5 C 42.917969 11.515625 42.976562 11.53125 43.03125 11.550781 C 43.03125 11.738281 43.03125 11.929688 43.03125 12.125 C 41.929688 12.125 40.832031 12.125 39.695312 12.125 C 39.695312 11.9375 39.695312 11.746094 39.695312 11.550781 C 39.875 11.5 40.054688 11.460938 40.234375 11.425781 C 40.265625 11.417969 40.265625 11.417969 40.296875 11.410156 C 40.316406 11.40625 40.335938 11.402344 40.355469 11.398438 C 40.375 11.394531 40.394531 11.390625 40.410156 11.386719 C 40.46875 11.378906 40.523438 11.378906 40.585938 11.378906 C 40.582031 10.886719 40.578125 10.390625 40.574219 9.898438 C 40.574219 9.667969 40.574219 9.4375 40.570312 9.207031 C 40.570312 9.007812 40.570312 8.808594 40.570312 8.605469 C 40.566406 8.5 40.566406 8.394531 40.566406 8.289062 C 40.566406 8.191406 40.566406 8.089844 40.566406 7.988281 C 40.566406 7.953125 40.566406 7.917969 40.566406 7.878906 C 40.5625 7.683594 40.558594 7.488281 40.550781 7.292969 C 40.546875 7.273438 40.546875 7.253906 40.546875 7.234375 C 40.542969 7.140625 40.542969 7.140625 40.511719 7.050781 C 40.445312 7.035156 40.378906 7.027344 40.308594 7.019531 C 40.289062 7.015625 40.269531 7.011719 40.25 7.011719 C 40.183594 7.003906 40.121094 6.996094 40.054688 6.988281 C 40.011719 6.980469 39.96875 6.976562 39.921875 6.96875 C 39.816406 6.957031 39.707031 6.941406 39.601562 6.929688 C 39.601562 6.746094 39.601562 6.5625 39.601562 6.375 C 39.964844 6.242188 40.328125 6.109375 40.695312 5.980469 C 40.777344 5.953125 40.859375 5.921875 40.945312 5.894531 C 41 5.875 41.054688 5.855469 41.109375 5.835938 C 41.246094 5.789062 41.386719 5.738281 41.523438 5.6875 C 41.550781 5.675781 41.578125 5.667969 41.605469 5.65625 C 41.660156 5.636719 41.710938 5.617188 41.761719 5.597656 C 41.785156 5.589844 41.808594 5.578125 41.835938 5.570312 C 41.863281 5.558594 41.863281 5.558594 41.894531 5.546875 C 42.027344 5.515625 42.09375 5.585938 42.214844 5.652344 Z M 42.214844 5.652344 "/> +<path style=" stroke:none;fill-rule:nonzero;fill:rgb(98.823529%,73.725492%,19.607843%);fill-opacity:1;" d="M 8.640625 9.261719 C 8.972656 9.519531 9.191406 9.859375 9.277344 10.273438 C 9.328125 10.675781 9.265625 11.089844 9.019531 11.421875 C 8.734375 11.769531 8.371094 12.007812 7.917969 12.058594 C 7.476562 12.09375 7.078125 11.957031 6.742188 11.667969 C 6.71875 11.648438 6.71875 11.648438 6.695312 11.628906 C 6.421875 11.378906 6.261719 10.992188 6.234375 10.628906 C 6.226562 10.179688 6.355469 9.789062 6.664062 9.457031 C 7.191406 8.921875 8.019531 8.84375 8.640625 9.261719 Z M 8.640625 9.261719 "/> +<path style=" stroke:none;fill-rule:nonzero;fill:rgb(98.823529%,73.725492%,19.607843%);fill-opacity:1;" d="M 2.855469 4.089844 C 2.941406 4.15625 3.019531 4.230469 3.097656 4.308594 C 3.113281 4.324219 3.128906 4.339844 3.148438 4.359375 C 3.414062 4.640625 3.542969 5.027344 3.539062 5.410156 C 3.519531 5.851562 3.332031 6.21875 3.015625 6.527344 C 2.707031 6.792969 2.304688 6.921875 1.898438 6.898438 C 1.578125 6.871094 1.308594 6.769531 1.054688 6.570312 C 1.03125 6.546875 1.03125 6.546875 1.003906 6.527344 C 0.699219 6.277344 0.527344 5.898438 0.484375 5.511719 C 0.453125 5.121094 0.558594 4.730469 0.804688 4.425781 C 1.003906 4.191406 1.226562 4.03125 1.511719 3.921875 C 1.53125 3.914062 1.550781 3.90625 1.570312 3.898438 C 1.988281 3.757812 2.496094 3.84375 2.855469 4.089844 Z M 2.855469 4.089844 "/> +<path style=" stroke:none;fill-rule:nonzero;fill:rgb(98.823529%,73.725492%,19.607843%);fill-opacity:1;" d="M 2.960938 11.683594 C 3.269531 11.949219 3.492188 12.316406 3.53125 12.726562 C 3.558594 13.152344 3.449219 13.550781 3.171875 13.878906 C 2.875 14.203125 2.519531 14.375 2.082031 14.417969 C 1.671875 14.433594 1.28125 14.28125 0.972656 14.011719 C 0.660156 13.714844 0.488281 13.324219 0.476562 12.890625 C 0.480469 12.53125 0.59375 12.214844 0.816406 11.933594 C 0.828125 11.917969 0.839844 11.902344 0.851562 11.882812 C 1.078125 11.597656 1.433594 11.421875 1.785156 11.363281 C 2.207031 11.316406 2.628906 11.417969 2.960938 11.683594 Z M 2.960938 11.683594 "/> +<path style=" stroke:none;fill-rule:nonzero;fill:rgb(98.823529%,73.725492%,19.607843%);fill-opacity:1;" d="M 14.449219 4.167969 C 14.507812 4.222656 14.5625 4.273438 14.617188 4.332031 C 14.628906 4.34375 14.640625 4.355469 14.65625 4.367188 C 14.914062 4.632812 15.015625 5.023438 15.03125 5.378906 C 15.019531 5.734375 14.902344 6.046875 14.6875 6.328125 C 14.675781 6.34375 14.664062 6.359375 14.652344 6.378906 C 14.410156 6.679688 14.042969 6.851562 13.664062 6.898438 C 13.242188 6.925781 12.84375 6.816406 12.523438 6.535156 C 12.484375 6.5 12.445312 6.460938 12.40625 6.425781 C 12.394531 6.410156 12.378906 6.394531 12.363281 6.378906 C 12.085938 6.089844 11.988281 5.707031 11.992188 5.316406 C 12.003906 4.914062 12.167969 4.53125 12.457031 4.25 C 13.023438 3.75 13.847656 3.714844 14.449219 4.167969 Z M 14.449219 4.167969 "/> +<path style=" stroke:none;fill-rule:nonzero;fill:rgb(10.196079%,11.372549%,14.117648%);fill-opacity:1;" d="M 42.046875 2.648438 C 42.253906 2.816406 42.375 3.03125 42.40625 3.296875 C 42.433594 3.558594 42.351562 3.808594 42.191406 4.019531 C 42.007812 4.214844 41.785156 4.351562 41.507812 4.363281 C 41.46875 4.363281 41.429688 4.363281 41.390625 4.363281 C 41.371094 4.363281 41.351562 4.363281 41.332031 4.363281 C 41.058594 4.355469 40.832031 4.273438 40.625 4.089844 C 40.476562 3.933594 40.359375 3.734375 40.34375 3.511719 C 40.34375 3.496094 40.339844 3.480469 40.339844 3.460938 C 40.328125 3.230469 40.390625 2.992188 40.542969 2.8125 C 40.554688 2.796875 40.570312 2.78125 40.585938 2.765625 C 40.597656 2.75 40.613281 2.734375 40.632812 2.714844 C 41.019531 2.339844 41.621094 2.332031 42.046875 2.648438 Z M 42.046875 2.648438 "/> +<path style=" stroke:none;fill-rule:nonzero;fill:rgb(10.196079%,11.372549%,14.117648%);fill-opacity:1;" d="M 93.210938 2.566406 C 93.453125 2.757812 93.570312 2.96875 93.609375 3.273438 C 93.621094 3.53125 93.550781 3.773438 93.378906 3.972656 C 93.367188 3.988281 93.351562 4.003906 93.335938 4.019531 C 93.316406 4.039062 93.316406 4.039062 93.296875 4.058594 C 93.066406 4.28125 92.78125 4.316406 92.476562 4.3125 C 92.355469 4.308594 92.25 4.285156 92.136719 4.234375 C 92.117188 4.226562 92.101562 4.21875 92.082031 4.210938 C 91.871094 4.105469 91.703125 3.9375 91.605469 3.722656 C 91.515625 3.449219 91.515625 3.171875 91.632812 2.90625 C 91.773438 2.652344 92 2.484375 92.277344 2.402344 C 92.605469 2.324219 92.933594 2.375 93.210938 2.566406 Z M 93.210938 2.566406 "/> +<path style=" stroke:none;fill-rule:nonzero;fill:rgb(98.823529%,73.725492%,19.607843%);fill-opacity:1;" d="M 8.320312 5.960938 C 8.539062 6.117188 8.664062 6.320312 8.726562 6.582031 C 8.769531 6.839844 8.710938 7.089844 8.574219 7.3125 C 8.410156 7.535156 8.207031 7.65625 7.945312 7.722656 C 7.621094 7.753906 7.355469 7.6875 7.105469 7.484375 C 6.921875 7.3125 6.8125 7.078125 6.792969 6.832031 C 6.789062 6.535156 6.871094 6.28125 7.078125 6.0625 C 7.4375 5.738281 7.914062 5.710938 8.320312 5.960938 Z M 8.320312 5.960938 "/> +<path style=" stroke:none;fill-rule:nonzero;fill:rgb(98.431373%,73.725492%,19.607843%);fill-opacity:1;" d="M 14.09375 0.820312 C 14.289062 0.96875 14.421875 1.179688 14.472656 1.417969 C 14.5 1.714844 14.464844 1.980469 14.273438 2.214844 C 14.082031 2.421875 13.890625 2.558594 13.605469 2.582031 C 13.320312 2.585938 13.078125 2.535156 12.863281 2.332031 C 12.660156 2.128906 12.550781 1.910156 12.542969 1.621094 C 12.546875 1.332031 12.644531 1.117188 12.839844 0.910156 C 13.199219 0.574219 13.6875 0.5625 14.09375 0.820312 Z M 14.09375 0.820312 "/> +<path style=" stroke:none;fill-rule:nonzero;fill:rgb(98.823529%,73.725492%,19.607843%);fill-opacity:1;" d="M 2.574219 0.808594 C 2.769531 0.972656 2.925781 1.164062 2.976562 1.417969 C 2.996094 1.71875 2.972656 1.976562 2.777344 2.214844 C 2.585938 2.421875 2.394531 2.558594 2.109375 2.582031 C 1.824219 2.585938 1.582031 2.53125 1.367188 2.332031 C 1.226562 2.195312 1.136719 2.066406 1.078125 1.875 C 1.074219 1.863281 1.070312 1.847656 1.066406 1.832031 C 1.015625 1.570312 1.054688 1.3125 1.195312 1.089844 C 1.515625 0.644531 2.101562 0.484375 2.574219 0.808594 Z M 2.574219 0.808594 "/> +<path style=" stroke:none;fill-rule:nonzero;fill:rgb(98.823529%,73.725492%,19.607843%);fill-opacity:1;" d="M 2.550781 8.316406 C 2.582031 8.34375 2.609375 8.371094 2.640625 8.398438 C 2.65625 8.414062 2.671875 8.429688 2.691406 8.445312 C 2.878906 8.640625 2.960938 8.847656 2.964844 9.121094 C 2.960938 9.398438 2.882812 9.613281 2.6875 9.816406 C 2.5 9.996094 2.257812 10.109375 1.992188 10.105469 C 1.695312 10.085938 1.449219 9.972656 1.246094 9.753906 C 1.074219 9.535156 1.003906 9.277344 1.03125 9 C 1.089844 8.710938 1.214844 8.484375 1.453125 8.3125 C 1.789062 8.105469 2.222656 8.085938 2.550781 8.316406 Z M 2.550781 8.316406 "/> +<path style=" stroke:none;fill-rule:nonzero;fill:rgb(98.823529%,73.725492%,19.607843%);fill-opacity:1;" d="M 14.058594 8.34375 C 14.269531 8.496094 14.425781 8.714844 14.472656 8.972656 C 14.496094 9.300781 14.441406 9.542969 14.238281 9.804688 C 14.148438 9.90625 14.042969 9.972656 13.921875 10.03125 C 13.894531 10.046875 13.894531 10.046875 13.867188 10.058594 C 13.660156 10.144531 13.386719 10.136719 13.175781 10.066406 C 12.929688 9.960938 12.714844 9.769531 12.613281 9.519531 C 12.601562 9.484375 12.585938 9.445312 12.574219 9.40625 C 12.570312 9.390625 12.566406 9.378906 12.5625 9.363281 C 12.511719 9.109375 12.550781 8.855469 12.679688 8.632812 C 12.824219 8.421875 13.003906 8.277344 13.246094 8.203125 C 13.261719 8.199219 13.277344 8.195312 13.292969 8.191406 C 13.570312 8.136719 13.820312 8.195312 14.058594 8.34375 Z M 14.058594 8.34375 "/> +<path style=" stroke:none;fill-rule:nonzero;fill:rgb(98.823529%,73.725492%,19.607843%);fill-opacity:1;" d="M 8.375 13.523438 C 8.605469 13.730469 8.71875 13.984375 8.742188 14.289062 C 8.734375 14.554688 8.621094 14.789062 8.445312 14.984375 C 8.25 15.164062 7.996094 15.25 7.734375 15.253906 C 7.46875 15.242188 7.25 15.136719 7.0625 14.953125 C 6.863281 14.730469 6.789062 14.488281 6.792969 14.195312 C 6.832031 13.894531 6.964844 13.664062 7.199219 13.472656 C 7.582031 13.230469 8.015625 13.25 8.375 13.523438 Z M 8.375 13.523438 "/> +<path style=" stroke:none;fill-rule:nonzero;fill:rgb(98.823529%,73.725492%,19.607843%);fill-opacity:1;" d="M 14.027344 12.066406 C 14.25 12.234375 14.421875 12.449219 14.472656 12.726562 C 14.492188 13.019531 14.464844 13.269531 14.273438 13.5 C 14.089844 13.699219 13.886719 13.84375 13.609375 13.863281 C 13.3125 13.867188 13.058594 13.800781 12.832031 13.589844 C 12.730469 13.480469 12.65625 13.371094 12.601562 13.234375 C 12.589844 13.207031 12.582031 13.183594 12.570312 13.15625 C 12.519531 12.886719 12.539062 12.625 12.679688 12.386719 C 12.984375 11.945312 13.558594 11.765625 14.027344 12.066406 Z M 14.027344 12.066406 "/> +<path style=" stroke:none;fill-rule:nonzero;fill:rgb(98.823529%,73.725492%,19.607843%);fill-opacity:1;" d="M 8.269531 2.203125 C 8.492188 2.371094 8.652344 2.574219 8.703125 2.855469 C 8.738281 3.113281 8.695312 3.371094 8.535156 3.582031 C 8.355469 3.796875 8.136719 3.949219 7.851562 3.976562 C 7.539062 3.988281 7.289062 3.894531 7.054688 3.679688 C 6.851562 3.449219 6.796875 3.222656 6.808594 2.914062 C 6.824219 2.671875 6.929688 2.480469 7.105469 2.308594 C 7.445312 2.039062 7.886719 1.964844 8.269531 2.203125 Z M 8.269531 2.203125 "/> +</g> +</svg> diff --git a/web/app/components/base/icons/assets/public/tracing/weave-icon.svg b/web/app/components/base/icons/assets/public/tracing/weave-icon.svg new file mode 100644 index 0000000000..62e6067ebb --- /dev/null +++ b/web/app/components/base/icons/assets/public/tracing/weave-icon.svg @@ -0,0 +1,33 @@ +<?xml version="1.0" encoding="UTF-8"?> +<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="120px" height="16px" viewBox="0 0 120 16" version="1.1"> +<g id="surface1"> +<path style=" stroke:none;fill-rule:nonzero;fill:rgb(10.196079%,11.372549%,14.117648%);fill-opacity:1;" d="M 20.847656 3.292969 C 20.875 3.292969 20.902344 3.292969 20.933594 3.292969 C 20.949219 3.292969 20.964844 3.292969 20.980469 3.292969 C 21.035156 3.292969 21.089844 3.292969 21.140625 3.292969 C 21.179688 3.292969 21.21875 3.292969 21.253906 3.292969 C 21.359375 3.292969 21.464844 3.292969 21.566406 3.292969 C 21.675781 3.292969 21.78125 3.292969 21.890625 3.292969 C 22.097656 3.292969 22.300781 3.292969 22.507812 3.292969 C 22.738281 3.292969 22.972656 3.292969 23.207031 3.296875 C 23.6875 3.296875 24.167969 3.296875 24.648438 3.296875 C 24.648438 3.519531 24.648438 3.742188 24.648438 3.96875 C 24.113281 4.042969 24.113281 4.042969 23.566406 4.113281 C 23.667969 4.496094 23.769531 4.882812 23.867188 5.265625 C 23.878906 5.308594 23.878906 5.308594 23.890625 5.351562 C 24.128906 6.269531 24.371094 7.183594 24.609375 8.097656 C 24.675781 8.339844 24.738281 8.582031 24.800781 8.824219 C 24.816406 8.878906 24.832031 8.933594 24.84375 8.992188 C 24.867188 9.078125 24.890625 9.167969 24.914062 9.257812 C 24.921875 9.289062 24.933594 9.320312 24.941406 9.355469 C 24.953125 9.398438 24.964844 9.441406 24.976562 9.484375 C 24.984375 9.523438 24.984375 9.523438 24.996094 9.558594 C 25.007812 9.625 25.007812 9.625 25.007812 9.71875 C 25.023438 9.71875 25.039062 9.71875 25.054688 9.71875 C 25.058594 9.707031 25.058594 9.695312 25.0625 9.679688 C 25.097656 9.492188 25.152344 9.3125 25.210938 9.128906 C 25.222656 9.097656 25.234375 9.0625 25.246094 9.027344 C 25.269531 8.953125 25.292969 8.882812 25.316406 8.808594 C 25.355469 8.691406 25.390625 8.574219 25.429688 8.457031 C 25.464844 8.339844 25.503906 8.21875 25.542969 8.097656 C 25.660156 7.738281 25.773438 7.375 25.890625 7.011719 C 25.902344 6.96875 25.917969 6.921875 25.933594 6.875 C 26.226562 5.945312 26.519531 5.019531 26.808594 4.089844 C 26.785156 4.089844 26.765625 4.089844 26.742188 4.085938 C 26.507812 4.074219 26.273438 4.046875 26.042969 4.015625 C 26.007812 4.011719 25.972656 4.007812 25.933594 4.003906 C 25.851562 3.992188 25.765625 3.980469 25.679688 3.96875 C 25.679688 3.746094 25.679688 3.523438 25.679688 3.296875 C 26.175781 3.296875 26.667969 3.296875 27.160156 3.296875 C 27.390625 3.292969 27.621094 3.292969 27.851562 3.292969 C 28.050781 3.292969 28.25 3.292969 28.449219 3.292969 C 28.554688 3.292969 28.660156 3.292969 28.765625 3.292969 C 28.867188 3.292969 28.964844 3.292969 29.066406 3.292969 C 29.101562 3.292969 29.140625 3.292969 29.175781 3.292969 C 29.226562 3.292969 29.273438 3.292969 29.324219 3.292969 C 29.367188 3.292969 29.367188 3.292969 29.410156 3.292969 C 29.472656 3.296875 29.472656 3.296875 29.496094 3.320312 C 29.5 3.367188 29.5 3.417969 29.5 3.464844 C 29.5 3.492188 29.5 3.515625 29.5 3.542969 C 29.496094 3.59375 29.496094 3.59375 29.496094 3.648438 C 29.496094 3.753906 29.496094 3.859375 29.496094 3.96875 C 29.09375 4.015625 28.6875 4.066406 28.273438 4.113281 C 28.679688 5.460938 28.679688 5.460938 29.089844 6.808594 C 29.105469 6.859375 29.121094 6.910156 29.136719 6.960938 C 29.234375 7.292969 29.335938 7.625 29.4375 7.960938 C 29.484375 8.113281 29.53125 8.265625 29.578125 8.417969 C 29.605469 8.507812 29.632812 8.597656 29.660156 8.691406 C 29.878906 9.40625 29.878906 9.40625 29.976562 9.746094 C 30.027344 9.664062 30.046875 9.601562 30.070312 9.507812 C 30.078125 9.484375 30.078125 9.484375 30.085938 9.457031 C 30.101562 9.402344 30.117188 9.34375 30.132812 9.289062 C 30.144531 9.25 30.152344 9.207031 30.164062 9.167969 C 30.1875 9.082031 30.214844 8.992188 30.238281 8.90625 C 30.292969 8.691406 30.351562 8.480469 30.410156 8.269531 C 30.433594 8.191406 30.453125 8.117188 30.472656 8.042969 C 30.621094 7.5 30.769531 6.960938 30.921875 6.421875 C 30.949219 6.324219 30.976562 6.226562 31 6.128906 C 31.066406 5.902344 31.128906 5.675781 31.191406 5.449219 C 31.230469 5.308594 31.269531 5.164062 31.308594 5.023438 C 31.335938 4.925781 31.363281 4.828125 31.390625 4.734375 C 31.402344 4.6875 31.414062 4.640625 31.429688 4.59375 C 31.445312 4.53125 31.464844 4.46875 31.480469 4.40625 C 31.488281 4.386719 31.492188 4.367188 31.496094 4.347656 C 31.515625 4.277344 31.535156 4.207031 31.558594 4.136719 C 31.210938 4.074219 30.855469 4.023438 30.503906 3.96875 C 30.503906 3.746094 30.503906 3.523438 30.503906 3.296875 C 30.878906 3.296875 31.253906 3.296875 31.628906 3.296875 C 31.804688 3.292969 31.976562 3.292969 32.152344 3.292969 C 32.304688 3.292969 32.457031 3.292969 32.605469 3.292969 C 32.6875 3.292969 32.769531 3.292969 32.847656 3.292969 C 32.9375 3.292969 33.027344 3.292969 33.117188 3.292969 C 33.144531 3.292969 33.171875 3.292969 33.199219 3.292969 C 33.222656 3.292969 33.246094 3.292969 33.273438 3.292969 C 33.304688 3.292969 33.304688 3.292969 33.335938 3.292969 C 33.382812 3.296875 33.382812 3.296875 33.40625 3.320312 C 33.410156 3.367188 33.410156 3.414062 33.410156 3.460938 C 33.410156 3.488281 33.410156 3.515625 33.410156 3.542969 C 33.410156 3.574219 33.410156 3.605469 33.410156 3.632812 C 33.410156 3.664062 33.410156 3.695312 33.410156 3.726562 C 33.410156 3.796875 33.410156 3.871094 33.40625 3.945312 C 33.292969 3.964844 33.175781 3.984375 33.0625 4.007812 C 33.023438 4.011719 32.984375 4.019531 32.945312 4.027344 C 32.738281 4.0625 32.535156 4.097656 32.328125 4.113281 C 32.320312 4.144531 32.320312 4.144531 32.3125 4.179688 C 32.238281 4.480469 32.15625 4.78125 32.070312 5.082031 C 32.058594 5.128906 32.042969 5.171875 32.03125 5.21875 C 31.875 5.78125 31.714844 6.347656 31.550781 6.910156 C 31.375 7.535156 31.195312 8.160156 31.019531 8.785156 C 30.992188 8.871094 30.96875 8.957031 30.945312 9.042969 C 30.835938 9.433594 30.722656 9.820312 30.613281 10.210938 C 30.566406 10.378906 30.519531 10.542969 30.472656 10.707031 C 30.445312 10.804688 30.417969 10.902344 30.390625 11 C 30.277344 11.390625 30.167969 11.785156 30.046875 12.175781 C 29.730469 12.175781 29.414062 12.175781 29.089844 12.175781 C 29.03125 12.003906 29.03125 12.003906 28.976562 11.832031 C 28.925781 11.675781 28.878906 11.523438 28.828125 11.367188 C 28.820312 11.347656 28.8125 11.328125 28.808594 11.304688 C 28.632812 10.769531 28.460938 10.230469 28.285156 9.695312 C 28.144531 9.273438 28.007812 8.847656 27.875 8.425781 C 27.695312 7.867188 27.515625 7.308594 27.332031 6.753906 C 27.304688 6.679688 27.28125 6.605469 27.257812 6.53125 C 27.238281 6.476562 27.222656 6.425781 27.207031 6.375 C 27.046875 5.894531 27.046875 5.894531 27.046875 5.796875 C 27.03125 5.796875 27.015625 5.796875 27 5.796875 C 26.996094 5.8125 26.996094 5.828125 26.992188 5.84375 C 26.964844 5.988281 26.925781 6.132812 26.882812 6.273438 C 26.875 6.296875 26.867188 6.316406 26.859375 6.339844 C 26.84375 6.390625 26.828125 6.4375 26.8125 6.488281 C 26.769531 6.625 26.726562 6.761719 26.683594 6.898438 C 26.675781 6.929688 26.664062 6.957031 26.65625 6.988281 C 26.546875 7.328125 26.445312 7.667969 26.339844 8.007812 C 26.316406 8.078125 26.296875 8.144531 26.273438 8.214844 C 26.230469 8.355469 26.1875 8.496094 26.144531 8.636719 C 26.074219 8.863281 26.007812 9.089844 25.9375 9.3125 C 25.933594 9.328125 25.925781 9.347656 25.921875 9.363281 C 25.894531 9.449219 25.871094 9.535156 25.84375 9.617188 C 25.796875 9.769531 25.75 9.921875 25.703125 10.074219 C 25.675781 10.15625 25.652344 10.242188 25.625 10.328125 C 25.613281 10.363281 25.605469 10.394531 25.59375 10.429688 C 25.414062 11.011719 25.234375 11.59375 25.054688 12.175781 C 24.738281 12.175781 24.421875 12.175781 24.097656 12.175781 C 23.816406 11.230469 23.535156 10.285156 23.261719 9.339844 C 23.253906 9.320312 23.25 9.304688 23.246094 9.285156 C 23.195312 9.117188 23.144531 8.949219 23.097656 8.78125 C 22.960938 8.3125 22.824219 7.84375 22.6875 7.375 C 22.664062 7.304688 22.644531 7.234375 22.625 7.164062 C 22.414062 6.449219 22.207031 5.738281 22 5.027344 C 21.976562 4.953125 21.953125 4.878906 21.933594 4.804688 C 21.898438 4.683594 21.859375 4.5625 21.824219 4.441406 C 21.820312 4.421875 21.8125 4.402344 21.808594 4.382812 C 21.796875 4.347656 21.785156 4.3125 21.777344 4.28125 C 21.753906 4.203125 21.742188 4.148438 21.742188 4.066406 C 21.726562 4.066406 21.710938 4.0625 21.691406 4.0625 C 21.382812 4.042969 21.070312 4.003906 20.761719 3.96875 C 20.757812 3.863281 20.757812 3.753906 20.757812 3.648438 C 20.757812 3.617188 20.757812 3.585938 20.757812 3.554688 C 20.757812 3.523438 20.757812 3.496094 20.757812 3.464844 C 20.757812 3.4375 20.757812 3.410156 20.757812 3.382812 C 20.761719 3.296875 20.761719 3.296875 20.847656 3.292969 Z M 20.847656 3.292969 "/> +<path style=" stroke:none;fill-rule:nonzero;fill:rgb(10.196079%,11.372549%,14.117648%);fill-opacity:1;" d="M 82.488281 3.25 C 83.046875 3.246094 83.605469 3.246094 84.167969 3.246094 C 84.425781 3.246094 84.6875 3.246094 84.945312 3.246094 C 85.171875 3.242188 85.398438 3.242188 85.625 3.242188 C 85.746094 3.242188 85.867188 3.242188 85.984375 3.242188 C 88.15625 3.238281 88.15625 3.238281 88.894531 3.898438 C 88.914062 3.914062 88.9375 3.929688 88.957031 3.945312 C 89.191406 4.144531 89.363281 4.402344 89.472656 4.691406 C 89.480469 4.714844 89.492188 4.742188 89.5 4.765625 C 89.65625 5.25 89.601562 5.785156 89.382812 6.234375 C 89.117188 6.753906 88.695312 7.078125 88.152344 7.265625 C 87.984375 7.320312 87.816406 7.367188 87.648438 7.410156 C 87.664062 7.414062 87.679688 7.417969 87.699219 7.421875 C 88.523438 7.605469 89.300781 7.851562 89.78125 8.597656 C 90.0625 9.0625 90.125 9.636719 90.003906 10.164062 C 89.808594 10.804688 89.363281 11.304688 88.78125 11.621094 C 88.324219 11.863281 87.820312 11.988281 87.3125 12.054688 C 87.28125 12.058594 87.253906 12.0625 87.222656 12.066406 C 86.777344 12.121094 86.332031 12.109375 85.882812 12.105469 C 85.765625 12.105469 85.644531 12.105469 85.523438 12.105469 C 85.300781 12.105469 85.074219 12.105469 84.847656 12.105469 C 84.589844 12.105469 84.332031 12.105469 84.074219 12.105469 C 83.546875 12.105469 83.015625 12.101562 82.488281 12.101562 C 82.488281 11.878906 82.488281 11.65625 82.488281 11.429688 C 82.859375 11.390625 83.234375 11.347656 83.617188 11.308594 C 83.617188 8.910156 83.617188 6.511719 83.617188 4.042969 C 83.488281 4.035156 83.363281 4.027344 83.230469 4.019531 C 83.117188 4.007812 83.003906 3.996094 82.890625 3.980469 C 82.863281 3.980469 82.832031 3.976562 82.804688 3.972656 C 82.695312 3.960938 82.59375 3.949219 82.488281 3.921875 C 82.488281 3.699219 82.488281 3.476562 82.488281 3.25 Z M 85.390625 3.96875 C 85.390625 4.242188 85.386719 4.515625 85.382812 4.785156 C 85.382812 4.914062 85.378906 5.039062 85.378906 5.164062 C 85.371094 5.824219 85.367188 6.484375 85.367188 7.144531 C 86.488281 7.183594 86.488281 7.183594 87.457031 6.691406 C 87.796875 6.320312 87.859375 5.832031 87.847656 5.351562 C 87.832031 4.992188 87.71875 4.644531 87.460938 4.378906 C 87 3.96875 86.363281 3.964844 85.78125 3.96875 C 85.742188 3.96875 85.703125 3.96875 85.667969 3.96875 C 85.574219 3.96875 85.484375 3.96875 85.390625 3.96875 Z M 85.390625 7.84375 C 85.390625 9.003906 85.390625 10.160156 85.390625 11.355469 C 86.28125 11.386719 86.28125 11.386719 87.152344 11.21875 C 87.171875 11.214844 87.1875 11.207031 87.207031 11.199219 C 87.578125 11.066406 87.886719 10.824219 88.066406 10.46875 C 88.28125 9.988281 88.289062 9.417969 88.125 8.921875 C 87.960938 8.492188 87.664062 8.234375 87.257812 8.046875 C 86.664062 7.804688 86.023438 7.84375 85.390625 7.84375 Z M 85.390625 7.84375 "/> +<path style=" stroke:none;fill-rule:nonzero;fill:rgb(10.196079%,11.372549%,14.117648%);fill-opacity:1;" d="M 76.167969 3.476562 C 76.367188 3.671875 76.507812 3.917969 76.585938 4.1875 C 76.589844 4.203125 76.59375 4.222656 76.601562 4.242188 C 76.707031 4.675781 76.621094 5.144531 76.414062 5.53125 C 76.34375 5.644531 76.265625 5.746094 76.175781 5.847656 C 76.15625 5.867188 76.136719 5.886719 76.117188 5.910156 C 75.71875 6.332031 75.199219 6.617188 74.6875 6.882812 C 74.707031 6.902344 74.726562 6.921875 74.746094 6.941406 C 74.972656 7.191406 74.972656 7.191406 75.066406 7.296875 C 75.140625 7.382812 75.21875 7.464844 75.300781 7.542969 C 75.351562 7.59375 75.394531 7.640625 75.4375 7.695312 C 75.527344 7.796875 75.621094 7.894531 75.714844 7.992188 C 76.089844 8.394531 76.089844 8.394531 76.253906 8.585938 C 76.351562 8.695312 76.449219 8.800781 76.546875 8.90625 C 76.621094 8.980469 76.691406 9.058594 76.761719 9.136719 C 76.773438 9.152344 76.789062 9.164062 76.800781 9.179688 C 76.824219 9.207031 76.851562 9.234375 76.875 9.261719 C 76.933594 9.324219 76.992188 9.382812 77.0625 9.429688 C 77.070312 9.410156 77.070312 9.410156 77.082031 9.386719 C 77.113281 9.304688 77.152344 9.230469 77.195312 9.15625 C 77.5625 8.476562 77.800781 7.753906 77.976562 7 C 77.953125 7 77.933594 6.996094 77.910156 6.996094 C 77.707031 6.96875 77.5 6.9375 77.296875 6.902344 C 77.273438 6.898438 77.25 6.894531 77.222656 6.890625 C 77.050781 6.859375 77.050781 6.859375 76.96875 6.832031 C 76.960938 6.328125 76.960938 6.328125 77.015625 6.160156 C 77.949219 6.160156 78.886719 6.160156 79.847656 6.160156 C 79.847656 6.367188 79.847656 6.574219 79.847656 6.785156 C 79.53125 6.839844 79.214844 6.894531 78.886719 6.953125 C 78.859375 7.046875 78.832031 7.140625 78.804688 7.234375 C 78.539062 8.09375 78.164062 9.035156 77.601562 9.746094 C 77.5625 9.792969 77.5625 9.792969 77.566406 9.851562 C 77.601562 9.933594 77.648438 9.980469 77.714844 10.039062 C 77.792969 10.113281 77.867188 10.1875 77.9375 10.269531 C 78.027344 10.375 78.125 10.46875 78.222656 10.566406 C 78.308594 10.65625 78.390625 10.742188 78.472656 10.839844 C 78.539062 10.914062 78.601562 10.933594 78.695312 10.949219 C 78.71875 10.953125 78.746094 10.957031 78.769531 10.960938 C 78.796875 10.964844 78.824219 10.96875 78.851562 10.972656 C 78.875 10.980469 78.902344 10.984375 78.933594 10.988281 C 79.019531 11.003906 79.105469 11.019531 79.191406 11.03125 C 79.277344 11.046875 79.363281 11.0625 79.449219 11.078125 C 79.503906 11.085938 79.558594 11.097656 79.613281 11.105469 C 79.648438 11.113281 79.648438 11.113281 79.6875 11.117188 C 79.707031 11.121094 79.730469 11.125 79.75 11.128906 C 79.800781 11.140625 79.800781 11.140625 79.824219 11.164062 C 79.820312 11.421875 79.785156 11.679688 79.753906 11.933594 C 79.691406 11.949219 79.632812 11.964844 79.570312 11.980469 C 79.546875 11.984375 79.546875 11.984375 79.519531 11.992188 C 79.214844 12.066406 78.910156 12.085938 78.597656 12.085938 C 78.539062 12.085938 78.484375 12.085938 78.425781 12.085938 C 77.847656 12.089844 77.332031 11.917969 76.894531 11.523438 C 76.855469 11.484375 76.816406 11.445312 76.777344 11.40625 C 76.71875 11.347656 76.660156 11.296875 76.601562 11.242188 C 76.578125 11.21875 76.578125 11.21875 76.554688 11.195312 C 76.515625 11.160156 76.476562 11.125 76.441406 11.089844 C 76.429688 11.101562 76.417969 11.109375 76.410156 11.117188 C 76.140625 11.351562 75.859375 11.554688 75.542969 11.71875 C 75.511719 11.738281 75.476562 11.757812 75.445312 11.777344 C 75.3125 11.847656 75.179688 11.894531 75.039062 11.9375 C 75.011719 11.945312 75.011719 11.945312 74.984375 11.953125 C 74.632812 12.058594 74.269531 12.089844 73.90625 12.085938 C 73.84375 12.085938 73.785156 12.085938 73.722656 12.089844 C 72.941406 12.089844 72.222656 11.824219 71.652344 11.28125 C 71.203125 10.820312 71.023438 10.246094 71.03125 9.609375 C 71.042969 9.058594 71.230469 8.546875 71.59375 8.132812 C 71.609375 8.113281 71.625 8.09375 71.644531 8.070312 C 71.980469 7.683594 72.398438 7.421875 72.839844 7.171875 C 72.871094 7.152344 72.902344 7.132812 72.9375 7.113281 C 72.960938 7.101562 72.984375 7.085938 73.007812 7.074219 C 72.996094 7.0625 72.988281 7.050781 72.976562 7.042969 C 72.398438 6.425781 72.09375 5.613281 72.113281 4.773438 C 72.128906 4.371094 72.257812 3.988281 72.527344 3.679688 C 72.542969 3.660156 72.558594 3.644531 72.570312 3.625 C 72.917969 3.210938 73.496094 2.996094 74.015625 2.933594 C 74.050781 2.929688 74.050781 2.929688 74.082031 2.925781 C 74.804688 2.847656 75.621094 2.964844 76.167969 3.476562 Z M 73.671875 3.796875 C 73.433594 4.113281 73.414062 4.4375 73.457031 4.820312 C 73.550781 5.460938 73.921875 5.9375 74.328125 6.425781 C 74.398438 6.390625 74.449219 6.355469 74.503906 6.300781 C 74.527344 6.28125 74.527344 6.28125 74.550781 6.257812 C 74.566406 6.242188 74.582031 6.226562 74.597656 6.210938 C 74.613281 6.191406 74.628906 6.175781 74.644531 6.160156 C 74.773438 6.03125 74.890625 5.894531 75 5.75 C 75.019531 5.726562 75.035156 5.699219 75.054688 5.675781 C 75.335938 5.292969 75.5 4.859375 75.457031 4.378906 C 75.40625 4.078125 75.289062 3.820312 75.035156 3.636719 C 74.59375 3.363281 74.03125 3.410156 73.671875 3.796875 Z M 73.046875 7.828125 C 72.664062 8.226562 72.519531 8.789062 72.519531 9.332031 C 72.53125 9.800781 72.71875 10.257812 73.039062 10.601562 C 73.46875 10.996094 73.980469 11.140625 74.550781 11.125 C 74.960938 11.105469 75.339844 11.003906 75.703125 10.8125 C 75.71875 10.804688 75.738281 10.796875 75.753906 10.785156 C 75.84375 10.738281 75.90625 10.699219 75.960938 10.609375 C 75.949219 10.601562 75.941406 10.589844 75.933594 10.582031 C 75.460938 10.074219 75.460938 10.074219 75.25 9.8125 C 75.1875 9.738281 75.125 9.664062 75.0625 9.59375 C 74.972656 9.484375 74.882812 9.375 74.796875 9.265625 C 74.695312 9.132812 74.589844 9.003906 74.480469 8.878906 C 74.390625 8.773438 74.304688 8.667969 74.214844 8.5625 C 74.152344 8.484375 74.085938 8.40625 74.019531 8.328125 C 73.921875 8.214844 73.828125 8.101562 73.734375 7.984375 C 73.726562 7.96875 73.714844 7.957031 73.703125 7.941406 C 73.683594 7.914062 73.660156 7.886719 73.640625 7.859375 C 73.589844 7.792969 73.539062 7.730469 73.488281 7.667969 C 73.460938 7.632812 73.460938 7.632812 73.433594 7.601562 C 73.414062 7.578125 73.414062 7.578125 73.390625 7.554688 C 73.265625 7.554688 73.132812 7.742188 73.046875 7.828125 Z M 73.046875 7.828125 "/> +<path style=" stroke:none;fill-rule:nonzero;fill:rgb(10.196079%,11.372549%,14.117648%);fill-opacity:1;" d="M 49.992188 5.535156 C 50.101562 5.609375 50.101562 5.609375 50.136719 5.679688 C 50.136719 5.746094 50.140625 5.8125 50.140625 5.882812 C 50.140625 5.914062 50.140625 5.914062 50.140625 5.941406 C 50.140625 5.984375 50.140625 6.027344 50.140625 6.070312 C 50.140625 6.136719 50.140625 6.203125 50.140625 6.269531 C 50.140625 6.308594 50.140625 6.351562 50.140625 6.390625 C 50.140625 6.410156 50.140625 6.429688 50.140625 6.453125 C 50.140625 6.589844 50.140625 6.589844 50.113281 6.617188 C 50.074219 6.617188 50.035156 6.621094 49.996094 6.621094 C 49.972656 6.621094 49.949219 6.621094 49.921875 6.621094 C 49.894531 6.621094 49.871094 6.617188 49.84375 6.617188 C 49.816406 6.617188 49.789062 6.617188 49.757812 6.617188 C 49.671875 6.617188 49.585938 6.617188 49.5 6.617188 C 49.441406 6.617188 49.378906 6.617188 49.320312 6.617188 C 49.175781 6.617188 49.03125 6.617188 48.886719 6.617188 C 48.898438 6.640625 48.90625 6.660156 48.917969 6.683594 C 48.929688 6.714844 48.945312 6.746094 48.957031 6.773438 C 48.964844 6.789062 48.96875 6.804688 48.976562 6.820312 C 49.203125 7.339844 49.195312 8 48.988281 8.523438 C 48.75 9.0625 48.355469 9.457031 47.804688 9.671875 C 47.066406 9.941406 46.210938 9.941406 45.457031 9.746094 C 45.277344 10.003906 45.214844 10.273438 45.238281 10.585938 C 45.269531 10.699219 45.316406 10.761719 45.402344 10.835938 C 45.617188 10.945312 45.851562 10.949219 46.089844 10.949219 C 46.113281 10.953125 46.132812 10.953125 46.15625 10.953125 C 46.203125 10.953125 46.25 10.953125 46.292969 10.953125 C 46.367188 10.953125 46.441406 10.953125 46.515625 10.953125 C 46.726562 10.953125 46.9375 10.957031 47.144531 10.957031 C 47.273438 10.957031 47.402344 10.957031 47.53125 10.960938 C 47.582031 10.960938 47.628906 10.960938 47.675781 10.960938 C 48.324219 10.960938 49.039062 11.019531 49.53125 11.492188 C 49.546875 11.511719 49.566406 11.53125 49.585938 11.550781 C 49.601562 11.566406 49.617188 11.582031 49.636719 11.597656 C 49.957031 11.929688 50.0625 12.394531 50.066406 12.84375 C 50.054688 13.351562 49.847656 13.800781 49.511719 14.171875 C 49.496094 14.191406 49.480469 14.207031 49.460938 14.226562 C 48.8125 14.921875 47.769531 15.179688 46.855469 15.210938 C 45.890625 15.234375 44.761719 15.230469 44.015625 14.523438 C 43.738281 14.222656 43.660156 13.886719 43.671875 13.488281 C 43.679688 13.363281 43.699219 13.253906 43.753906 13.136719 C 43.761719 13.117188 43.769531 13.09375 43.78125 13.074219 C 43.996094 12.644531 44.386719 12.410156 44.785156 12.175781 C 44.765625 12.167969 44.746094 12.160156 44.730469 12.152344 C 44.398438 11.996094 44.222656 11.808594 44.089844 11.476562 C 43.988281 11.136719 44.070312 10.757812 44.222656 10.453125 C 44.421875 10.109375 44.695312 9.824219 44.976562 9.550781 C 44.960938 9.542969 44.945312 9.53125 44.925781 9.523438 C 44.757812 9.417969 44.613281 9.304688 44.472656 9.167969 C 44.457031 9.152344 44.441406 9.136719 44.425781 9.121094 C 44.214844 8.902344 44.085938 8.597656 44.015625 8.300781 C 44.011719 8.28125 44.003906 8.257812 44 8.238281 C 43.882812 7.675781 43.964844 7.042969 44.277344 6.558594 C 44.621094 6.070312 45.09375 5.773438 45.671875 5.628906 C 45.6875 5.625 45.703125 5.621094 45.71875 5.617188 C 46.25 5.492188 46.917969 5.496094 47.449219 5.628906 C 47.464844 5.632812 47.480469 5.636719 47.496094 5.640625 C 47.6875 5.691406 47.867188 5.761719 48.046875 5.84375 C 48.0625 5.851562 48.078125 5.859375 48.09375 5.867188 C 48.164062 5.902344 48.226562 5.933594 48.289062 5.980469 C 48.390625 6.066406 48.390625 6.066406 48.515625 6.082031 C 48.582031 6.0625 48.644531 6.035156 48.707031 6.003906 C 48.730469 5.996094 48.753906 5.984375 48.78125 5.976562 C 48.855469 5.941406 48.929688 5.910156 49.003906 5.875 C 49.054688 5.851562 49.101562 5.832031 49.152344 5.808594 C 49.320312 5.738281 49.488281 5.664062 49.652344 5.585938 C 49.679688 5.574219 49.703125 5.566406 49.730469 5.554688 C 49.75 5.542969 49.769531 5.535156 49.789062 5.523438 C 49.867188 5.503906 49.917969 5.515625 49.992188 5.535156 Z M 45.835938 6.507812 C 45.472656 6.984375 45.421875 7.597656 45.492188 8.175781 C 45.550781 8.542969 45.6875 8.890625 45.980469 9.132812 C 46.207031 9.285156 46.46875 9.3125 46.734375 9.277344 C 47.015625 9.21875 47.210938 9.089844 47.375 8.855469 C 47.683594 8.375 47.742188 7.746094 47.640625 7.191406 C 47.5625 6.859375 47.402344 6.507812 47.117188 6.308594 C 46.703125 6.074219 46.152344 6.148438 45.835938 6.507812 Z M 45.238281 12.367188 C 44.957031 12.734375 44.867188 13.113281 44.902344 13.570312 C 44.957031 13.84375 45.09375 14.058594 45.316406 14.226562 C 45.613281 14.417969 46.015625 14.496094 46.367188 14.507812 C 46.394531 14.507812 46.394531 14.507812 46.417969 14.507812 C 47.132812 14.527344 47.90625 14.457031 48.453125 13.945312 C 48.652344 13.738281 48.710938 13.515625 48.703125 13.230469 C 48.683594 12.992188 48.570312 12.800781 48.394531 12.644531 C 48.113281 12.441406 47.726562 12.449219 47.398438 12.449219 C 47.355469 12.449219 47.3125 12.449219 47.269531 12.449219 C 47.15625 12.449219 47.046875 12.449219 46.933594 12.449219 C 46.753906 12.449219 46.574219 12.445312 46.394531 12.445312 C 46.332031 12.445312 46.269531 12.445312 46.210938 12.445312 C 45.882812 12.445312 45.5625 12.414062 45.238281 12.367188 Z M 45.238281 12.367188 "/> +<path style=" stroke:none;fill-rule:nonzero;fill:rgb(10.196079%,11.372549%,14.117648%);fill-opacity:1;" d="M 53.039062 2.382812 C 53.0625 2.390625 53.0625 2.390625 53.085938 2.398438 C 53.140625 2.429688 53.171875 2.453125 53.207031 2.503906 C 53.230469 2.617188 53.21875 2.730469 53.214844 2.84375 C 53.210938 2.878906 53.210938 2.914062 53.210938 2.953125 C 53.207031 3.027344 53.203125 3.105469 53.199219 3.183594 C 53.191406 3.371094 53.183594 3.558594 53.179688 3.746094 C 53.175781 3.792969 53.175781 3.835938 53.175781 3.882812 C 53.15625 4.441406 53.15625 5 53.15625 5.5625 C 53.15625 5.640625 53.15625 5.71875 53.15625 5.792969 C 53.15625 6.085938 53.160156 6.375 53.160156 6.664062 C 53.179688 6.644531 53.195312 6.625 53.214844 6.605469 C 53.238281 6.578125 53.261719 6.550781 53.285156 6.523438 C 53.296875 6.511719 53.3125 6.5 53.324219 6.484375 C 53.78125 5.984375 54.445312 5.601562 55.128906 5.550781 C 55.640625 5.535156 56.128906 5.578125 56.527344 5.929688 C 56.566406 5.964844 56.601562 6 56.640625 6.039062 C 56.664062 6.0625 56.664062 6.0625 56.691406 6.089844 C 57.246094 6.660156 57.203125 7.570312 57.203125 8.304688 C 57.203125 8.414062 57.203125 8.523438 57.207031 8.632812 C 57.207031 8.839844 57.207031 9.042969 57.207031 9.25 C 57.207031 9.484375 57.210938 9.722656 57.210938 9.957031 C 57.210938 10.4375 57.214844 10.921875 57.214844 11.40625 C 57.246094 11.410156 57.246094 11.410156 57.28125 11.414062 C 57.308594 11.417969 57.335938 11.421875 57.363281 11.425781 C 57.40625 11.433594 57.40625 11.433594 57.445312 11.441406 C 57.558594 11.457031 57.671875 11.480469 57.785156 11.503906 C 57.808594 11.507812 57.828125 11.511719 57.851562 11.515625 C 57.878906 11.519531 57.878906 11.519531 57.910156 11.527344 C 57.929688 11.53125 57.949219 11.535156 57.964844 11.539062 C 58.007812 11.550781 58.007812 11.550781 58.03125 11.574219 C 58.035156 11.613281 58.035156 11.65625 58.035156 11.695312 C 58.035156 11.71875 58.035156 11.746094 58.035156 11.769531 C 58.035156 11.796875 58.035156 11.824219 58.035156 11.851562 C 58.035156 11.875 58.035156 11.902344 58.035156 11.929688 C 58.035156 11.964844 58.035156 11.964844 58.035156 12.003906 C 58.035156 12.027344 58.035156 12.050781 58.035156 12.074219 C 58.03125 12.125 58.03125 12.125 58.007812 12.148438 C 57.964844 12.152344 57.921875 12.152344 57.882812 12.152344 C 57.839844 12.152344 57.839844 12.152344 57.796875 12.152344 C 57.769531 12.152344 57.738281 12.152344 57.707031 12.152344 C 57.675781 12.152344 57.640625 12.152344 57.609375 12.152344 C 57.523438 12.152344 57.433594 12.152344 57.347656 12.152344 C 57.257812 12.152344 57.164062 12.152344 57.074219 12.152344 C 56.917969 12.152344 56.765625 12.152344 56.613281 12.152344 C 56.433594 12.152344 56.257812 12.152344 56.082031 12.152344 C 55.929688 12.152344 55.777344 12.152344 55.625 12.152344 C 55.53125 12.152344 55.441406 12.152344 55.351562 12.152344 C 55.265625 12.152344 55.179688 12.152344 55.09375 12.152344 C 55.046875 12.152344 55 12.152344 54.953125 12.152344 C 54.925781 12.152344 54.898438 12.152344 54.871094 12.152344 C 54.847656 12.152344 54.824219 12.152344 54.796875 12.152344 C 54.742188 12.148438 54.742188 12.148438 54.71875 12.125 C 54.71875 12.085938 54.71875 12.042969 54.71875 12.003906 C 54.71875 11.976562 54.71875 11.953125 54.71875 11.925781 C 54.71875 11.902344 54.71875 11.875 54.71875 11.847656 C 54.71875 11.820312 54.71875 11.796875 54.71875 11.769531 C 54.71875 11.703125 54.71875 11.636719 54.71875 11.574219 C 54.8125 11.53125 54.902344 11.507812 55.003906 11.488281 C 55.03125 11.480469 55.0625 11.476562 55.09375 11.46875 C 55.113281 11.464844 55.128906 11.460938 55.144531 11.460938 C 55.191406 11.449219 55.242188 11.441406 55.289062 11.429688 C 55.527344 11.378906 55.527344 11.378906 55.585938 11.378906 C 55.582031 10.90625 55.582031 10.433594 55.578125 9.960938 C 55.578125 9.742188 55.578125 9.523438 55.574219 9.304688 C 55.574219 9.109375 55.574219 8.917969 55.574219 8.726562 C 55.574219 8.625 55.570312 8.527344 55.570312 8.425781 C 55.570312 8.328125 55.570312 8.234375 55.570312 8.136719 C 55.570312 8.101562 55.570312 8.066406 55.570312 8.03125 C 55.570312 7.664062 55.554688 7.199219 55.289062 6.917969 C 55.054688 6.722656 54.742188 6.746094 54.457031 6.761719 C 54.101562 6.800781 53.738281 7.007812 53.464844 7.234375 C 53.425781 7.265625 53.425781 7.265625 53.371094 7.300781 C 53.273438 7.371094 53.214844 7.421875 53.1875 7.539062 C 53.179688 7.640625 53.179688 7.742188 53.183594 7.84375 C 53.183594 7.882812 53.183594 7.921875 53.183594 7.960938 C 53.183594 8.066406 53.183594 8.167969 53.1875 8.273438 C 53.1875 8.386719 53.1875 8.496094 53.1875 8.605469 C 53.1875 8.8125 53.191406 9.023438 53.191406 9.230469 C 53.195312 9.46875 53.195312 9.703125 53.195312 9.941406 C 53.199219 10.429688 53.203125 10.917969 53.207031 11.40625 C 53.238281 11.410156 53.238281 11.410156 53.265625 11.414062 C 53.351562 11.429688 53.4375 11.445312 53.523438 11.464844 C 53.554688 11.46875 53.585938 11.472656 53.613281 11.480469 C 53.644531 11.484375 53.671875 11.492188 53.703125 11.496094 C 53.730469 11.5 53.753906 11.507812 53.78125 11.511719 C 53.847656 11.523438 53.910156 11.535156 53.976562 11.550781 C 53.976562 11.746094 53.976562 11.945312 53.976562 12.148438 C 52.890625 12.148438 51.804688 12.148438 50.6875 12.148438 C 50.6875 11.953125 50.6875 11.753906 50.6875 11.550781 C 50.964844 11.492188 51.242188 11.4375 51.527344 11.378906 C 51.542969 11.160156 51.554688 10.945312 51.554688 10.722656 C 51.554688 10.691406 51.554688 10.660156 51.554688 10.628906 C 51.554688 10.546875 51.558594 10.464844 51.558594 10.378906 C 51.558594 10.289062 51.558594 10.199219 51.558594 10.109375 C 51.558594 9.953125 51.558594 9.796875 51.558594 9.640625 C 51.558594 9.414062 51.558594 9.1875 51.5625 8.960938 C 51.5625 8.59375 51.5625 8.230469 51.5625 7.863281 C 51.566406 7.507812 51.566406 7.152344 51.566406 6.792969 C 51.566406 6.773438 51.566406 6.75 51.566406 6.726562 C 51.566406 6.617188 51.566406 6.507812 51.566406 6.398438 C 51.570312 5.484375 51.574219 4.570312 51.574219 3.65625 C 51.554688 3.65625 51.535156 3.652344 51.515625 3.652344 C 51.476562 3.648438 51.476562 3.648438 51.4375 3.644531 C 51.414062 3.644531 51.386719 3.640625 51.359375 3.640625 C 51.277344 3.632812 51.195312 3.621094 51.113281 3.609375 C 51.082031 3.605469 51.054688 3.601562 51.023438 3.597656 C 50.996094 3.59375 50.964844 3.589844 50.933594 3.582031 C 50.902344 3.578125 50.871094 3.574219 50.839844 3.570312 C 50.765625 3.558594 50.691406 3.546875 50.617188 3.535156 C 50.617188 3.347656 50.617188 3.15625 50.617188 2.960938 C 50.910156 2.875 51.207031 2.796875 51.5 2.722656 C 51.523438 2.714844 51.542969 2.710938 51.5625 2.707031 C 51.671875 2.679688 51.777344 2.652344 51.886719 2.625 C 51.972656 2.601562 52.0625 2.578125 52.152344 2.554688 C 52.257812 2.527344 52.367188 2.5 52.472656 2.472656 C 52.515625 2.460938 52.554688 2.453125 52.597656 2.441406 C 52.652344 2.425781 52.710938 2.410156 52.765625 2.398438 C 52.78125 2.394531 52.800781 2.386719 52.816406 2.382812 C 52.898438 2.363281 52.960938 2.351562 53.039062 2.382812 Z M 53.039062 2.382812 "/> +<path style=" stroke:none;fill-rule:nonzero;fill:rgb(10.196079%,11.372549%,14.117648%);fill-opacity:1;" d="M 99.691406 6.011719 C 100.109375 6.40625 100.234375 7.003906 100.273438 7.554688 C 100.277344 7.667969 100.277344 7.785156 100.277344 7.898438 C 100.277344 7.933594 100.277344 7.964844 100.277344 8 C 100.277344 8.074219 100.277344 8.144531 100.277344 8.21875 C 100.277344 8.332031 100.277344 8.445312 100.277344 8.558594 C 100.28125 8.886719 100.28125 9.210938 100.28125 9.535156 C 100.28125 9.714844 100.28125 9.894531 100.285156 10.074219 C 100.285156 10.171875 100.285156 10.265625 100.285156 10.359375 C 100.285156 10.449219 100.285156 10.539062 100.285156 10.628906 C 100.285156 10.675781 100.285156 10.726562 100.285156 10.773438 C 100.289062 10.964844 100.292969 11.167969 100.417969 11.320312 C 100.53125 11.378906 100.636719 11.398438 100.757812 11.367188 C 100.898438 11.308594 101.003906 11.21875 101.113281 11.117188 C 101.226562 11.199219 101.339844 11.289062 101.449219 11.378906 C 101.394531 11.527344 101.300781 11.640625 101.207031 11.765625 C 101.179688 11.804688 101.179688 11.804688 101.152344 11.84375 C 100.859375 12.152344 100.476562 12.265625 100.058594 12.277344 C 99.699219 12.269531 99.332031 12.164062 99.066406 11.910156 C 98.933594 11.757812 98.761719 11.519531 98.761719 11.308594 C 98.582031 11.449219 98.582031 11.449219 98.417969 11.605469 C 98.289062 11.738281 98.140625 11.847656 97.992188 11.957031 C 97.96875 11.972656 97.949219 11.992188 97.925781 12.007812 C 97.488281 12.3125 96.855469 12.339844 96.34375 12.25 C 95.917969 12.15625 95.527344 11.929688 95.28125 11.558594 C 95.035156 11.136719 94.964844 10.617188 95.082031 10.140625 C 95.289062 9.527344 95.746094 9.175781 96.300781 8.894531 C 96.941406 8.582031 97.644531 8.375 98.328125 8.179688 C 98.34375 8.175781 98.359375 8.171875 98.375 8.167969 C 98.472656 8.140625 98.566406 8.113281 98.664062 8.085938 C 98.695312 7.195312 98.695312 7.195312 98.328125 6.425781 C 98.121094 6.230469 97.828125 6.203125 97.558594 6.203125 C 97.53125 6.203125 97.53125 6.203125 97.503906 6.203125 C 97.339844 6.207031 97.171875 6.21875 97.007812 6.230469 C 97.003906 6.257812 97.003906 6.289062 97 6.316406 C 96.984375 6.46875 96.957031 6.617188 96.925781 6.765625 C 96.917969 6.816406 96.910156 6.863281 96.902344 6.910156 C 96.832031 7.273438 96.738281 7.585938 96.425781 7.808594 C 96.191406 7.933594 95.945312 7.933594 95.6875 7.867188 C 95.515625 7.808594 95.40625 7.707031 95.320312 7.546875 C 95.234375 7.347656 95.238281 7.183594 95.308594 6.980469 C 95.316406 6.957031 95.316406 6.957031 95.324219 6.929688 C 95.421875 6.644531 95.574219 6.441406 95.785156 6.230469 C 95.800781 6.214844 95.816406 6.195312 95.835938 6.179688 C 96.734375 5.308594 98.742188 5.21875 99.691406 6.011719 Z M 98.394531 8.742188 C 98.292969 8.777344 98.1875 8.8125 98.082031 8.847656 C 97.574219 9.007812 97.011719 9.230469 96.746094 9.722656 C 96.582031 10.042969 96.554688 10.355469 96.648438 10.703125 C 96.71875 10.914062 96.816406 11.042969 97.011719 11.152344 C 97.296875 11.292969 97.609375 11.304688 97.910156 11.199219 C 98.058594 11.132812 98.207031 11.050781 98.34375 10.960938 C 98.398438 10.921875 98.398438 10.921875 98.46875 10.890625 C 98.558594 10.839844 98.644531 10.789062 98.679688 10.683594 C 98.703125 10.542969 98.695312 10.402344 98.691406 10.257812 C 98.6875 10.214844 98.6875 10.167969 98.6875 10.121094 C 98.6875 10 98.683594 9.878906 98.683594 9.757812 C 98.679688 9.636719 98.679688 9.511719 98.675781 9.386719 C 98.675781 9.144531 98.667969 8.902344 98.664062 8.660156 C 98.578125 8.660156 98.476562 8.714844 98.394531 8.742188 Z M 98.394531 8.742188 "/> +<path style=" stroke:none;fill-rule:nonzero;fill:rgb(10.196079%,11.372549%,14.117648%);fill-opacity:1;" d="M 38.035156 6.242188 C 38.207031 6.40625 38.332031 6.578125 38.449219 6.785156 C 38.460938 6.808594 38.460938 6.808594 38.472656 6.832031 C 38.835938 7.472656 38.875 8.257812 38.761719 8.972656 C 38.734375 9 38.734375 9 38.671875 9 C 38.625 9 38.625 9 38.578125 9 C 38.5625 9 38.546875 9 38.53125 9 C 38.472656 9 38.417969 9 38.363281 9 C 38.324219 9 38.285156 9 38.242188 9 C 38.136719 9 38.027344 9 37.917969 9 C 37.804688 9 37.695312 9 37.582031 9 C 37.367188 9 37.152344 9 36.9375 9 C 36.695312 9 36.453125 9 36.207031 9 C 35.707031 9 35.207031 9 34.703125 9 C 34.714844 9.089844 34.726562 9.183594 34.738281 9.273438 C 34.742188 9.300781 34.742188 9.328125 34.746094 9.351562 C 34.8125 9.898438 35.007812 10.441406 35.4375 10.808594 C 35.933594 11.1875 36.476562 11.269531 37.089844 11.203125 C 37.539062 11.128906 37.90625 10.847656 38.222656 10.535156 C 38.347656 10.417969 38.347656 10.417969 38.425781 10.417969 C 38.464844 10.445312 38.464844 10.445312 38.503906 10.488281 C 38.589844 10.585938 38.683594 10.671875 38.785156 10.753906 C 38.679688 11.046875 38.46875 11.28125 38.257812 11.5 C 38.242188 11.519531 38.222656 11.535156 38.207031 11.554688 C 37.792969 12 37.171875 12.246094 36.574219 12.320312 C 36.554688 12.320312 36.535156 12.324219 36.515625 12.328125 C 35.640625 12.425781 34.773438 12.210938 34.074219 11.671875 C 33.421875 11.125 33.078125 10.363281 32.976562 9.527344 C 32.972656 9.496094 32.972656 9.496094 32.96875 9.460938 C 32.871094 8.5 33.074219 7.515625 33.675781 6.746094 C 33.707031 6.710938 33.738281 6.675781 33.769531 6.640625 C 33.78125 6.621094 33.796875 6.605469 33.8125 6.585938 C 34.316406 5.988281 35.136719 5.640625 35.902344 5.566406 C 36.699219 5.511719 37.429688 5.699219 38.035156 6.242188 Z M 35.226562 6.652344 C 34.949219 7.007812 34.820312 7.386719 34.746094 7.824219 C 34.742188 7.851562 34.738281 7.875 34.734375 7.898438 C 34.730469 7.921875 34.726562 7.949219 34.722656 7.972656 C 34.71875 7.992188 34.714844 8.015625 34.710938 8.035156 C 34.703125 8.097656 34.703125 8.15625 34.703125 8.222656 C 34.703125 8.242188 34.703125 8.261719 34.703125 8.28125 C 34.703125 8.292969 34.703125 8.308594 34.703125 8.324219 C 34.972656 8.328125 35.242188 8.328125 35.507812 8.328125 C 35.632812 8.328125 35.757812 8.328125 35.882812 8.332031 C 36.003906 8.332031 36.125 8.332031 36.246094 8.332031 C 36.289062 8.332031 36.335938 8.332031 36.382812 8.332031 C 36.445312 8.332031 36.511719 8.332031 36.574219 8.332031 C 36.59375 8.332031 36.613281 8.332031 36.632812 8.332031 C 36.800781 8.332031 36.964844 8.304688 37.085938 8.183594 C 37.273438 7.9375 37.277344 7.609375 37.246094 7.3125 C 37.195312 6.96875 37.015625 6.636719 36.730469 6.425781 C 36.226562 6.113281 35.617188 6.195312 35.226562 6.652344 Z M 35.226562 6.652344 "/> +<path style=" stroke:none;fill-rule:nonzero;fill:rgb(10.196079%,11.372549%,14.117648%);fill-opacity:1;" d="M 112.726562 6.085938 C 112.90625 6.242188 113.058594 6.414062 113.183594 6.617188 C 113.199219 6.636719 113.210938 6.65625 113.226562 6.679688 C 113.628906 7.320312 113.699219 8.167969 113.566406 8.902344 C 113.523438 8.945312 113.449219 8.929688 113.386719 8.929688 C 113.371094 8.929688 113.355469 8.929688 113.339844 8.929688 C 113.28125 8.929688 113.226562 8.929688 113.171875 8.929688 C 113.132812 8.929688 113.089844 8.933594 113.050781 8.933594 C 112.945312 8.933594 112.835938 8.933594 112.726562 8.933594 C 112.613281 8.933594 112.5 8.933594 112.386719 8.933594 C 112.175781 8.9375 111.960938 8.9375 111.746094 8.9375 C 111.503906 8.941406 111.257812 8.941406 111.015625 8.941406 C 110.515625 8.945312 110.011719 8.949219 109.511719 8.949219 C 109.519531 9.023438 109.527344 9.097656 109.535156 9.171875 C 109.535156 9.191406 109.539062 9.210938 109.539062 9.230469 C 109.554688 9.378906 109.578125 9.523438 109.613281 9.667969 C 109.621094 9.6875 109.625 9.710938 109.628906 9.730469 C 109.703125 10.011719 109.808594 10.261719 109.992188 10.488281 C 110.003906 10.507812 110.019531 10.527344 110.035156 10.542969 C 110.320312 10.898438 110.765625 11.117188 111.214844 11.164062 C 111.839844 11.203125 112.339844 11.078125 112.820312 10.671875 C 112.9375 10.570312 113.050781 10.457031 113.160156 10.347656 C 113.230469 10.378906 113.28125 10.414062 113.339844 10.46875 C 113.355469 10.480469 113.367188 10.496094 113.382812 10.507812 C 113.398438 10.523438 113.414062 10.539062 113.429688 10.554688 C 113.445312 10.566406 113.460938 10.582031 113.476562 10.597656 C 113.515625 10.632812 113.554688 10.671875 113.59375 10.707031 C 113.324219 11.316406 112.769531 11.816406 112.15625 12.066406 C 112.054688 12.105469 111.953125 12.136719 111.847656 12.171875 C 111.832031 12.175781 111.816406 12.179688 111.800781 12.183594 C 111.507812 12.265625 111.214844 12.28125 110.914062 12.28125 C 110.898438 12.28125 110.878906 12.28125 110.859375 12.28125 C 110.554688 12.277344 110.261719 12.257812 109.96875 12.175781 C 109.941406 12.167969 109.941406 12.167969 109.914062 12.160156 C 109.203125 11.953125 108.628906 11.503906 108.238281 10.875 C 108.230469 10.859375 108.222656 10.847656 108.210938 10.832031 C 107.699219 9.980469 107.648438 8.855469 107.878906 7.90625 C 108.074219 7.136719 108.570312 6.417969 109.253906 6 C 110.304688 5.378906 111.75 5.261719 112.726562 6.085938 Z M 110.105469 6.496094 C 109.710938 6.9375 109.507812 7.546875 109.511719 8.136719 C 109.511719 8.160156 109.511719 8.179688 109.511719 8.203125 C 109.511719 8.21875 109.511719 8.234375 109.511719 8.253906 C 109.78125 8.253906 110.050781 8.253906 110.316406 8.257812 C 110.441406 8.257812 110.566406 8.257812 110.691406 8.257812 C 110.8125 8.257812 110.933594 8.257812 111.054688 8.257812 C 111.097656 8.257812 111.144531 8.261719 111.191406 8.261719 C 111.253906 8.261719 111.320312 8.261719 111.382812 8.261719 C 111.402344 8.261719 111.421875 8.261719 111.441406 8.261719 C 111.59375 8.261719 111.746094 8.234375 111.871094 8.140625 C 112.082031 7.886719 112.078125 7.605469 112.054688 7.289062 C 112.011719 6.949219 111.867188 6.6875 111.625 6.449219 C 111.609375 6.433594 111.59375 6.417969 111.582031 6.40625 C 111.164062 6.015625 110.496094 6.121094 110.105469 6.496094 Z M 110.105469 6.496094 "/> +<path style=" stroke:none;fill-rule:nonzero;fill:rgb(10.196079%,11.372549%,14.117648%);fill-opacity:1;" d="M 119.207031 6.039062 C 119.210938 6.308594 119.203125 6.578125 119.1875 6.847656 C 119.183594 6.910156 119.183594 6.972656 119.179688 7.035156 C 119.175781 7.078125 119.175781 7.117188 119.171875 7.160156 C 119.171875 7.175781 119.171875 7.195312 119.171875 7.214844 C 119.164062 7.308594 119.152344 7.390625 119.136719 7.484375 C 118.835938 7.484375 118.535156 7.484375 118.222656 7.484375 C 118.207031 7.40625 118.191406 7.328125 118.171875 7.246094 C 118.15625 7.171875 118.140625 7.097656 118.121094 7.023438 C 118.109375 6.96875 118.101562 6.917969 118.089844 6.867188 C 118.070312 6.792969 118.054688 6.714844 118.039062 6.640625 C 118.035156 6.617188 118.027344 6.59375 118.023438 6.570312 C 118.019531 6.550781 118.015625 6.527344 118.007812 6.503906 C 118.003906 6.484375 118 6.464844 117.996094 6.445312 C 117.984375 6.398438 117.984375 6.398438 117.960938 6.351562 C 117.902344 6.332031 117.847656 6.316406 117.789062 6.300781 C 117.765625 6.296875 117.765625 6.296875 117.738281 6.289062 C 117.339844 6.1875 116.835938 6.167969 116.464844 6.375 C 116.296875 6.480469 116.203125 6.609375 116.128906 6.789062 C 116.082031 7.042969 116.105469 7.261719 116.234375 7.484375 C 116.496094 7.828125 117.082031 7.953125 117.46875 8.070312 C 118.183594 8.289062 118.960938 8.597656 119.359375 9.277344 C 119.578125 9.714844 119.621094 10.257812 119.496094 10.734375 C 119.316406 11.277344 118.957031 11.703125 118.449219 11.964844 C 117.460938 12.445312 116.246094 12.394531 115.222656 12.054688 C 115.007812 11.972656 114.804688 11.867188 114.601562 11.765625 C 114.570312 11.234375 114.574219 10.707031 114.574219 10.175781 C 114.886719 10.175781 115.195312 10.175781 115.511719 10.175781 C 115.539062 10.304688 115.539062 10.304688 115.5625 10.433594 C 115.582031 10.515625 115.597656 10.597656 115.613281 10.679688 C 115.625 10.734375 115.636719 10.792969 115.648438 10.847656 C 115.664062 10.929688 115.679688 11.011719 115.695312 11.09375 C 115.703125 11.121094 115.707031 11.144531 115.710938 11.171875 C 115.71875 11.195312 115.722656 11.21875 115.726562 11.242188 C 115.730469 11.261719 115.734375 11.285156 115.738281 11.304688 C 115.75 11.355469 115.75 11.355469 115.777344 11.40625 C 116.324219 11.617188 117.03125 11.667969 117.578125 11.4375 C 117.800781 11.332031 117.949219 11.207031 118.039062 10.972656 C 118.089844 10.761719 118.082031 10.527344 117.992188 10.328125 C 117.910156 10.191406 117.820312 10.105469 117.6875 10.023438 C 117.664062 10.003906 117.636719 9.988281 117.609375 9.972656 C 117.265625 9.769531 116.875 9.65625 116.496094 9.527344 C 116.066406 9.386719 115.683594 9.222656 115.320312 8.949219 C 115.296875 8.933594 115.273438 8.917969 115.25 8.898438 C 115.226562 8.875 115.226562 8.875 115.199219 8.855469 C 115.199219 8.839844 115.199219 8.824219 115.199219 8.804688 C 115.1875 8.800781 115.171875 8.796875 115.160156 8.789062 C 114.933594 8.65625 114.792969 8.285156 114.726562 8.046875 C 114.605469 7.507812 114.6875 6.964844 114.984375 6.496094 C 115.347656 5.957031 115.902344 5.671875 116.523438 5.542969 C 117.460938 5.367188 118.386719 5.574219 119.207031 6.039062 Z M 119.207031 6.039062 "/> +<path style=" stroke:none;fill-rule:nonzero;fill:rgb(10.196079%,11.372549%,14.117648%);fill-opacity:1;" d="M 67.945312 6.109375 C 67.96875 6.136719 67.96875 6.136719 67.96875 6.1875 C 67.96875 6.210938 67.96875 6.234375 67.964844 6.257812 C 67.964844 6.285156 67.964844 6.3125 67.964844 6.339844 C 67.960938 6.367188 67.960938 6.398438 67.960938 6.425781 C 67.960938 6.457031 67.957031 6.484375 67.957031 6.515625 C 67.941406 6.863281 67.917969 7.207031 67.894531 7.554688 C 67.59375 7.554688 67.292969 7.554688 66.984375 7.554688 C 66.921875 7.3125 66.863281 7.070312 66.808594 6.828125 C 66.804688 6.796875 66.796875 6.769531 66.789062 6.742188 C 66.785156 6.714844 66.777344 6.6875 66.773438 6.660156 C 66.765625 6.632812 66.761719 6.609375 66.753906 6.585938 C 66.742188 6.519531 66.742188 6.519531 66.742188 6.425781 C 66.707031 6.414062 66.667969 6.402344 66.628906 6.390625 C 66.605469 6.386719 66.585938 6.378906 66.5625 6.371094 C 66.355469 6.320312 66.15625 6.296875 65.941406 6.296875 C 65.917969 6.296875 65.894531 6.296875 65.871094 6.296875 C 65.5625 6.300781 65.296875 6.351562 65.0625 6.566406 C 64.894531 6.742188 64.859375 6.90625 64.863281 7.148438 C 64.867188 7.285156 64.890625 7.390625 64.96875 7.507812 C 64.976562 7.519531 64.984375 7.535156 64.996094 7.550781 C 65.261719 7.921875 65.914062 8.0625 66.328125 8.179688 C 67.054688 8.390625 67.703125 8.726562 68.089844 9.40625 C 68.214844 9.648438 68.289062 9.90625 68.304688 10.175781 C 68.304688 10.199219 68.304688 10.21875 68.308594 10.242188 C 68.324219 10.753906 68.15625 11.1875 67.824219 11.574219 C 67.808594 11.589844 67.796875 11.605469 67.78125 11.621094 C 67.28125 12.164062 66.515625 12.347656 65.808594 12.390625 C 65.144531 12.414062 64.535156 12.34375 63.910156 12.117188 C 63.886719 12.109375 63.886719 12.109375 63.859375 12.097656 C 63.675781 12.03125 63.507812 11.929688 63.335938 11.835938 C 63.335938 11.632812 63.335938 11.429688 63.335938 11.226562 C 63.335938 11.132812 63.335938 11.039062 63.335938 10.945312 C 63.332031 10.851562 63.332031 10.761719 63.332031 10.671875 C 63.332031 10.636719 63.332031 10.601562 63.332031 10.566406 C 63.332031 10.515625 63.332031 10.46875 63.332031 10.421875 C 63.332031 10.390625 63.332031 10.363281 63.332031 10.335938 C 63.335938 10.273438 63.335938 10.273438 63.359375 10.25 C 63.421875 10.246094 63.484375 10.246094 63.550781 10.246094 C 63.570312 10.246094 63.585938 10.246094 63.605469 10.246094 C 63.648438 10.246094 63.6875 10.246094 63.726562 10.246094 C 63.789062 10.246094 63.851562 10.246094 63.914062 10.246094 C 63.953125 10.246094 63.992188 10.246094 64.03125 10.246094 C 64.050781 10.246094 64.066406 10.246094 64.085938 10.246094 C 64.21875 10.246094 64.21875 10.246094 64.273438 10.273438 C 64.285156 10.316406 64.285156 10.316406 64.296875 10.375 C 64.300781 10.394531 64.304688 10.414062 64.308594 10.4375 C 64.316406 10.460938 64.320312 10.484375 64.324219 10.507812 C 64.328125 10.53125 64.332031 10.554688 64.339844 10.578125 C 64.347656 10.628906 64.359375 10.679688 64.367188 10.730469 C 64.382812 10.808594 64.398438 10.882812 64.414062 10.960938 C 64.421875 11.007812 64.433594 11.058594 64.441406 11.105469 C 64.445312 11.128906 64.453125 11.152344 64.457031 11.175781 C 64.476562 11.285156 64.492188 11.386719 64.488281 11.5 C 64.53125 11.511719 64.53125 11.511719 64.578125 11.519531 C 64.691406 11.546875 64.804688 11.574219 64.921875 11.605469 C 65.117188 11.648438 65.308594 11.648438 65.507812 11.652344 C 65.539062 11.652344 65.570312 11.652344 65.601562 11.652344 C 65.964844 11.652344 66.320312 11.59375 66.605469 11.359375 C 66.761719 11.199219 66.820312 11.003906 66.828125 10.789062 C 66.820312 10.566406 66.761719 10.382812 66.601562 10.226562 C 66.214844 9.933594 65.765625 9.789062 65.3125 9.640625 C 64.621094 9.414062 63.949219 9.125 63.59375 8.445312 C 63.378906 8 63.375 7.464844 63.53125 6.996094 C 63.761719 6.410156 64.183594 6.027344 64.753906 5.773438 C 65.792969 5.351562 66.988281 5.59375 67.945312 6.109375 Z M 67.945312 6.109375 "/> +<path style=" stroke:none;fill-rule:nonzero;fill:rgb(10.196079%,11.372549%,14.117648%);fill-opacity:1;" d="M 105.941406 5.769531 C 105.960938 5.777344 105.976562 5.785156 105.996094 5.792969 C 106.1875 5.867188 106.351562 5.964844 106.535156 6.0625 C 106.511719 6.539062 106.488281 7.015625 106.464844 7.507812 C 106.164062 7.507812 105.863281 7.507812 105.550781 7.507812 C 105.445312 7.101562 105.445312 7.101562 105.410156 6.941406 C 105.40625 6.925781 105.402344 6.910156 105.398438 6.890625 C 105.386719 6.839844 105.375 6.789062 105.367188 6.738281 C 105.359375 6.703125 105.351562 6.667969 105.34375 6.632812 C 105.324219 6.546875 105.304688 6.460938 105.289062 6.375 C 105.234375 6.359375 105.183594 6.34375 105.128906 6.328125 C 105.097656 6.320312 105.070312 6.3125 105.039062 6.304688 C 104.859375 6.253906 104.6875 6.25 104.503906 6.25 C 104.464844 6.25 104.464844 6.25 104.421875 6.25 C 104.136719 6.246094 103.90625 6.3125 103.664062 6.464844 C 103.507812 6.621094 103.417969 6.816406 103.414062 7.042969 C 103.425781 7.25 103.492188 7.433594 103.632812 7.585938 C 103.976562 7.886719 104.484375 8.003906 104.910156 8.132812 C 105.628906 8.351562 106.285156 8.667969 106.667969 9.355469 C 106.878906 9.800781 106.9375 10.371094 106.785156 10.84375 C 106.554688 11.417969 106.144531 11.8125 105.585938 12.070312 C 104.601562 12.488281 103.335938 12.402344 102.359375 12.007812 C 102.203125 11.9375 102.046875 11.859375 101.902344 11.765625 C 101.894531 11.699219 101.894531 11.699219 101.894531 11.617188 C 101.894531 11.585938 101.894531 11.554688 101.890625 11.523438 C 101.890625 11.488281 101.890625 11.453125 101.890625 11.417969 C 101.890625 11.382812 101.890625 11.347656 101.890625 11.3125 C 101.890625 11.222656 101.886719 11.128906 101.886719 11.039062 C 101.886719 10.925781 101.886719 10.816406 101.882812 10.707031 C 101.882812 10.539062 101.882812 10.371094 101.878906 10.203125 C 102.1875 10.203125 102.496094 10.203125 102.816406 10.203125 C 102.871094 10.363281 102.871094 10.363281 102.886719 10.449219 C 102.890625 10.46875 102.894531 10.488281 102.898438 10.507812 C 102.902344 10.527344 102.90625 10.546875 102.910156 10.566406 C 102.914062 10.585938 102.917969 10.609375 102.921875 10.628906 C 102.933594 10.671875 102.941406 10.71875 102.949219 10.761719 C 102.964844 10.828125 102.976562 10.894531 102.988281 10.960938 C 103 11.003906 103.007812 11.046875 103.015625 11.089844 C 103.019531 11.109375 103.023438 11.132812 103.027344 11.152344 C 103.046875 11.246094 103.0625 11.332031 103.054688 11.429688 C 103.699219 11.59375 104.421875 11.726562 105.03125 11.386719 C 105.21875 11.265625 105.316406 11.125 105.375 10.914062 C 105.402344 10.691406 105.378906 10.496094 105.273438 10.292969 C 104.921875 9.867188 104.363281 9.730469 103.859375 9.5625 C 103.1875 9.339844 102.511719 9.058594 102.167969 8.398438 C 101.949219 7.929688 101.9375 7.414062 102.097656 6.929688 C 102.101562 6.90625 102.109375 6.886719 102.117188 6.863281 C 102.269531 6.417969 102.628906 6.066406 103.03125 5.847656 C 103.054688 5.832031 103.078125 5.820312 103.101562 5.808594 C 103.382812 5.65625 103.699219 5.574219 104.015625 5.535156 C 104.035156 5.53125 104.054688 5.527344 104.074219 5.527344 C 104.714844 5.449219 105.34375 5.535156 105.941406 5.769531 Z M 105.941406 5.769531 "/> +<path style=" stroke:none;fill-rule:nonzero;fill:rgb(10.196079%,11.372549%,14.117648%);fill-opacity:1;" d="M 59.953125 3.921875 C 60.316406 3.921875 60.679688 3.921875 61.054688 3.921875 C 61.058594 4.292969 61.054688 4.667969 61.046875 5.039062 C 61.042969 5.066406 61.042969 5.066406 61.042969 5.09375 C 61.042969 5.144531 61.042969 5.195312 61.039062 5.246094 C 61.039062 5.277344 61.039062 5.304688 61.035156 5.335938 C 61.03125 5.472656 61.019531 5.613281 61.007812 5.75 C 61.53125 5.75 62.054688 5.75 62.59375 5.75 C 62.59375 6.035156 62.59375 6.320312 62.59375 6.617188 C 62.0625 6.617188 61.53125 6.617188 60.984375 6.617188 C 60.984375 7.128906 60.988281 7.644531 60.988281 8.160156 C 60.992188 8.398438 60.992188 8.636719 60.992188 8.875 C 60.992188 9.082031 60.992188 9.292969 60.996094 9.5 C 60.996094 9.609375 60.996094 9.71875 60.996094 9.832031 C 60.996094 9.933594 60.996094 10.039062 60.996094 10.140625 C 60.996094 10.179688 60.996094 10.21875 60.996094 10.253906 C 60.992188 10.765625 60.992188 10.765625 61.199219 11.210938 C 61.34375 11.347656 61.507812 11.40625 61.703125 11.40625 C 61.941406 11.371094 62.144531 11.289062 62.355469 11.175781 C 62.457031 11.125 62.457031 11.125 62.519531 11.117188 C 62.558594 11.140625 62.558594 11.140625 62.597656 11.183594 C 62.613281 11.195312 62.625 11.210938 62.640625 11.226562 C 62.652344 11.238281 62.667969 11.253906 62.683594 11.269531 C 62.695312 11.285156 62.710938 11.300781 62.726562 11.316406 C 62.761719 11.351562 62.796875 11.390625 62.832031 11.429688 C 62.785156 11.5625 62.707031 11.65625 62.617188 11.765625 C 62.605469 11.78125 62.59375 11.792969 62.578125 11.808594 C 62.375 12.03125 62.085938 12.183594 61.800781 12.269531 C 61.777344 12.277344 61.75 12.285156 61.726562 12.292969 C 61.511719 12.347656 61.296875 12.351562 61.078125 12.351562 C 61.046875 12.351562 61.019531 12.351562 60.988281 12.351562 C 60.523438 12.351562 60.085938 12.210938 59.730469 11.898438 C 59.542969 11.699219 59.425781 11.40625 59.375 11.140625 C 59.371094 11.117188 59.367188 11.097656 59.363281 11.074219 C 59.351562 10.988281 59.347656 10.902344 59.347656 10.8125 C 59.347656 10.792969 59.347656 10.777344 59.347656 10.757812 C 59.347656 10.695312 59.347656 10.636719 59.347656 10.578125 C 59.347656 10.535156 59.347656 10.492188 59.347656 10.449219 C 59.347656 10.332031 59.347656 10.214844 59.347656 10.097656 C 59.351562 9.972656 59.351562 9.851562 59.351562 9.730469 C 59.351562 9.496094 59.351562 9.265625 59.351562 9.035156 C 59.351562 8.769531 59.351562 8.507812 59.351562 8.242188 C 59.351562 7.703125 59.351562 7.160156 59.351562 6.617188 C 59.035156 6.617188 58.71875 6.617188 58.390625 6.617188 C 58.390625 6.371094 58.390625 6.125 58.390625 5.871094 C 58.914062 5.800781 58.914062 5.800781 59.449219 5.726562 C 59.480469 5.601562 59.515625 5.476562 59.550781 5.351562 C 59.574219 5.269531 59.59375 5.191406 59.617188 5.113281 C 59.652344 4.988281 59.6875 4.863281 59.722656 4.738281 C 59.75 4.640625 59.777344 4.539062 59.804688 4.4375 C 59.816406 4.398438 59.824219 4.359375 59.835938 4.324219 C 59.851562 4.269531 59.867188 4.214844 59.882812 4.160156 C 59.886719 4.144531 59.890625 4.128906 59.894531 4.113281 C 59.914062 4.046875 59.929688 3.984375 59.953125 3.921875 Z M 59.953125 3.921875 "/> +<path style=" stroke:none;fill-rule:nonzero;fill:rgb(10.196079%,11.372549%,14.117648%);fill-opacity:1;" d="M 93.351562 5.554688 C 93.40625 5.59375 93.429688 5.617188 93.445312 5.679688 C 93.449219 5.75 93.445312 5.8125 93.441406 5.878906 C 93.441406 5.90625 93.441406 5.929688 93.441406 5.957031 C 93.4375 6.015625 93.4375 6.070312 93.433594 6.128906 C 93.429688 6.273438 93.425781 6.417969 93.421875 6.5625 C 93.417969 6.617188 93.417969 6.671875 93.417969 6.726562 C 93.402344 7.175781 93.40625 7.625 93.40625 8.074219 C 93.40625 8.195312 93.40625 8.3125 93.40625 8.429688 C 93.40625 8.644531 93.40625 8.859375 93.40625 9.074219 C 93.40625 9.320312 93.40625 9.570312 93.40625 9.816406 C 93.40625 10.320312 93.40625 10.828125 93.40625 11.332031 C 93.425781 11.335938 93.449219 11.339844 93.46875 11.34375 C 93.542969 11.355469 93.613281 11.371094 93.6875 11.382812 C 93.734375 11.390625 93.785156 11.398438 93.832031 11.40625 C 93.859375 11.414062 93.890625 11.417969 93.921875 11.425781 C 93.949219 11.429688 93.976562 11.433594 94.003906 11.4375 C 94.070312 11.449219 94.136719 11.464844 94.199219 11.476562 C 94.199219 11.675781 94.199219 11.875 94.199219 12.078125 C 93.105469 12.078125 92.015625 12.078125 90.886719 12.078125 C 90.886719 11.878906 90.886719 11.679688 90.886719 11.476562 C 90.988281 11.457031 91.085938 11.433594 91.1875 11.414062 C 91.222656 11.40625 91.253906 11.402344 91.289062 11.394531 C 91.339844 11.382812 91.386719 11.375 91.4375 11.363281 C 91.480469 11.355469 91.480469 11.355469 91.527344 11.34375 C 91.601562 11.332031 91.675781 11.332031 91.753906 11.332031 C 91.753906 10.894531 91.753906 10.457031 91.753906 10.023438 C 91.753906 9.820312 91.753906 9.617188 91.753906 9.414062 C 91.753906 9.234375 91.753906 9.058594 91.753906 8.882812 C 91.753906 8.789062 91.753906 8.695312 91.753906 8.601562 C 91.753906 8.066406 91.746094 7.535156 91.726562 7 C 91.683594 6.996094 91.683594 6.996094 91.636719 6.988281 C 91.539062 6.976562 91.4375 6.964844 91.339844 6.953125 C 91.296875 6.945312 91.25 6.941406 91.207031 6.933594 C 91.144531 6.925781 91.082031 6.917969 91.019531 6.910156 C 90.988281 6.90625 90.988281 6.90625 90.957031 6.902344 C 90.820312 6.882812 90.820312 6.882812 90.792969 6.855469 C 90.789062 6.816406 90.789062 6.777344 90.789062 6.738281 C 90.789062 6.714844 90.789062 6.691406 90.789062 6.667969 C 90.789062 6.640625 90.789062 6.617188 90.789062 6.589844 C 90.789062 6.566406 90.789062 6.539062 90.789062 6.515625 C 90.792969 6.453125 90.792969 6.390625 90.792969 6.328125 C 90.96875 6.253906 91.148438 6.1875 91.328125 6.125 C 91.355469 6.117188 91.382812 6.105469 91.414062 6.097656 C 91.503906 6.066406 91.59375 6.03125 91.683594 6 C 91.808594 5.957031 91.929688 5.914062 92.054688 5.871094 C 92.070312 5.867188 92.085938 5.859375 92.101562 5.855469 C 92.285156 5.792969 92.46875 5.726562 92.652344 5.660156 C 92.667969 5.652344 92.6875 5.644531 92.703125 5.640625 C 92.78125 5.609375 92.859375 5.582031 92.9375 5.554688 C 92.976562 5.539062 92.976562 5.539062 93.019531 5.523438 C 93.050781 5.511719 93.050781 5.511719 93.082031 5.5 C 93.1875 5.476562 93.261719 5.503906 93.351562 5.554688 Z M 93.351562 5.554688 "/> +<path style=" stroke:none;fill-rule:nonzero;fill:rgb(10.196079%,11.372549%,14.117648%);fill-opacity:1;" d="M 42.214844 5.652344 C 42.214844 7.542969 42.214844 9.433594 42.214844 11.378906 C 42.535156 11.441406 42.535156 11.441406 42.863281 11.5 C 42.917969 11.515625 42.976562 11.53125 43.03125 11.550781 C 43.03125 11.738281 43.03125 11.929688 43.03125 12.125 C 41.929688 12.125 40.832031 12.125 39.695312 12.125 C 39.695312 11.9375 39.695312 11.746094 39.695312 11.550781 C 39.875 11.5 40.054688 11.460938 40.234375 11.425781 C 40.265625 11.417969 40.265625 11.417969 40.296875 11.410156 C 40.316406 11.40625 40.335938 11.402344 40.355469 11.398438 C 40.375 11.394531 40.394531 11.390625 40.410156 11.386719 C 40.46875 11.378906 40.523438 11.378906 40.585938 11.378906 C 40.582031 10.886719 40.578125 10.390625 40.574219 9.898438 C 40.574219 9.667969 40.574219 9.4375 40.570312 9.207031 C 40.570312 9.007812 40.570312 8.808594 40.570312 8.605469 C 40.566406 8.5 40.566406 8.394531 40.566406 8.289062 C 40.566406 8.191406 40.566406 8.089844 40.566406 7.988281 C 40.566406 7.953125 40.566406 7.917969 40.566406 7.878906 C 40.5625 7.683594 40.558594 7.488281 40.550781 7.292969 C 40.546875 7.273438 40.546875 7.253906 40.546875 7.234375 C 40.542969 7.140625 40.542969 7.140625 40.511719 7.050781 C 40.445312 7.035156 40.378906 7.027344 40.308594 7.019531 C 40.289062 7.015625 40.269531 7.011719 40.25 7.011719 C 40.183594 7.003906 40.121094 6.996094 40.054688 6.988281 C 40.011719 6.980469 39.96875 6.976562 39.921875 6.96875 C 39.816406 6.957031 39.707031 6.941406 39.601562 6.929688 C 39.601562 6.746094 39.601562 6.5625 39.601562 6.375 C 39.964844 6.242188 40.328125 6.109375 40.695312 5.980469 C 40.777344 5.953125 40.859375 5.921875 40.945312 5.894531 C 41 5.875 41.054688 5.855469 41.109375 5.835938 C 41.246094 5.789062 41.386719 5.738281 41.523438 5.6875 C 41.550781 5.675781 41.578125 5.667969 41.605469 5.65625 C 41.660156 5.636719 41.710938 5.617188 41.761719 5.597656 C 41.785156 5.589844 41.808594 5.578125 41.835938 5.570312 C 41.863281 5.558594 41.863281 5.558594 41.894531 5.546875 C 42.027344 5.515625 42.09375 5.585938 42.214844 5.652344 Z M 42.214844 5.652344 "/> +<path style=" stroke:none;fill-rule:nonzero;fill:rgb(98.823529%,73.725492%,19.607843%);fill-opacity:1;" d="M 8.640625 9.261719 C 8.972656 9.519531 9.191406 9.859375 9.277344 10.273438 C 9.328125 10.675781 9.265625 11.089844 9.019531 11.421875 C 8.734375 11.769531 8.371094 12.007812 7.917969 12.058594 C 7.476562 12.09375 7.078125 11.957031 6.742188 11.667969 C 6.71875 11.648438 6.71875 11.648438 6.695312 11.628906 C 6.421875 11.378906 6.261719 10.992188 6.234375 10.628906 C 6.226562 10.179688 6.355469 9.789062 6.664062 9.457031 C 7.191406 8.921875 8.019531 8.84375 8.640625 9.261719 Z M 8.640625 9.261719 "/> +<path style=" stroke:none;fill-rule:nonzero;fill:rgb(98.823529%,73.725492%,19.607843%);fill-opacity:1;" d="M 2.855469 4.089844 C 2.941406 4.15625 3.019531 4.230469 3.097656 4.308594 C 3.113281 4.324219 3.128906 4.339844 3.148438 4.359375 C 3.414062 4.640625 3.542969 5.027344 3.539062 5.410156 C 3.519531 5.851562 3.332031 6.21875 3.015625 6.527344 C 2.707031 6.792969 2.304688 6.921875 1.898438 6.898438 C 1.578125 6.871094 1.308594 6.769531 1.054688 6.570312 C 1.03125 6.546875 1.03125 6.546875 1.003906 6.527344 C 0.699219 6.277344 0.527344 5.898438 0.484375 5.511719 C 0.453125 5.121094 0.558594 4.730469 0.804688 4.425781 C 1.003906 4.191406 1.226562 4.03125 1.511719 3.921875 C 1.53125 3.914062 1.550781 3.90625 1.570312 3.898438 C 1.988281 3.757812 2.496094 3.84375 2.855469 4.089844 Z M 2.855469 4.089844 "/> +<path style=" stroke:none;fill-rule:nonzero;fill:rgb(98.823529%,73.725492%,19.607843%);fill-opacity:1;" d="M 2.960938 11.683594 C 3.269531 11.949219 3.492188 12.316406 3.53125 12.726562 C 3.558594 13.152344 3.449219 13.550781 3.171875 13.878906 C 2.875 14.203125 2.519531 14.375 2.082031 14.417969 C 1.671875 14.433594 1.28125 14.28125 0.972656 14.011719 C 0.660156 13.714844 0.488281 13.324219 0.476562 12.890625 C 0.480469 12.53125 0.59375 12.214844 0.816406 11.933594 C 0.828125 11.917969 0.839844 11.902344 0.851562 11.882812 C 1.078125 11.597656 1.433594 11.421875 1.785156 11.363281 C 2.207031 11.316406 2.628906 11.417969 2.960938 11.683594 Z M 2.960938 11.683594 "/> +<path style=" stroke:none;fill-rule:nonzero;fill:rgb(98.823529%,73.725492%,19.607843%);fill-opacity:1;" d="M 14.449219 4.167969 C 14.507812 4.222656 14.5625 4.273438 14.617188 4.332031 C 14.628906 4.34375 14.640625 4.355469 14.65625 4.367188 C 14.914062 4.632812 15.015625 5.023438 15.03125 5.378906 C 15.019531 5.734375 14.902344 6.046875 14.6875 6.328125 C 14.675781 6.34375 14.664062 6.359375 14.652344 6.378906 C 14.410156 6.679688 14.042969 6.851562 13.664062 6.898438 C 13.242188 6.925781 12.84375 6.816406 12.523438 6.535156 C 12.484375 6.5 12.445312 6.460938 12.40625 6.425781 C 12.394531 6.410156 12.378906 6.394531 12.363281 6.378906 C 12.085938 6.089844 11.988281 5.707031 11.992188 5.316406 C 12.003906 4.914062 12.167969 4.53125 12.457031 4.25 C 13.023438 3.75 13.847656 3.714844 14.449219 4.167969 Z M 14.449219 4.167969 "/> +<path style=" stroke:none;fill-rule:nonzero;fill:rgb(10.196079%,11.372549%,14.117648%);fill-opacity:1;" d="M 42.046875 2.648438 C 42.253906 2.816406 42.375 3.03125 42.40625 3.296875 C 42.433594 3.558594 42.351562 3.808594 42.191406 4.019531 C 42.007812 4.214844 41.785156 4.351562 41.507812 4.363281 C 41.46875 4.363281 41.429688 4.363281 41.390625 4.363281 C 41.371094 4.363281 41.351562 4.363281 41.332031 4.363281 C 41.058594 4.355469 40.832031 4.273438 40.625 4.089844 C 40.476562 3.933594 40.359375 3.734375 40.34375 3.511719 C 40.34375 3.496094 40.339844 3.480469 40.339844 3.460938 C 40.328125 3.230469 40.390625 2.992188 40.542969 2.8125 C 40.554688 2.796875 40.570312 2.78125 40.585938 2.765625 C 40.597656 2.75 40.613281 2.734375 40.632812 2.714844 C 41.019531 2.339844 41.621094 2.332031 42.046875 2.648438 Z M 42.046875 2.648438 "/> +<path style=" stroke:none;fill-rule:nonzero;fill:rgb(10.196079%,11.372549%,14.117648%);fill-opacity:1;" d="M 93.210938 2.566406 C 93.453125 2.757812 93.570312 2.96875 93.609375 3.273438 C 93.621094 3.53125 93.550781 3.773438 93.378906 3.972656 C 93.367188 3.988281 93.351562 4.003906 93.335938 4.019531 C 93.316406 4.039062 93.316406 4.039062 93.296875 4.058594 C 93.066406 4.28125 92.78125 4.316406 92.476562 4.3125 C 92.355469 4.308594 92.25 4.285156 92.136719 4.234375 C 92.117188 4.226562 92.101562 4.21875 92.082031 4.210938 C 91.871094 4.105469 91.703125 3.9375 91.605469 3.722656 C 91.515625 3.449219 91.515625 3.171875 91.632812 2.90625 C 91.773438 2.652344 92 2.484375 92.277344 2.402344 C 92.605469 2.324219 92.933594 2.375 93.210938 2.566406 Z M 93.210938 2.566406 "/> +<path style=" stroke:none;fill-rule:nonzero;fill:rgb(98.823529%,73.725492%,19.607843%);fill-opacity:1;" d="M 8.320312 5.960938 C 8.539062 6.117188 8.664062 6.320312 8.726562 6.582031 C 8.769531 6.839844 8.710938 7.089844 8.574219 7.3125 C 8.410156 7.535156 8.207031 7.65625 7.945312 7.722656 C 7.621094 7.753906 7.355469 7.6875 7.105469 7.484375 C 6.921875 7.3125 6.8125 7.078125 6.792969 6.832031 C 6.789062 6.535156 6.871094 6.28125 7.078125 6.0625 C 7.4375 5.738281 7.914062 5.710938 8.320312 5.960938 Z M 8.320312 5.960938 "/> +<path style=" stroke:none;fill-rule:nonzero;fill:rgb(98.431373%,73.725492%,19.607843%);fill-opacity:1;" d="M 14.09375 0.820312 C 14.289062 0.96875 14.421875 1.179688 14.472656 1.417969 C 14.5 1.714844 14.464844 1.980469 14.273438 2.214844 C 14.082031 2.421875 13.890625 2.558594 13.605469 2.582031 C 13.320312 2.585938 13.078125 2.535156 12.863281 2.332031 C 12.660156 2.128906 12.550781 1.910156 12.542969 1.621094 C 12.546875 1.332031 12.644531 1.117188 12.839844 0.910156 C 13.199219 0.574219 13.6875 0.5625 14.09375 0.820312 Z M 14.09375 0.820312 "/> +<path style=" stroke:none;fill-rule:nonzero;fill:rgb(98.823529%,73.725492%,19.607843%);fill-opacity:1;" d="M 2.574219 0.808594 C 2.769531 0.972656 2.925781 1.164062 2.976562 1.417969 C 2.996094 1.71875 2.972656 1.976562 2.777344 2.214844 C 2.585938 2.421875 2.394531 2.558594 2.109375 2.582031 C 1.824219 2.585938 1.582031 2.53125 1.367188 2.332031 C 1.226562 2.195312 1.136719 2.066406 1.078125 1.875 C 1.074219 1.863281 1.070312 1.847656 1.066406 1.832031 C 1.015625 1.570312 1.054688 1.3125 1.195312 1.089844 C 1.515625 0.644531 2.101562 0.484375 2.574219 0.808594 Z M 2.574219 0.808594 "/> +<path style=" stroke:none;fill-rule:nonzero;fill:rgb(98.823529%,73.725492%,19.607843%);fill-opacity:1;" d="M 2.550781 8.316406 C 2.582031 8.34375 2.609375 8.371094 2.640625 8.398438 C 2.65625 8.414062 2.671875 8.429688 2.691406 8.445312 C 2.878906 8.640625 2.960938 8.847656 2.964844 9.121094 C 2.960938 9.398438 2.882812 9.613281 2.6875 9.816406 C 2.5 9.996094 2.257812 10.109375 1.992188 10.105469 C 1.695312 10.085938 1.449219 9.972656 1.246094 9.753906 C 1.074219 9.535156 1.003906 9.277344 1.03125 9 C 1.089844 8.710938 1.214844 8.484375 1.453125 8.3125 C 1.789062 8.105469 2.222656 8.085938 2.550781 8.316406 Z M 2.550781 8.316406 "/> +<path style=" stroke:none;fill-rule:nonzero;fill:rgb(98.823529%,73.725492%,19.607843%);fill-opacity:1;" d="M 14.058594 8.34375 C 14.269531 8.496094 14.425781 8.714844 14.472656 8.972656 C 14.496094 9.300781 14.441406 9.542969 14.238281 9.804688 C 14.148438 9.90625 14.042969 9.972656 13.921875 10.03125 C 13.894531 10.046875 13.894531 10.046875 13.867188 10.058594 C 13.660156 10.144531 13.386719 10.136719 13.175781 10.066406 C 12.929688 9.960938 12.714844 9.769531 12.613281 9.519531 C 12.601562 9.484375 12.585938 9.445312 12.574219 9.40625 C 12.570312 9.390625 12.566406 9.378906 12.5625 9.363281 C 12.511719 9.109375 12.550781 8.855469 12.679688 8.632812 C 12.824219 8.421875 13.003906 8.277344 13.246094 8.203125 C 13.261719 8.199219 13.277344 8.195312 13.292969 8.191406 C 13.570312 8.136719 13.820312 8.195312 14.058594 8.34375 Z M 14.058594 8.34375 "/> +<path style=" stroke:none;fill-rule:nonzero;fill:rgb(98.823529%,73.725492%,19.607843%);fill-opacity:1;" d="M 8.375 13.523438 C 8.605469 13.730469 8.71875 13.984375 8.742188 14.289062 C 8.734375 14.554688 8.621094 14.789062 8.445312 14.984375 C 8.25 15.164062 7.996094 15.25 7.734375 15.253906 C 7.46875 15.242188 7.25 15.136719 7.0625 14.953125 C 6.863281 14.730469 6.789062 14.488281 6.792969 14.195312 C 6.832031 13.894531 6.964844 13.664062 7.199219 13.472656 C 7.582031 13.230469 8.015625 13.25 8.375 13.523438 Z M 8.375 13.523438 "/> +<path style=" stroke:none;fill-rule:nonzero;fill:rgb(98.823529%,73.725492%,19.607843%);fill-opacity:1;" d="M 14.027344 12.066406 C 14.25 12.234375 14.421875 12.449219 14.472656 12.726562 C 14.492188 13.019531 14.464844 13.269531 14.273438 13.5 C 14.089844 13.699219 13.886719 13.84375 13.609375 13.863281 C 13.3125 13.867188 13.058594 13.800781 12.832031 13.589844 C 12.730469 13.480469 12.65625 13.371094 12.601562 13.234375 C 12.589844 13.207031 12.582031 13.183594 12.570312 13.15625 C 12.519531 12.886719 12.539062 12.625 12.679688 12.386719 C 12.984375 11.945312 13.558594 11.765625 14.027344 12.066406 Z M 14.027344 12.066406 "/> +<path style=" stroke:none;fill-rule:nonzero;fill:rgb(98.823529%,73.725492%,19.607843%);fill-opacity:1;" d="M 8.269531 2.203125 C 8.492188 2.371094 8.652344 2.574219 8.703125 2.855469 C 8.738281 3.113281 8.695312 3.371094 8.535156 3.582031 C 8.355469 3.796875 8.136719 3.949219 7.851562 3.976562 C 7.539062 3.988281 7.289062 3.894531 7.054688 3.679688 C 6.851562 3.449219 6.796875 3.222656 6.808594 2.914062 C 6.824219 2.671875 6.929688 2.480469 7.105469 2.308594 C 7.445312 2.039062 7.886719 1.964844 8.269531 2.203125 Z M 8.269531 2.203125 "/> +</g> +</svg> diff --git a/web/app/components/base/icons/assets/vender/line/editor/collapse.svg b/web/app/components/base/icons/assets/vender/line/editor/collapse.svg new file mode 100644 index 0000000000..b54e046085 --- /dev/null +++ b/web/app/components/base/icons/assets/vender/line/editor/collapse.svg @@ -0,0 +1,9 @@ +<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"> +<g id="Icon L"> +<g id="Vector"> +<path d="M2.66602 11.3333H0.666016L3.33268 8.66667L5.99935 11.3333H3.99935L3.99935 14H2.66602L2.66602 11.3333Z" fill="#354052"/> +<path d="M2.66602 4.66667L2.66602 2L3.99935 2L3.99935 4.66667L5.99935 4.66667L3.33268 7.33333L0.666016 4.66667L2.66602 4.66667Z" fill="#354052"/> +<path d="M7.33268 2.66667H13.9993V4H7.33268V2.66667ZM7.33268 12H13.9993V13.3333H7.33268V12ZM5.99935 7.33333H13.9993V8.66667H5.99935V7.33333Z" fill="#354052"/> +</g> +</g> +</svg> diff --git a/web/app/components/base/icons/src/public/avatar/Robot.json b/web/app/components/base/icons/src/public/avatar/Robot.json index babc0f87a0..8969a2a649 100644 --- a/web/app/components/base/icons/src/public/avatar/Robot.json +++ b/web/app/components/base/icons/src/public/avatar/Robot.json @@ -89,4 +89,4 @@ ] }, "name": "Robot" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/avatar/User.json b/web/app/components/base/icons/src/public/avatar/User.json index 01fb8e39c3..4b9ad7615f 100644 --- a/web/app/components/base/icons/src/public/avatar/User.json +++ b/web/app/components/base/icons/src/public/avatar/User.json @@ -86,4 +86,4 @@ ] }, "name": "User" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/billing/ArCube1.json b/web/app/components/base/icons/src/public/billing/ArCube1.json index f341c9218f..89d9786c04 100644 --- a/web/app/components/base/icons/src/public/billing/ArCube1.json +++ b/web/app/components/base/icons/src/public/billing/ArCube1.json @@ -26,4 +26,4 @@ ] }, "name": "ArCube1" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/billing/Asterisk.json b/web/app/components/base/icons/src/public/billing/Asterisk.json index 6f70b27a1f..d4a2e91b45 100644 --- a/web/app/components/base/icons/src/public/billing/Asterisk.json +++ b/web/app/components/base/icons/src/public/billing/Asterisk.json @@ -35,4 +35,4 @@ ] }, "name": "Asterisk" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/billing/AwsMarketplace.json b/web/app/components/base/icons/src/public/billing/AwsMarketplace.json index 8a0b1003cd..8aeb93f7b2 100644 --- a/web/app/components/base/icons/src/public/billing/AwsMarketplace.json +++ b/web/app/components/base/icons/src/public/billing/AwsMarketplace.json @@ -176,4 +176,4 @@ ] }, "name": "AwsMarketplace" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/billing/Azure.json b/web/app/components/base/icons/src/public/billing/Azure.json index ad4cd429a6..fb6a9b9e95 100644 --- a/web/app/components/base/icons/src/public/billing/Azure.json +++ b/web/app/components/base/icons/src/public/billing/Azure.json @@ -190,4 +190,4 @@ ] }, "name": "Azure" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/billing/Buildings.json b/web/app/components/base/icons/src/public/billing/Buildings.json index f9dd338328..62d22f97c6 100644 --- a/web/app/components/base/icons/src/public/billing/Buildings.json +++ b/web/app/components/base/icons/src/public/billing/Buildings.json @@ -36,4 +36,4 @@ ] }, "name": "Buildings" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/billing/Diamond.json b/web/app/components/base/icons/src/public/billing/Diamond.json index 69ab74606b..6717026232 100644 --- a/web/app/components/base/icons/src/public/billing/Diamond.json +++ b/web/app/components/base/icons/src/public/billing/Diamond.json @@ -36,4 +36,4 @@ ] }, "name": "Diamond" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/billing/GoogleCloud.json b/web/app/components/base/icons/src/public/billing/GoogleCloud.json index 244f05776f..0c55bdaf03 100644 --- a/web/app/components/base/icons/src/public/billing/GoogleCloud.json +++ b/web/app/components/base/icons/src/public/billing/GoogleCloud.json @@ -63,4 +63,4 @@ ] }, "name": "GoogleCloud" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/billing/Group2.json b/web/app/components/base/icons/src/public/billing/Group2.json index b2424ba881..8cc0896d5d 100644 --- a/web/app/components/base/icons/src/public/billing/Group2.json +++ b/web/app/components/base/icons/src/public/billing/Group2.json @@ -26,4 +26,4 @@ ] }, "name": "Group2" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/billing/Keyframe.json b/web/app/components/base/icons/src/public/billing/Keyframe.json index c721854d14..ed0dcb4fba 100644 --- a/web/app/components/base/icons/src/public/billing/Keyframe.json +++ b/web/app/components/base/icons/src/public/billing/Keyframe.json @@ -25,4 +25,4 @@ ] }, "name": "Keyframe" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/billing/Sparkles.json b/web/app/components/base/icons/src/public/billing/Sparkles.json index ea2bae44e7..5317b50936 100644 --- a/web/app/components/base/icons/src/public/billing/Sparkles.json +++ b/web/app/components/base/icons/src/public/billing/Sparkles.json @@ -92,4 +92,4 @@ ] }, "name": "Sparkles" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/billing/SparklesSoft.json b/web/app/components/base/icons/src/public/billing/SparklesSoft.json index ce4f11f489..b6a5a6ddf4 100644 --- a/web/app/components/base/icons/src/public/billing/SparklesSoft.json +++ b/web/app/components/base/icons/src/public/billing/SparklesSoft.json @@ -33,4 +33,4 @@ ] }, "name": "SparklesSoft" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/common/D.json b/web/app/components/base/icons/src/public/common/D.json index 2090b8909d..ab4ed79135 100644 --- a/web/app/components/base/icons/src/public/common/D.json +++ b/web/app/components/base/icons/src/public/common/D.json @@ -122,4 +122,4 @@ ] }, "name": "D" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/common/DiagonalDividingLine.json b/web/app/components/base/icons/src/public/common/DiagonalDividingLine.json index 04475c2288..a9e7cd7217 100644 --- a/web/app/components/base/icons/src/public/common/DiagonalDividingLine.json +++ b/web/app/components/base/icons/src/public/common/DiagonalDividingLine.json @@ -25,4 +25,4 @@ ] }, "name": "DiagonalDividingLine" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/common/Dify.json b/web/app/components/base/icons/src/public/common/Dify.json index 9926e91986..a954b66757 100644 --- a/web/app/components/base/icons/src/public/common/Dify.json +++ b/web/app/components/base/icons/src/public/common/Dify.json @@ -59,4 +59,4 @@ ] }, "name": "Dify" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/common/Gdpr.json b/web/app/components/base/icons/src/public/common/Gdpr.json index 3605030eb8..1e030b54d1 100644 --- a/web/app/components/base/icons/src/public/common/Gdpr.json +++ b/web/app/components/base/icons/src/public/common/Gdpr.json @@ -337,4 +337,4 @@ ] }, "name": "Gdpr" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/common/Github.json b/web/app/components/base/icons/src/public/common/Github.json index abccde4f5e..523bcd55b8 100644 --- a/web/app/components/base/icons/src/public/common/Github.json +++ b/web/app/components/base/icons/src/public/common/Github.json @@ -33,4 +33,4 @@ ] }, "name": "Github" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/common/Highlight.json b/web/app/components/base/icons/src/public/common/Highlight.json index d18386eb01..055d9f79ca 100644 --- a/web/app/components/base/icons/src/public/common/Highlight.json +++ b/web/app/components/base/icons/src/public/common/Highlight.json @@ -64,4 +64,4 @@ ] }, "name": "Highlight" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/common/Iso.json b/web/app/components/base/icons/src/public/common/Iso.json index 6864a591c4..50f0267b60 100644 --- a/web/app/components/base/icons/src/public/common/Iso.json +++ b/web/app/components/base/icons/src/public/common/Iso.json @@ -118,4 +118,4 @@ ] }, "name": "Iso" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/common/Line3.json b/web/app/components/base/icons/src/public/common/Line3.json index 32f6d50bb8..2beb66a5f4 100644 --- a/web/app/components/base/icons/src/public/common/Line3.json +++ b/web/app/components/base/icons/src/public/common/Line3.json @@ -25,4 +25,4 @@ ] }, "name": "Line3" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/common/Lock.json b/web/app/components/base/icons/src/public/common/Lock.json index 24af41a73d..a5a1f4b781 100644 --- a/web/app/components/base/icons/src/public/common/Lock.json +++ b/web/app/components/base/icons/src/public/common/Lock.json @@ -35,4 +35,4 @@ ] }, "name": "Lock" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/common/MessageChatSquare.json b/web/app/components/base/icons/src/public/common/MessageChatSquare.json index 18069eda39..71cf6d0c98 100644 --- a/web/app/components/base/icons/src/public/common/MessageChatSquare.json +++ b/web/app/components/base/icons/src/public/common/MessageChatSquare.json @@ -34,4 +34,4 @@ ] }, "name": "MessageChatSquare" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/common/MultiPathRetrieval.json b/web/app/components/base/icons/src/public/common/MultiPathRetrieval.json index d37b263688..9d64edadd4 100644 --- a/web/app/components/base/icons/src/public/common/MultiPathRetrieval.json +++ b/web/app/components/base/icons/src/public/common/MultiPathRetrieval.json @@ -150,4 +150,4 @@ ] }, "name": "MultiPathRetrieval" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/common/NTo1Retrieval.json b/web/app/components/base/icons/src/public/common/NTo1Retrieval.json index 086522046f..74ca34573f 100644 --- a/web/app/components/base/icons/src/public/common/NTo1Retrieval.json +++ b/web/app/components/base/icons/src/public/common/NTo1Retrieval.json @@ -143,4 +143,4 @@ ] }, "name": "NTo1Retrieval" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/common/Notion.json b/web/app/components/base/icons/src/public/common/Notion.json index 27bb0081d0..d27aeb8190 100644 --- a/web/app/components/base/icons/src/public/common/Notion.json +++ b/web/app/components/base/icons/src/public/common/Notion.json @@ -80,4 +80,4 @@ ] }, "name": "Notion" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/common/Soc2.json b/web/app/components/base/icons/src/public/common/Soc2.json index 34080b0adb..38b9c5e606 100644 --- a/web/app/components/base/icons/src/public/common/Soc2.json +++ b/web/app/components/base/icons/src/public/common/Soc2.json @@ -935,4 +935,4 @@ ] }, "name": "Soc2" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/common/SparklesSoft.json b/web/app/components/base/icons/src/public/common/SparklesSoft.json index e22cec82a3..11ac030c5e 100644 --- a/web/app/components/base/icons/src/public/common/SparklesSoft.json +++ b/web/app/components/base/icons/src/public/common/SparklesSoft.json @@ -44,4 +44,4 @@ ] }, "name": "SparklesSoft" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/education/Triangle.json b/web/app/components/base/icons/src/public/education/Triangle.json index 92d7c82c43..ab00049ce1 100644 --- a/web/app/components/base/icons/src/public/education/Triangle.json +++ b/web/app/components/base/icons/src/public/education/Triangle.json @@ -24,4 +24,4 @@ ] }, "name": "Triangle" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/files/Csv.json b/web/app/components/base/icons/src/public/files/Csv.json index d4d2bd9f3e..533dcd7525 100644 --- a/web/app/components/base/icons/src/public/files/Csv.json +++ b/web/app/components/base/icons/src/public/files/Csv.json @@ -178,4 +178,4 @@ ] }, "name": "Csv" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/files/Doc.json b/web/app/components/base/icons/src/public/files/Doc.json index f4513177a6..9d219addd2 100644 --- a/web/app/components/base/icons/src/public/files/Doc.json +++ b/web/app/components/base/icons/src/public/files/Doc.json @@ -166,4 +166,4 @@ ] }, "name": "Doc" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/files/Docx.json b/web/app/components/base/icons/src/public/files/Docx.json index 5054f083b4..ffa9ef8d3b 100644 --- a/web/app/components/base/icons/src/public/files/Docx.json +++ b/web/app/components/base/icons/src/public/files/Docx.json @@ -175,4 +175,4 @@ ] }, "name": "Docx" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/files/Html.json b/web/app/components/base/icons/src/public/files/Html.json index 86134d1df9..f267073c47 100644 --- a/web/app/components/base/icons/src/public/files/Html.json +++ b/web/app/components/base/icons/src/public/files/Html.json @@ -175,4 +175,4 @@ ] }, "name": "Html" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/files/Json.json b/web/app/components/base/icons/src/public/files/Json.json index ae2943dac6..0801fecc1c 100644 --- a/web/app/components/base/icons/src/public/files/Json.json +++ b/web/app/components/base/icons/src/public/files/Json.json @@ -175,4 +175,4 @@ ] }, "name": "Json" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/files/Md.json b/web/app/components/base/icons/src/public/files/Md.json index da1669658c..4a3cb687e6 100644 --- a/web/app/components/base/icons/src/public/files/Md.json +++ b/web/app/components/base/icons/src/public/files/Md.json @@ -141,4 +141,4 @@ ] }, "name": "Md" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/files/Pdf.json b/web/app/components/base/icons/src/public/files/Pdf.json index e5ff4bc33b..7770f2790d 100644 --- a/web/app/components/base/icons/src/public/files/Pdf.json +++ b/web/app/components/base/icons/src/public/files/Pdf.json @@ -166,4 +166,4 @@ ] }, "name": "Pdf" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/files/Txt.json b/web/app/components/base/icons/src/public/files/Txt.json index e511b9271c..c689fc680d 100644 --- a/web/app/components/base/icons/src/public/files/Txt.json +++ b/web/app/components/base/icons/src/public/files/Txt.json @@ -177,4 +177,4 @@ ] }, "name": "Txt" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/files/Unknown.json b/web/app/components/base/icons/src/public/files/Unknown.json index c39df990d0..f1351e039e 100644 --- a/web/app/components/base/icons/src/public/files/Unknown.json +++ b/web/app/components/base/icons/src/public/files/Unknown.json @@ -196,4 +196,4 @@ ] }, "name": "Unknown" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/files/Xlsx.json b/web/app/components/base/icons/src/public/files/Xlsx.json index 9cd6a618bf..5f0e7a96fc 100644 --- a/web/app/components/base/icons/src/public/files/Xlsx.json +++ b/web/app/components/base/icons/src/public/files/Xlsx.json @@ -142,4 +142,4 @@ ] }, "name": "Xlsx" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/files/Yaml.json b/web/app/components/base/icons/src/public/files/Yaml.json index e35087a8e8..aa05cb468e 100644 --- a/web/app/components/base/icons/src/public/files/Yaml.json +++ b/web/app/components/base/icons/src/public/files/Yaml.json @@ -178,4 +178,4 @@ ] }, "name": "Yaml" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/knowledge/Chunk.json b/web/app/components/base/icons/src/public/knowledge/Chunk.json index 469d85d1a7..91e85f2ce1 100644 --- a/web/app/components/base/icons/src/public/knowledge/Chunk.json +++ b/web/app/components/base/icons/src/public/knowledge/Chunk.json @@ -113,4 +113,4 @@ ] }, "name": "Chunk" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/knowledge/Collapse.json b/web/app/components/base/icons/src/public/knowledge/Collapse.json index 66d457155d..726b074007 100644 --- a/web/app/components/base/icons/src/public/knowledge/Collapse.json +++ b/web/app/components/base/icons/src/public/knowledge/Collapse.json @@ -59,4 +59,4 @@ ] }, "name": "Collapse" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/knowledge/GeneralType.json b/web/app/components/base/icons/src/public/knowledge/GeneralType.json index 9a87d00a60..5cbfb1a83c 100644 --- a/web/app/components/base/icons/src/public/knowledge/GeneralType.json +++ b/web/app/components/base/icons/src/public/knowledge/GeneralType.json @@ -35,4 +35,4 @@ ] }, "name": "GeneralType" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/knowledge/LayoutRight2LineMod.json b/web/app/components/base/icons/src/public/knowledge/LayoutRight2LineMod.json index 26c5cf1d4f..194bec705e 100644 --- a/web/app/components/base/icons/src/public/knowledge/LayoutRight2LineMod.json +++ b/web/app/components/base/icons/src/public/knowledge/LayoutRight2LineMod.json @@ -33,4 +33,4 @@ ] }, "name": "LayoutRight2LineMod" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/knowledge/ParentChildType.json b/web/app/components/base/icons/src/public/knowledge/ParentChildType.json index 250da77fc8..2d3270e418 100644 --- a/web/app/components/base/icons/src/public/knowledge/ParentChildType.json +++ b/web/app/components/base/icons/src/public/knowledge/ParentChildType.json @@ -53,4 +53,4 @@ ] }, "name": "ParentChildType" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/knowledge/SelectionMod.json b/web/app/components/base/icons/src/public/knowledge/SelectionMod.json index ff8174a572..c88e27809f 100644 --- a/web/app/components/base/icons/src/public/knowledge/SelectionMod.json +++ b/web/app/components/base/icons/src/public/knowledge/SelectionMod.json @@ -113,4 +113,4 @@ ] }, "name": "SelectionMod" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/llm/Anthropic.json b/web/app/components/base/icons/src/public/llm/Anthropic.json index f237bba80e..db33abd6cc 100644 --- a/web/app/components/base/icons/src/public/llm/Anthropic.json +++ b/web/app/components/base/icons/src/public/llm/Anthropic.json @@ -34,4 +34,4 @@ ] }, "name": "Anthropic" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/llm/AnthropicDark.json b/web/app/components/base/icons/src/public/llm/AnthropicDark.json index 4f3af3ce79..ca066c2e78 100644 --- a/web/app/components/base/icons/src/public/llm/AnthropicDark.json +++ b/web/app/components/base/icons/src/public/llm/AnthropicDark.json @@ -1043,4 +1043,4 @@ ] }, "name": "AnthropicDark" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/llm/AnthropicLight.json b/web/app/components/base/icons/src/public/llm/AnthropicLight.json index 3e84eb4dd6..2d2b0aab3e 100644 --- a/web/app/components/base/icons/src/public/llm/AnthropicLight.json +++ b/web/app/components/base/icons/src/public/llm/AnthropicLight.json @@ -1043,4 +1043,4 @@ ] }, "name": "AnthropicLight" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/llm/AnthropicText.json b/web/app/components/base/icons/src/public/llm/AnthropicText.json index 72b3e6ebb7..7f89795d2f 100644 --- a/web/app/components/base/icons/src/public/llm/AnthropicText.json +++ b/web/app/components/base/icons/src/public/llm/AnthropicText.json @@ -536,4 +536,4 @@ ] }, "name": "AnthropicText" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/llm/AzureOpenaiService.json b/web/app/components/base/icons/src/public/llm/AzureOpenaiService.json index 42cba3143b..bf07b59a51 100644 --- a/web/app/components/base/icons/src/public/llm/AzureOpenaiService.json +++ b/web/app/components/base/icons/src/public/llm/AzureOpenaiService.json @@ -71,4 +71,4 @@ ] }, "name": "AzureOpenaiService" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/llm/AzureOpenaiServiceText.json b/web/app/components/base/icons/src/public/llm/AzureOpenaiServiceText.json index 12cdeec971..f4342d7c39 100644 --- a/web/app/components/base/icons/src/public/llm/AzureOpenaiServiceText.json +++ b/web/app/components/base/icons/src/public/llm/AzureOpenaiServiceText.json @@ -233,4 +233,4 @@ ] }, "name": "AzureOpenaiServiceText" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/llm/Azureai.json b/web/app/components/base/icons/src/public/llm/Azureai.json index 8662cfb937..004da326da 100644 --- a/web/app/components/base/icons/src/public/llm/Azureai.json +++ b/web/app/components/base/icons/src/public/llm/Azureai.json @@ -177,4 +177,4 @@ ] }, "name": "Azureai" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/llm/AzureaiText.json b/web/app/components/base/icons/src/public/llm/AzureaiText.json index 2eb359960e..44976aa8e2 100644 --- a/web/app/components/base/icons/src/public/llm/AzureaiText.json +++ b/web/app/components/base/icons/src/public/llm/AzureaiText.json @@ -240,4 +240,4 @@ ] }, "name": "AzureaiText" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/llm/Baichuan.json b/web/app/components/base/icons/src/public/llm/Baichuan.json index ad93703002..196fbada8c 100644 --- a/web/app/components/base/icons/src/public/llm/Baichuan.json +++ b/web/app/components/base/icons/src/public/llm/Baichuan.json @@ -73,4 +73,4 @@ ] }, "name": "Baichuan" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/llm/BaichuanText.json b/web/app/components/base/icons/src/public/llm/BaichuanText.json index cda52e97fd..c4dc1d1101 100644 --- a/web/app/components/base/icons/src/public/llm/BaichuanText.json +++ b/web/app/components/base/icons/src/public/llm/BaichuanText.json @@ -153,4 +153,4 @@ ] }, "name": "BaichuanText" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/llm/Chatglm.json b/web/app/components/base/icons/src/public/llm/Chatglm.json index 37a6aa9913..c01787f8eb 100644 --- a/web/app/components/base/icons/src/public/llm/Chatglm.json +++ b/web/app/components/base/icons/src/public/llm/Chatglm.json @@ -69,4 +69,4 @@ ] }, "name": "Chatglm" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/llm/ChatglmText.json b/web/app/components/base/icons/src/public/llm/ChatglmText.json index 80b765cfc8..1fe28ea749 100644 --- a/web/app/components/base/icons/src/public/llm/ChatglmText.json +++ b/web/app/components/base/icons/src/public/llm/ChatglmText.json @@ -132,4 +132,4 @@ ] }, "name": "ChatglmText" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/llm/Cohere.json b/web/app/components/base/icons/src/public/llm/Cohere.json index 255514e8b0..70628917da 100644 --- a/web/app/components/base/icons/src/public/llm/Cohere.json +++ b/web/app/components/base/icons/src/public/llm/Cohere.json @@ -109,4 +109,4 @@ ] }, "name": "Cohere" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/llm/CohereText.json b/web/app/components/base/icons/src/public/llm/CohereText.json index 588b345814..89657ccac6 100644 --- a/web/app/components/base/icons/src/public/llm/CohereText.json +++ b/web/app/components/base/icons/src/public/llm/CohereText.json @@ -87,4 +87,4 @@ ] }, "name": "CohereText" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/llm/Gpt3.json b/web/app/components/base/icons/src/public/llm/Gpt3.json index 253b9a3d3f..383cb98d3a 100644 --- a/web/app/components/base/icons/src/public/llm/Gpt3.json +++ b/web/app/components/base/icons/src/public/llm/Gpt3.json @@ -48,4 +48,4 @@ ] }, "name": "Gpt3" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/llm/Gpt4.json b/web/app/components/base/icons/src/public/llm/Gpt4.json index 0e50c5f712..b0d1941df1 100644 --- a/web/app/components/base/icons/src/public/llm/Gpt4.json +++ b/web/app/components/base/icons/src/public/llm/Gpt4.json @@ -48,4 +48,4 @@ ] }, "name": "Gpt4" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/llm/Huggingface.json b/web/app/components/base/icons/src/public/llm/Huggingface.json index b3bd943da7..57e10e2b45 100644 --- a/web/app/components/base/icons/src/public/llm/Huggingface.json +++ b/web/app/components/base/icons/src/public/llm/Huggingface.json @@ -155,4 +155,4 @@ ] }, "name": "Huggingface" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/llm/HuggingfaceText.json b/web/app/components/base/icons/src/public/llm/HuggingfaceText.json index 4e80364b55..d113e64f17 100644 --- a/web/app/components/base/icons/src/public/llm/HuggingfaceText.json +++ b/web/app/components/base/icons/src/public/llm/HuggingfaceText.json @@ -319,4 +319,4 @@ ] }, "name": "HuggingfaceText" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/llm/HuggingfaceTextHub.json b/web/app/components/base/icons/src/public/llm/HuggingfaceTextHub.json index 9dcc6d64a8..0500abf2db 100644 --- a/web/app/components/base/icons/src/public/llm/HuggingfaceTextHub.json +++ b/web/app/components/base/icons/src/public/llm/HuggingfaceTextHub.json @@ -347,4 +347,4 @@ ] }, "name": "HuggingfaceTextHub" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/llm/IflytekSpark.json b/web/app/components/base/icons/src/public/llm/IflytekSpark.json index 03f50d7e39..1803b5f573 100644 --- a/web/app/components/base/icons/src/public/llm/IflytekSpark.json +++ b/web/app/components/base/icons/src/public/llm/IflytekSpark.json @@ -41,4 +41,4 @@ ] }, "name": "IflytekSpark" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/llm/IflytekSparkText.json b/web/app/components/base/icons/src/public/llm/IflytekSparkText.json index bd51f88aeb..2b01c14a6d 100644 --- a/web/app/components/base/icons/src/public/llm/IflytekSparkText.json +++ b/web/app/components/base/icons/src/public/llm/IflytekSparkText.json @@ -184,4 +184,4 @@ ] }, "name": "IflytekSparkText" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/llm/IflytekSparkTextCn.json b/web/app/components/base/icons/src/public/llm/IflytekSparkTextCn.json index 4c874ad6ec..22d1411037 100644 --- a/web/app/components/base/icons/src/public/llm/IflytekSparkTextCn.json +++ b/web/app/components/base/icons/src/public/llm/IflytekSparkTextCn.json @@ -95,4 +95,4 @@ ] }, "name": "IflytekSparkTextCn" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/llm/Jina.json b/web/app/components/base/icons/src/public/llm/Jina.json index fc40c022f5..88d70a3ff1 100644 --- a/web/app/components/base/icons/src/public/llm/Jina.json +++ b/web/app/components/base/icons/src/public/llm/Jina.json @@ -32,4 +32,4 @@ ] }, "name": "Jina" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/llm/JinaText.json b/web/app/components/base/icons/src/public/llm/JinaText.json index 04831fa4aa..08e76ef580 100644 --- a/web/app/components/base/icons/src/public/llm/JinaText.json +++ b/web/app/components/base/icons/src/public/llm/JinaText.json @@ -79,4 +79,4 @@ ] }, "name": "JinaText" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/llm/Localai.json b/web/app/components/base/icons/src/public/llm/Localai.json index 30b9786182..e0f85498d7 100644 --- a/web/app/components/base/icons/src/public/llm/Localai.json +++ b/web/app/components/base/icons/src/public/llm/Localai.json @@ -104,4 +104,4 @@ ] }, "name": "Localai" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/llm/LocalaiText.json b/web/app/components/base/icons/src/public/llm/LocalaiText.json index e7a45194aa..849f7ae4f4 100644 --- a/web/app/components/base/icons/src/public/llm/LocalaiText.json +++ b/web/app/components/base/icons/src/public/llm/LocalaiText.json @@ -167,4 +167,4 @@ ] }, "name": "LocalaiText" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/llm/Microsoft.json b/web/app/components/base/icons/src/public/llm/Microsoft.json index 692cd25eae..ab2c0522c7 100644 --- a/web/app/components/base/icons/src/public/llm/Microsoft.json +++ b/web/app/components/base/icons/src/public/llm/Microsoft.json @@ -73,4 +73,4 @@ ] }, "name": "Microsoft" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/llm/OpenaiBlack.json b/web/app/components/base/icons/src/public/llm/OpenaiBlack.json index ad722849e7..9f4a9914d3 100644 --- a/web/app/components/base/icons/src/public/llm/OpenaiBlack.json +++ b/web/app/components/base/icons/src/public/llm/OpenaiBlack.json @@ -34,4 +34,4 @@ ] }, "name": "OpenaiBlack" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/llm/OpenaiBlue.json b/web/app/components/base/icons/src/public/llm/OpenaiBlue.json index 60b3fc6cf8..5c716f7cdd 100644 --- a/web/app/components/base/icons/src/public/llm/OpenaiBlue.json +++ b/web/app/components/base/icons/src/public/llm/OpenaiBlue.json @@ -34,4 +34,4 @@ ] }, "name": "OpenaiBlue" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/llm/OpenaiGreen.json b/web/app/components/base/icons/src/public/llm/OpenaiGreen.json index 9ca36b6aa4..8980e858ca 100644 --- a/web/app/components/base/icons/src/public/llm/OpenaiGreen.json +++ b/web/app/components/base/icons/src/public/llm/OpenaiGreen.json @@ -34,4 +34,4 @@ ] }, "name": "OpenaiGreen" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/llm/OpenaiTeal.json b/web/app/components/base/icons/src/public/llm/OpenaiTeal.json new file mode 100644 index 0000000000..1e85c161be --- /dev/null +++ b/web/app/components/base/icons/src/public/llm/OpenaiTeal.json @@ -0,0 +1,37 @@ +{ + "icon": { + "type": "element", + "isRootNode": true, + "name": "svg", + "attributes": { + "width": "24", + "height": "24", + "viewBox": "0 0 24 24", + "fill": "none", + "xmlns": "http://www.w3.org/2000/svg" + }, + "children": [ + { + "type": "element", + "name": "rect", + "attributes": { + "width": "24", + "height": "24", + "rx": "6", + "fill": "#009688" + }, + "children": [] + }, + { + "type": "element", + "name": "path", + "attributes": { + "d": "M19.7758 11.5959C19.9546 11.9948 20.0681 12.4213 20.1145 12.8563C20.1592 13.2913 20.1369 13.7315 20.044 14.1596C19.9529 14.5878 19.7947 14.9987 19.5746 15.377C19.4302 15.6298 19.2599 15.867 19.0639 16.0854C18.8696 16.3021 18.653 16.4981 18.4174 16.67C18.1801 16.842 17.9274 16.9864 17.6591 17.105C17.3926 17.222 17.1141 17.3114 16.8286 17.3698C16.6945 17.7859 16.4951 18.1797 16.2371 18.5339C15.9809 18.8881 15.6697 19.1993 15.3155 19.4555C14.9613 19.7134 14.5693 19.9129 14.1532 20.047C13.7371 20.1829 13.302 20.2499 12.8636 20.2499C12.573 20.2516 12.2807 20.2207 11.9953 20.1622C11.7116 20.102 11.433 20.0109 11.1665 19.8923C10.9 19.7736 10.6472 19.6258 10.4116 19.4538C10.1778 19.2819 9.96115 19.0841 9.76857 18.8658C9.33871 18.9586 8.89853 18.981 8.46351 18.9363C8.02849 18.8898 7.60207 18.7763 7.20143 18.5975C6.80252 18.4204 6.43284 18.1797 6.10786 17.8857C5.78289 17.5916 5.50606 17.2478 5.28769 16.8695C5.14153 16.6167 5.02117 16.3502 4.93004 16.0734C4.83891 15.7965 4.77873 15.5111 4.74778 15.2205C4.71683 14.9317 4.71855 14.6393 4.7495 14.3488C4.78045 14.0599 4.84407 13.7745 4.9352 13.4976C4.64289 13.1727 4.40217 12.803 4.22335 12.4041C4.04624 12.0034 3.93104 11.5787 3.88634 11.1437C3.83991 10.7087 3.86398 10.2685 3.95511 9.84036C4.04624 9.41222 4.20443 9.00127 4.42452 8.62299C4.56896 8.37023 4.73918 8.13123 4.93348 7.91458C5.12778 7.69793 5.34615 7.50191 5.58171 7.32997C5.81728 7.15802 6.07176 7.01187 6.33827 6.89495C6.6065 6.7763 6.88506 6.68861 7.17048 6.63015C7.3046 6.21232 7.50406 5.82029 7.76026 5.46608C8.01817 5.11188 8.32939 4.80066 8.6836 4.54274C9.03781 4.28654 9.42984 4.08708 9.84595 3.95125C10.2621 3.81713 10.6971 3.74835 11.1355 3.75007C11.4261 3.74835 11.7184 3.77758 12.0039 3.83776C12.2893 3.89794 12.5678 3.98736 12.8344 4.106C13.1009 4.22636 13.3536 4.37251 13.5892 4.54446C13.8248 4.71812 14.0414 4.91414 14.234 5.13251C14.6621 5.04138 15.1023 5.01903 15.5373 5.06373C15.9723 5.10844 16.3971 5.22364 16.7977 5.40074C17.1966 5.57957 17.5663 5.81857 17.8913 6.1126C18.2162 6.4049 18.4931 6.74707 18.7114 7.12707C18.8576 7.37811 18.9779 7.64463 19.0691 7.92318C19.1602 8.20001 19.2221 8.48544 19.2513 8.77602C19.2823 9.06661 19.2823 9.35892 19.2496 9.64951C19.2187 9.94009 19.155 10.2255 19.0639 10.5024C19.3579 10.8273 19.5969 11.1953 19.7758 11.5959ZM14.0466 18.9363C14.4214 18.7815 14.7619 18.5528 15.049 18.2657C15.3362 17.9785 15.5648 17.6381 15.7196 17.2615C15.8743 16.8867 15.9552 16.4843 15.9552 16.0785V12.2442C15.954 12.2407 15.9529 12.2367 15.9517 12.2321C15.9506 12.2287 15.9488 12.2252 15.9466 12.2218C15.9443 12.2184 15.9414 12.2155 15.938 12.2132C15.9345 12.2098 15.9311 12.2075 15.9276 12.2063L14.54 11.4051V16.0373C14.54 16.0837 14.5332 16.1318 14.5211 16.1765C14.5091 16.223 14.4919 16.2659 14.4678 16.3072C14.4438 16.3485 14.4162 16.3863 14.3819 16.419C14.3484 16.4523 14.3109 16.4812 14.2701 16.505L10.9842 18.4015C10.9567 18.4187 10.9103 18.4428 10.8862 18.4565C11.0221 18.5717 11.1699 18.6732 11.3247 18.7626C11.4811 18.852 11.6428 18.9277 11.8113 18.9896C11.9798 19.0497 12.1535 19.0962 12.3288 19.1271C12.5059 19.1581 12.6848 19.1735 12.8636 19.1735C13.2694 19.1735 13.6717 19.0927 14.0466 18.9363ZM6.22135 16.333C6.42596 16.6855 6.69592 16.9916 7.01745 17.2392C7.34071 17.4868 7.70695 17.6673 8.09899 17.7722C8.49102 17.8771 8.90025 17.9046 9.3026 17.8513C9.70495 17.798 10.0918 17.6673 10.4443 17.4644L13.7663 15.5472L13.7749 15.5386C13.7772 15.5363 13.7789 15.5329 13.78 15.5283C13.7823 15.5249 13.7841 15.5214 13.7852 15.518V13.9017L9.77545 16.2212C9.73418 16.2453 9.6912 16.2625 9.64649 16.2763C9.60007 16.2883 9.55364 16.2935 9.5055 16.2935C9.45907 16.2935 9.41265 16.2883 9.36622 16.2763C9.32152 16.2625 9.27681 16.2453 9.23554 16.2212L5.94967 14.323C5.92044 14.3058 5.87746 14.28 5.85339 14.2645C5.82244 14.4416 5.80696 14.6204 5.80696 14.7993C5.80696 14.9781 5.82415 15.1569 5.85511 15.334C5.88605 15.5094 5.9342 15.6831 5.99438 15.8516C6.05628 16.0201 6.13194 16.1817 6.22135 16.3364V16.333ZM5.35818 9.1629C5.15529 9.51539 5.02461 9.90398 4.97131 10.3063C4.918 10.7087 4.94552 11.1162 5.0504 11.51C5.15529 11.902 5.33583 12.2682 5.58343 12.5915C5.83103 12.913 6.13881 13.183 6.48958 13.3859L9.80984 15.3048C9.81328 15.3059 9.81729 15.3071 9.82188 15.3082H9.83391C9.8385 15.3082 9.84251 15.3071 9.84595 15.3048C9.84939 15.3036 9.85283 15.3019 9.85627 15.2996L11.249 14.4949L7.23926 12.1805C7.19971 12.1565 7.16189 12.1272 7.1275 12.0946C7.09418 12.0611 7.06529 12.0236 7.04153 11.9828C7.01917 11.9415 7.00026 11.8985 6.98822 11.8521C6.97619 11.8074 6.96931 11.761 6.97103 11.7128V7.80797C6.80252 7.86987 6.63917 7.94553 6.48442 8.03494C6.32967 8.12607 6.18352 8.22924 6.04596 8.34444C5.91013 8.45965 5.78289 8.58688 5.66769 8.72444C5.55248 8.86028 5.45103 9.00815 5.36162 9.1629H5.35818ZM16.7633 11.8177C16.8046 11.8418 16.8424 11.8693 16.8768 11.9037C16.9094 11.9364 16.9387 11.9742 16.9628 12.0155C16.9851 12.0567 17.004 12.1014 17.0161 12.1461C17.0264 12.1926 17.0332 12.239 17.0315 12.2871V16.192C17.5835 15.9891 18.0649 15.6332 18.4208 15.1655C18.7785 14.6978 18.9934 14.139 19.0433 13.5544C19.0931 12.9698 18.9762 12.3817 18.7046 11.8607C18.4329 11.3397 18.0185 10.9064 17.5095 10.6141L14.1893 8.69521C14.1858 8.69406 14.1818 8.69292 14.1772 8.69177H14.1652C14.1618 8.69292 14.1578 8.69406 14.1532 8.69521C14.1497 8.69636 14.1463 8.69808 14.1429 8.70037L12.757 9.50163L16.7667 11.8177H16.7633ZM18.1475 9.7372H18.1457V9.73892L18.1475 9.7372ZM18.1457 9.73548C18.2455 9.15774 18.1784 8.56281 17.9514 8.02119C17.7262 7.47956 17.3496 7.01359 16.8682 6.67658C16.3867 6.34128 15.8193 6.1487 15.233 6.12291C14.6449 6.09884 14.0638 6.24155 13.5548 6.53386L10.2345 8.45105C10.2311 8.45334 10.2282 8.45621 10.2259 8.45965L10.2191 8.46996C10.2179 8.4734 10.2168 8.47741 10.2156 8.482C10.2145 8.48544 10.2139 8.48945 10.2139 8.49403V10.0966L14.2237 7.78046C14.2649 7.75639 14.3096 7.7392 14.3543 7.72544C14.4008 7.7134 14.4472 7.70825 14.4936 7.70825C14.5418 7.70825 14.5882 7.7134 14.6346 7.72544C14.6793 7.7392 14.7223 7.75639 14.7636 7.78046L18.0494 9.67874C18.0787 9.69593 18.1217 9.72 18.1457 9.73548ZM9.45735 7.96101C9.45735 7.91458 9.46423 7.86816 9.47627 7.82173C9.4883 7.77702 9.5055 7.73232 9.52957 7.69105C9.55364 7.6515 9.58115 7.61368 9.61554 7.57929C9.64821 7.54662 9.68604 7.51739 9.72731 7.49503L13.0132 5.59848C13.0441 5.57957 13.0871 5.55549 13.1112 5.54346C12.6607 5.1669 12.1105 4.92618 11.5276 4.85224C10.9447 4.77658 10.3532 4.86943 9.82188 5.11875C9.28885 5.36807 8.83835 5.76527 8.52369 6.26047C8.20903 6.75739 8.04224 7.33169 8.04224 7.91974V11.7541C8.04339 11.7587 8.04454 11.7627 8.04568 11.7661C8.04683 11.7696 8.04855 11.773 8.05084 11.7765C8.05313 11.7799 8.056 11.7833 8.05944 11.7868C8.06173 11.7891 8.06517 11.7914 8.06976 11.7937L9.45735 12.5949V7.96101ZM10.2105 13.0282L11.997 14.0599L13.7835 13.0282V10.9666L11.9987 9.93493L10.2122 10.9666L10.2105 13.0282Z", + "fill": "white" + }, + "children": [] + } + ] + }, + "name": "OpenaiTeal" +} diff --git a/web/app/components/base/icons/src/public/llm/OpenaiTeal.tsx b/web/app/components/base/icons/src/public/llm/OpenaiTeal.tsx new file mode 100644 index 0000000000..ab50b42a1e --- /dev/null +++ b/web/app/components/base/icons/src/public/llm/OpenaiTeal.tsx @@ -0,0 +1,20 @@ +// GENERATE BY script +// DON NOT EDIT IT MANUALLY + +import * as React from 'react' +import data from './OpenaiTeal.json' +import IconBase from '@/app/components/base/icons/IconBase' +import type { IconData } from '@/app/components/base/icons/IconBase' + +const Icon = ( + { + ref, + ...props + }: React.SVGProps<SVGSVGElement> & { + ref?: React.RefObject<React.MutableRefObject<HTMLOrSVGElement>>; + }, +) => <IconBase {...props} ref={ref} data={data as IconData} /> + +Icon.displayName = 'OpenaiTeal' + +export default Icon diff --git a/web/app/components/base/icons/src/public/llm/OpenaiText.json b/web/app/components/base/icons/src/public/llm/OpenaiText.json index 469aacf9d3..f5fc3de6b9 100644 --- a/web/app/components/base/icons/src/public/llm/OpenaiText.json +++ b/web/app/components/base/icons/src/public/llm/OpenaiText.json @@ -74,4 +74,4 @@ ] }, "name": "OpenaiText" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/llm/OpenaiTransparent.json b/web/app/components/base/icons/src/public/llm/OpenaiTransparent.json index 00a410dce0..13b9cb4905 100644 --- a/web/app/components/base/icons/src/public/llm/OpenaiTransparent.json +++ b/web/app/components/base/icons/src/public/llm/OpenaiTransparent.json @@ -23,4 +23,4 @@ ] }, "name": "OpenaiTransparent" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/llm/OpenaiViolet.json b/web/app/components/base/icons/src/public/llm/OpenaiViolet.json index 927699bec2..efff2feacf 100644 --- a/web/app/components/base/icons/src/public/llm/OpenaiViolet.json +++ b/web/app/components/base/icons/src/public/llm/OpenaiViolet.json @@ -34,4 +34,4 @@ ] }, "name": "OpenaiViolet" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/llm/OpenaiYellow.json b/web/app/components/base/icons/src/public/llm/OpenaiYellow.json new file mode 100644 index 0000000000..d0a4f10744 --- /dev/null +++ b/web/app/components/base/icons/src/public/llm/OpenaiYellow.json @@ -0,0 +1,37 @@ +{ + "icon": { + "type": "element", + "isRootNode": true, + "name": "svg", + "attributes": { + "width": "24", + "height": "24", + "viewBox": "0 0 24 24", + "fill": "none", + "xmlns": "http://www.w3.org/2000/svg" + }, + "children": [ + { + "type": "element", + "name": "rect", + "attributes": { + "width": "24", + "height": "24", + "rx": "6", + "fill": "#FAB005" + }, + "children": [] + }, + { + "type": "element", + "name": "path", + "attributes": { + "d": "M19.7758 11.5959C19.9546 11.9948 20.0681 12.4213 20.1145 12.8563C20.1592 13.2913 20.1369 13.7315 20.044 14.1596C19.9529 14.5878 19.7947 14.9987 19.5746 15.377C19.4302 15.6298 19.2599 15.867 19.0639 16.0854C18.8696 16.3021 18.653 16.4981 18.4174 16.67C18.1801 16.842 17.9274 16.9864 17.6591 17.105C17.3926 17.222 17.1141 17.3114 16.8286 17.3698C16.6945 17.7859 16.4951 18.1797 16.2371 18.5339C15.9809 18.8881 15.6697 19.1993 15.3155 19.4555C14.9613 19.7134 14.5693 19.9129 14.1532 20.047C13.7371 20.1829 13.302 20.2499 12.8636 20.2499C12.573 20.2516 12.2807 20.2207 11.9953 20.1622C11.7116 20.102 11.433 20.0109 11.1665 19.8923C10.9 19.7736 10.6472 19.6258 10.4116 19.4538C10.1778 19.2819 9.96115 19.0841 9.76857 18.8658C9.33871 18.9586 8.89853 18.981 8.46351 18.9363C8.02849 18.8898 7.60207 18.7763 7.20143 18.5975C6.80252 18.4204 6.43284 18.1797 6.10786 17.8857C5.78289 17.5916 5.50606 17.2478 5.28769 16.8695C5.14153 16.6167 5.02117 16.3502 4.93004 16.0734C4.83891 15.7965 4.77873 15.5111 4.74778 15.2205C4.71683 14.9317 4.71855 14.6393 4.7495 14.3488C4.78045 14.0599 4.84407 13.7745 4.9352 13.4976C4.64289 13.1727 4.40217 12.803 4.22335 12.4041C4.04624 12.0034 3.93104 11.5787 3.88634 11.1437C3.83991 10.7087 3.86398 10.2685 3.95511 9.84036C4.04624 9.41222 4.20443 9.00127 4.42452 8.62299C4.56896 8.37023 4.73918 8.13123 4.93348 7.91458C5.12778 7.69793 5.34615 7.50191 5.58171 7.32997C5.81728 7.15802 6.07176 7.01187 6.33827 6.89495C6.6065 6.7763 6.88506 6.68861 7.17048 6.63015C7.3046 6.21232 7.50406 5.82029 7.76026 5.46608C8.01817 5.11188 8.32939 4.80066 8.6836 4.54274C9.03781 4.28654 9.42984 4.08708 9.84595 3.95125C10.2621 3.81713 10.6971 3.74835 11.1355 3.75007C11.4261 3.74835 11.7184 3.77758 12.0039 3.83776C12.2893 3.89794 12.5678 3.98736 12.8344 4.106C13.1009 4.22636 13.3536 4.37251 13.5892 4.54446C13.8248 4.71812 14.0414 4.91414 14.234 5.13251C14.6621 5.04138 15.1023 5.01903 15.5373 5.06373C15.9723 5.10844 16.3971 5.22364 16.7977 5.40074C17.1966 5.57957 17.5663 5.81857 17.8913 6.1126C18.2162 6.4049 18.4931 6.74707 18.7114 7.12707C18.8576 7.37811 18.9779 7.64463 19.0691 7.92318C19.1602 8.20001 19.2221 8.48544 19.2513 8.77602C19.2823 9.06661 19.2823 9.35892 19.2496 9.64951C19.2187 9.94009 19.155 10.2255 19.0639 10.5024C19.3579 10.8273 19.5969 11.1953 19.7758 11.5959ZM14.0466 18.9363C14.4214 18.7815 14.7619 18.5528 15.049 18.2657C15.3362 17.9785 15.5648 17.6381 15.7196 17.2615C15.8743 16.8867 15.9552 16.4843 15.9552 16.0785V12.2442C15.954 12.2407 15.9529 12.2367 15.9517 12.2321C15.9506 12.2287 15.9488 12.2252 15.9466 12.2218C15.9443 12.2184 15.9414 12.2155 15.938 12.2132C15.9345 12.2098 15.9311 12.2075 15.9276 12.2063L14.54 11.4051V16.0373C14.54 16.0837 14.5332 16.1318 14.5211 16.1765C14.5091 16.223 14.4919 16.2659 14.4678 16.3072C14.4438 16.3485 14.4162 16.3863 14.3819 16.419C14.3484 16.4523 14.3109 16.4812 14.2701 16.505L10.9842 18.4015C10.9567 18.4187 10.9103 18.4428 10.8862 18.4565C11.0221 18.5717 11.1699 18.6732 11.3247 18.7626C11.4811 18.852 11.6428 18.9277 11.8113 18.9896C11.9798 19.0497 12.1535 19.0962 12.3288 19.1271C12.5059 19.1581 12.6848 19.1735 12.8636 19.1735C13.2694 19.1735 13.6717 19.0927 14.0466 18.9363ZM6.22135 16.333C6.42596 16.6855 6.69592 16.9916 7.01745 17.2392C7.34071 17.4868 7.70695 17.6673 8.09899 17.7722C8.49102 17.8771 8.90025 17.9046 9.3026 17.8513C9.70495 17.798 10.0918 17.6673 10.4443 17.4644L13.7663 15.5472L13.7749 15.5386C13.7772 15.5363 13.7789 15.5329 13.78 15.5283C13.7823 15.5249 13.7841 15.5214 13.7852 15.518V13.9017L9.77545 16.2212C9.73418 16.2453 9.6912 16.2625 9.64649 16.2763C9.60007 16.2883 9.55364 16.2935 9.5055 16.2935C9.45907 16.2935 9.41265 16.2883 9.36622 16.2763C9.32152 16.2625 9.27681 16.2453 9.23554 16.2212L5.94967 14.323C5.92044 14.3058 5.87746 14.28 5.85339 14.2645C5.82244 14.4416 5.80696 14.6204 5.80696 14.7993C5.80696 14.9781 5.82415 15.1569 5.85511 15.334C5.88605 15.5094 5.9342 15.6831 5.99438 15.8516C6.05628 16.0201 6.13194 16.1817 6.22135 16.3364V16.333ZM5.35818 9.1629C5.15529 9.51539 5.02461 9.90398 4.97131 10.3063C4.918 10.7087 4.94552 11.1162 5.0504 11.51C5.15529 11.902 5.33583 12.2682 5.58343 12.5915C5.83103 12.913 6.13881 13.183 6.48958 13.3859L9.80984 15.3048C9.81328 15.3059 9.81729 15.3071 9.82188 15.3082H9.83391C9.8385 15.3082 9.84251 15.3071 9.84595 15.3048C9.84939 15.3036 9.85283 15.3019 9.85627 15.2996L11.249 14.4949L7.23926 12.1805C7.19971 12.1565 7.16189 12.1272 7.1275 12.0946C7.09418 12.0611 7.06529 12.0236 7.04153 11.9828C7.01917 11.9415 7.00026 11.8985 6.98822 11.8521C6.97619 11.8074 6.96931 11.761 6.97103 11.7128V7.80797C6.80252 7.86987 6.63917 7.94553 6.48442 8.03494C6.32967 8.12607 6.18352 8.22924 6.04596 8.34444C5.91013 8.45965 5.78289 8.58688 5.66769 8.72444C5.55248 8.86028 5.45103 9.00815 5.36162 9.1629H5.35818ZM16.7633 11.8177C16.8046 11.8418 16.8424 11.8693 16.8768 11.9037C16.9094 11.9364 16.9387 11.9742 16.9628 12.0155C16.9851 12.0567 17.004 12.1014 17.0161 12.1461C17.0264 12.1926 17.0332 12.239 17.0315 12.2871V16.192C17.5835 15.9891 18.0649 15.6332 18.4208 15.1655C18.7785 14.6978 18.9934 14.139 19.0433 13.5544C19.0931 12.9698 18.9762 12.3817 18.7046 11.8607C18.4329 11.3397 18.0185 10.9064 17.5095 10.6141L14.1893 8.69521C14.1858 8.69406 14.1818 8.69292 14.1772 8.69177H14.1652C14.1618 8.69292 14.1578 8.69406 14.1532 8.69521C14.1497 8.69636 14.1463 8.69808 14.1429 8.70037L12.757 9.50163L16.7667 11.8177H16.7633ZM18.1475 9.7372H18.1457V9.73892L18.1475 9.7372ZM18.1457 9.73548C18.2455 9.15774 18.1784 8.56281 17.9514 8.02119C17.7262 7.47956 17.3496 7.01359 16.8682 6.67658C16.3867 6.34128 15.8193 6.1487 15.233 6.12291C14.6449 6.09884 14.0638 6.24155 13.5548 6.53386L10.2345 8.45105C10.2311 8.45334 10.2282 8.45621 10.2259 8.45965L10.2191 8.46996C10.2179 8.4734 10.2168 8.47741 10.2156 8.482C10.2145 8.48544 10.2139 8.48945 10.2139 8.49403V10.0966L14.2237 7.78046C14.2649 7.75639 14.3096 7.7392 14.3543 7.72544C14.4008 7.7134 14.4472 7.70825 14.4936 7.70825C14.5418 7.70825 14.5882 7.7134 14.6346 7.72544C14.6793 7.7392 14.7223 7.75639 14.7636 7.78046L18.0494 9.67874C18.0787 9.69593 18.1217 9.72 18.1457 9.73548ZM9.45735 7.96101C9.45735 7.91458 9.46423 7.86816 9.47627 7.82173C9.4883 7.77702 9.5055 7.73232 9.52957 7.69105C9.55364 7.6515 9.58115 7.61368 9.61554 7.57929C9.64821 7.54662 9.68604 7.51739 9.72731 7.49503L13.0132 5.59848C13.0441 5.57957 13.0871 5.55549 13.1112 5.54346C12.6607 5.1669 12.1105 4.92618 11.5276 4.85224C10.9447 4.77658 10.3532 4.86943 9.82188 5.11875C9.28885 5.36807 8.83835 5.76527 8.52369 6.26047C8.20903 6.75739 8.04224 7.33169 8.04224 7.91974V11.7541C8.04339 11.7587 8.04454 11.7627 8.04568 11.7661C8.04683 11.7696 8.04855 11.773 8.05084 11.7765C8.05313 11.7799 8.056 11.7833 8.05944 11.7868C8.06173 11.7891 8.06517 11.7914 8.06976 11.7937L9.45735 12.5949V7.96101ZM10.2105 13.0282L11.997 14.0599L13.7835 13.0282V10.9666L11.9987 9.93493L10.2122 10.9666L10.2105 13.0282Z", + "fill": "white" + }, + "children": [] + } + ] + }, + "name": "OpenaiYellow" +} diff --git a/web/app/components/base/icons/src/public/llm/OpenaiYellow.tsx b/web/app/components/base/icons/src/public/llm/OpenaiYellow.tsx new file mode 100644 index 0000000000..77dac7e322 --- /dev/null +++ b/web/app/components/base/icons/src/public/llm/OpenaiYellow.tsx @@ -0,0 +1,20 @@ +// GENERATE BY script +// DON NOT EDIT IT MANUALLY + +import * as React from 'react' +import data from './OpenaiYellow.json' +import IconBase from '@/app/components/base/icons/IconBase' +import type { IconData } from '@/app/components/base/icons/IconBase' + +const Icon = ( + { + ref, + ...props + }: React.SVGProps<SVGSVGElement> & { + ref?: React.RefObject<React.MutableRefObject<HTMLOrSVGElement>>; + }, +) => <IconBase {...props} ref={ref} data={data as IconData} /> + +Icon.displayName = 'OpenaiYellow' + +export default Icon diff --git a/web/app/components/base/icons/src/public/llm/Openllm.json b/web/app/components/base/icons/src/public/llm/Openllm.json index 1c71fa9c6f..93eec11dfe 100644 --- a/web/app/components/base/icons/src/public/llm/Openllm.json +++ b/web/app/components/base/icons/src/public/llm/Openllm.json @@ -80,4 +80,4 @@ ] }, "name": "Openllm" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/llm/OpenllmText.json b/web/app/components/base/icons/src/public/llm/OpenllmText.json index ad5179e9ee..d5705de10e 100644 --- a/web/app/components/base/icons/src/public/llm/OpenllmText.json +++ b/web/app/components/base/icons/src/public/llm/OpenllmText.json @@ -140,4 +140,4 @@ ] }, "name": "OpenllmText" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/llm/Replicate.json b/web/app/components/base/icons/src/public/llm/Replicate.json index 089d111eef..303c239193 100644 --- a/web/app/components/base/icons/src/public/llm/Replicate.json +++ b/web/app/components/base/icons/src/public/llm/Replicate.json @@ -36,4 +36,4 @@ ] }, "name": "Replicate" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/llm/ReplicateText.json b/web/app/components/base/icons/src/public/llm/ReplicateText.json index c163ccbe74..b2d597c2ed 100644 --- a/web/app/components/base/icons/src/public/llm/ReplicateText.json +++ b/web/app/components/base/icons/src/public/llm/ReplicateText.json @@ -113,4 +113,4 @@ ] }, "name": "ReplicateText" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/llm/XorbitsInference.json b/web/app/components/base/icons/src/public/llm/XorbitsInference.json index fae25b37f9..b2d3b1a072 100644 --- a/web/app/components/base/icons/src/public/llm/XorbitsInference.json +++ b/web/app/components/base/icons/src/public/llm/XorbitsInference.json @@ -173,4 +173,4 @@ ] }, "name": "XorbitsInference" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/llm/XorbitsInferenceText.json b/web/app/components/base/icons/src/public/llm/XorbitsInferenceText.json index b8dac91644..967ee6d6c4 100644 --- a/web/app/components/base/icons/src/public/llm/XorbitsInferenceText.json +++ b/web/app/components/base/icons/src/public/llm/XorbitsInferenceText.json @@ -326,4 +326,4 @@ ] }, "name": "XorbitsInferenceText" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/llm/Zhipuai.json b/web/app/components/base/icons/src/public/llm/Zhipuai.json index 7f93c634d0..87955688a5 100644 --- a/web/app/components/base/icons/src/public/llm/Zhipuai.json +++ b/web/app/components/base/icons/src/public/llm/Zhipuai.json @@ -50,4 +50,4 @@ ] }, "name": "Zhipuai" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/llm/ZhipuaiText.json b/web/app/components/base/icons/src/public/llm/ZhipuaiText.json index 455a60695c..12eb65a53a 100644 --- a/web/app/components/base/icons/src/public/llm/ZhipuaiText.json +++ b/web/app/components/base/icons/src/public/llm/ZhipuaiText.json @@ -41,4 +41,4 @@ ] }, "name": "ZhipuaiText" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/llm/ZhipuaiTextCn.json b/web/app/components/base/icons/src/public/llm/ZhipuaiTextCn.json index 6002e07f6f..c5b1755f0c 100644 --- a/web/app/components/base/icons/src/public/llm/ZhipuaiTextCn.json +++ b/web/app/components/base/icons/src/public/llm/ZhipuaiTextCn.json @@ -59,4 +59,4 @@ ] }, "name": "ZhipuaiTextCn" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/llm/index.ts b/web/app/components/base/icons/src/public/llm/index.ts index cc9b531ebf..fa4a1bdd10 100644 --- a/web/app/components/base/icons/src/public/llm/index.ts +++ b/web/app/components/base/icons/src/public/llm/index.ts @@ -28,9 +28,11 @@ export { default as Microsoft } from './Microsoft' export { default as OpenaiBlack } from './OpenaiBlack' export { default as OpenaiBlue } from './OpenaiBlue' export { default as OpenaiGreen } from './OpenaiGreen' +export { default as OpenaiTeal } from './OpenaiTeal' export { default as OpenaiText } from './OpenaiText' export { default as OpenaiTransparent } from './OpenaiTransparent' export { default as OpenaiViolet } from './OpenaiViolet' +export { default as OpenaiYellow } from './OpenaiYellow' export { default as OpenllmText } from './OpenllmText' export { default as Openllm } from './Openllm' export { default as ReplicateText } from './ReplicateText' diff --git a/web/app/components/base/icons/src/public/model/Checked.json b/web/app/components/base/icons/src/public/model/Checked.json index f8ea944818..7e96db728f 100644 --- a/web/app/components/base/icons/src/public/model/Checked.json +++ b/web/app/components/base/icons/src/public/model/Checked.json @@ -26,4 +26,4 @@ ] }, "name": "Checked" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/other/DefaultToolIcon.json b/web/app/components/base/icons/src/public/other/DefaultToolIcon.json index 32412e8d01..32786d2281 100644 --- a/web/app/components/base/icons/src/public/other/DefaultToolIcon.json +++ b/web/app/components/base/icons/src/public/other/DefaultToolIcon.json @@ -78,4 +78,4 @@ ] }, "name": "DefaultToolIcon" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/other/Icon3Dots.json b/web/app/components/base/icons/src/public/other/Icon3Dots.json index 9c6d232839..b59b293cec 100644 --- a/web/app/components/base/icons/src/public/other/Icon3Dots.json +++ b/web/app/components/base/icons/src/public/other/Icon3Dots.json @@ -26,4 +26,4 @@ ] }, "name": "Icon3Dots" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/other/Message3Fill.json b/web/app/components/base/icons/src/public/other/Message3Fill.json index 250ce5cdea..ae84890abc 100644 --- a/web/app/components/base/icons/src/public/other/Message3Fill.json +++ b/web/app/components/base/icons/src/public/other/Message3Fill.json @@ -170,4 +170,4 @@ ] }, "name": "Message3Fill" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/other/RowStruct.json b/web/app/components/base/icons/src/public/other/RowStruct.json index 0d1ef43f4f..49ef71753c 100644 --- a/web/app/components/base/icons/src/public/other/RowStruct.json +++ b/web/app/components/base/icons/src/public/other/RowStruct.json @@ -53,4 +53,4 @@ ] }, "name": "RowStruct" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/plugins/Google.json b/web/app/components/base/icons/src/public/plugins/Google.json index 6f04dddb9b..198050e04c 100644 --- a/web/app/components/base/icons/src/public/plugins/Google.json +++ b/web/app/components/base/icons/src/public/plugins/Google.json @@ -50,4 +50,4 @@ ] }, "name": "Google" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/plugins/PartnerDark.json b/web/app/components/base/icons/src/public/plugins/PartnerDark.json index 37135d4b21..af3f2083e4 100644 --- a/web/app/components/base/icons/src/public/plugins/PartnerDark.json +++ b/web/app/components/base/icons/src/public/plugins/PartnerDark.json @@ -444,4 +444,4 @@ ] }, "name": "PartnerDark" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/plugins/PartnerLight.json b/web/app/components/base/icons/src/public/plugins/PartnerLight.json index f726fca7d8..3d7391bcaa 100644 --- a/web/app/components/base/icons/src/public/plugins/PartnerLight.json +++ b/web/app/components/base/icons/src/public/plugins/PartnerLight.json @@ -443,4 +443,4 @@ ] }, "name": "PartnerLight" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/plugins/VerifiedDark.json b/web/app/components/base/icons/src/public/plugins/VerifiedDark.json index 4da3a28a74..ed228262b5 100644 --- a/web/app/components/base/icons/src/public/plugins/VerifiedDark.json +++ b/web/app/components/base/icons/src/public/plugins/VerifiedDark.json @@ -454,4 +454,4 @@ ] }, "name": "VerifiedDark" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/plugins/VerifiedLight.json b/web/app/components/base/icons/src/public/plugins/VerifiedLight.json index b41bdb72e1..b31fe655b3 100644 --- a/web/app/components/base/icons/src/public/plugins/VerifiedLight.json +++ b/web/app/components/base/icons/src/public/plugins/VerifiedLight.json @@ -453,4 +453,4 @@ ] }, "name": "VerifiedLight" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/plugins/WebReader.json b/web/app/components/base/icons/src/public/plugins/WebReader.json index 42ec3d9e78..58c828309c 100644 --- a/web/app/components/base/icons/src/public/plugins/WebReader.json +++ b/web/app/components/base/icons/src/public/plugins/WebReader.json @@ -36,4 +36,4 @@ ] }, "name": "WebReader" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/plugins/Wikipedia.json b/web/app/components/base/icons/src/public/plugins/Wikipedia.json index 7a16433be7..af2d505c85 100644 --- a/web/app/components/base/icons/src/public/plugins/Wikipedia.json +++ b/web/app/components/base/icons/src/public/plugins/Wikipedia.json @@ -23,4 +23,4 @@ ] }, "name": "Wikipedia" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/thought/DataSet.json b/web/app/components/base/icons/src/public/thought/DataSet.json index 55952fe9d2..5be61dac9d 100644 --- a/web/app/components/base/icons/src/public/thought/DataSet.json +++ b/web/app/components/base/icons/src/public/thought/DataSet.json @@ -61,4 +61,4 @@ ] }, "name": "DataSet" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/thought/Loading.json b/web/app/components/base/icons/src/public/thought/Loading.json index f19a3b1009..23e68662c4 100644 --- a/web/app/components/base/icons/src/public/thought/Loading.json +++ b/web/app/components/base/icons/src/public/thought/Loading.json @@ -61,4 +61,4 @@ ] }, "name": "Loading" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/thought/Search.json b/web/app/components/base/icons/src/public/thought/Search.json index 9213419bbc..1ad8876bcc 100644 --- a/web/app/components/base/icons/src/public/thought/Search.json +++ b/web/app/components/base/icons/src/public/thought/Search.json @@ -61,4 +61,4 @@ ] }, "name": "Search" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/thought/ThoughtList.json b/web/app/components/base/icons/src/public/thought/ThoughtList.json index 8b97633444..d5e13c339f 100644 --- a/web/app/components/base/icons/src/public/thought/ThoughtList.json +++ b/web/app/components/base/icons/src/public/thought/ThoughtList.json @@ -80,4 +80,4 @@ ] }, "name": "ThoughtList" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/thought/WebReader.json b/web/app/components/base/icons/src/public/thought/WebReader.json index ecf85d9ec9..ba2bc485f0 100644 --- a/web/app/components/base/icons/src/public/thought/WebReader.json +++ b/web/app/components/base/icons/src/public/thought/WebReader.json @@ -61,4 +61,4 @@ ] }, "name": "WebReader" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/tracing/LangfuseIcon.json b/web/app/components/base/icons/src/public/tracing/LangfuseIcon.json index ab0b8fbc1c..c2c8a73112 100644 --- a/web/app/components/base/icons/src/public/tracing/LangfuseIcon.json +++ b/web/app/components/base/icons/src/public/tracing/LangfuseIcon.json @@ -233,4 +233,4 @@ ] }, "name": "LangfuseIcon" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/tracing/LangfuseIconBig.json b/web/app/components/base/icons/src/public/tracing/LangfuseIconBig.json index 0fee622bd8..8172de6cf6 100644 --- a/web/app/components/base/icons/src/public/tracing/LangfuseIconBig.json +++ b/web/app/components/base/icons/src/public/tracing/LangfuseIconBig.json @@ -233,4 +233,4 @@ ] }, "name": "LangfuseIconBig" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/tracing/LangsmithIcon.json b/web/app/components/base/icons/src/public/tracing/LangsmithIcon.json index 04d480bd20..293c4bfd18 100644 --- a/web/app/components/base/icons/src/public/tracing/LangsmithIcon.json +++ b/web/app/components/base/icons/src/public/tracing/LangsmithIcon.json @@ -185,4 +185,4 @@ ] }, "name": "LangsmithIcon" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/tracing/LangsmithIconBig.json b/web/app/components/base/icons/src/public/tracing/LangsmithIconBig.json index 4aa76acc8d..18b1761e7f 100644 --- a/web/app/components/base/icons/src/public/tracing/LangsmithIconBig.json +++ b/web/app/components/base/icons/src/public/tracing/LangsmithIconBig.json @@ -185,4 +185,4 @@ ] }, "name": "LangsmithIconBig" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/tracing/OpikIcon.json b/web/app/components/base/icons/src/public/tracing/OpikIcon.json index 5bab796c78..c9f3ad7985 100644 --- a/web/app/components/base/icons/src/public/tracing/OpikIcon.json +++ b/web/app/components/base/icons/src/public/tracing/OpikIcon.json @@ -160,4 +160,4 @@ ] }, "name": "OpikIcon" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/tracing/OpikIconBig.json b/web/app/components/base/icons/src/public/tracing/OpikIconBig.json index 1372a92c0e..44e1e2c521 100644 --- a/web/app/components/base/icons/src/public/tracing/OpikIconBig.json +++ b/web/app/components/base/icons/src/public/tracing/OpikIconBig.json @@ -159,4 +159,4 @@ ] }, "name": "OpikIconBig" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/tracing/TracingIcon.json b/web/app/components/base/icons/src/public/tracing/TracingIcon.json index 508b555b0f..2157a08fa3 100644 --- a/web/app/components/base/icons/src/public/tracing/TracingIcon.json +++ b/web/app/components/base/icons/src/public/tracing/TracingIcon.json @@ -44,4 +44,4 @@ ] }, "name": "TracingIcon" -} \ No newline at end of file +} diff --git a/web/app/components/base/icons/src/public/tracing/WeaveIcon.json b/web/app/components/base/icons/src/public/tracing/WeaveIcon.json new file mode 100644 index 0000000000..1a96e70b6e --- /dev/null +++ b/web/app/components/base/icons/src/public/tracing/WeaveIcon.json @@ -0,0 +1,279 @@ +{ + "icon": { + "type": "element", + "isRootNode": true, + "name": "svg", + "attributes": { + "xmlns": "http://www.w3.org/2000/svg", + "xmlns:xlink": "http://www.w3.org/1999/xlink", + "width": "120px", + "height": "16px", + "viewBox": "0 0 120 16", + "version": "1.1" + }, + "children": [ + { + "type": "element", + "name": "g", + "attributes": { + "id": "surface1" + }, + "children": [ + { + "type": "element", + "name": "path", + "attributes": { + "style": " stroke:none;fill-rule:nonzero;fill:rgb(10.196079%,11.372549%,14.117648%);fill-opacity:1;", + "d": "M 20.847656 3.292969 C 20.875 3.292969 20.902344 3.292969 20.933594 3.292969 C 20.949219 3.292969 20.964844 3.292969 20.980469 3.292969 C 21.035156 3.292969 21.089844 3.292969 21.140625 3.292969 C 21.179688 3.292969 21.21875 3.292969 21.253906 3.292969 C 21.359375 3.292969 21.464844 3.292969 21.566406 3.292969 C 21.675781 3.292969 21.78125 3.292969 21.890625 3.292969 C 22.097656 3.292969 22.300781 3.292969 22.507812 3.292969 C 22.738281 3.292969 22.972656 3.292969 23.207031 3.296875 C 23.6875 3.296875 24.167969 3.296875 24.648438 3.296875 C 24.648438 3.519531 24.648438 3.742188 24.648438 3.96875 C 24.113281 4.042969 24.113281 4.042969 23.566406 4.113281 C 23.667969 4.496094 23.769531 4.882812 23.867188 5.265625 C 23.878906 5.308594 23.878906 5.308594 23.890625 5.351562 C 24.128906 6.269531 24.371094 7.183594 24.609375 8.097656 C 24.675781 8.339844 24.738281 8.582031 24.800781 8.824219 C 24.816406 8.878906 24.832031 8.933594 24.84375 8.992188 C 24.867188 9.078125 24.890625 9.167969 24.914062 9.257812 C 24.921875 9.289062 24.933594 9.320312 24.941406 9.355469 C 24.953125 9.398438 24.964844 9.441406 24.976562 9.484375 C 24.984375 9.523438 24.984375 9.523438 24.996094 9.558594 C 25.007812 9.625 25.007812 9.625 25.007812 9.71875 C 25.023438 9.71875 25.039062 9.71875 25.054688 9.71875 C 25.058594 9.707031 25.058594 9.695312 25.0625 9.679688 C 25.097656 9.492188 25.152344 9.3125 25.210938 9.128906 C 25.222656 9.097656 25.234375 9.0625 25.246094 9.027344 C 25.269531 8.953125 25.292969 8.882812 25.316406 8.808594 C 25.355469 8.691406 25.390625 8.574219 25.429688 8.457031 C 25.464844 8.339844 25.503906 8.21875 25.542969 8.097656 C 25.660156 7.738281 25.773438 7.375 25.890625 7.011719 C 25.902344 6.96875 25.917969 6.921875 25.933594 6.875 C 26.226562 5.945312 26.519531 5.019531 26.808594 4.089844 C 26.785156 4.089844 26.765625 4.089844 26.742188 4.085938 C 26.507812 4.074219 26.273438 4.046875 26.042969 4.015625 C 26.007812 4.011719 25.972656 4.007812 25.933594 4.003906 C 25.851562 3.992188 25.765625 3.980469 25.679688 3.96875 C 25.679688 3.746094 25.679688 3.523438 25.679688 3.296875 C 26.175781 3.296875 26.667969 3.296875 27.160156 3.296875 C 27.390625 3.292969 27.621094 3.292969 27.851562 3.292969 C 28.050781 3.292969 28.25 3.292969 28.449219 3.292969 C 28.554688 3.292969 28.660156 3.292969 28.765625 3.292969 C 28.867188 3.292969 28.964844 3.292969 29.066406 3.292969 C 29.101562 3.292969 29.140625 3.292969 29.175781 3.292969 C 29.226562 3.292969 29.273438 3.292969 29.324219 3.292969 C 29.367188 3.292969 29.367188 3.292969 29.410156 3.292969 C 29.472656 3.296875 29.472656 3.296875 29.496094 3.320312 C 29.5 3.367188 29.5 3.417969 29.5 3.464844 C 29.5 3.492188 29.5 3.515625 29.5 3.542969 C 29.496094 3.59375 29.496094 3.59375 29.496094 3.648438 C 29.496094 3.753906 29.496094 3.859375 29.496094 3.96875 C 29.09375 4.015625 28.6875 4.066406 28.273438 4.113281 C 28.679688 5.460938 28.679688 5.460938 29.089844 6.808594 C 29.105469 6.859375 29.121094 6.910156 29.136719 6.960938 C 29.234375 7.292969 29.335938 7.625 29.4375 7.960938 C 29.484375 8.113281 29.53125 8.265625 29.578125 8.417969 C 29.605469 8.507812 29.632812 8.597656 29.660156 8.691406 C 29.878906 9.40625 29.878906 9.40625 29.976562 9.746094 C 30.027344 9.664062 30.046875 9.601562 30.070312 9.507812 C 30.078125 9.484375 30.078125 9.484375 30.085938 9.457031 C 30.101562 9.402344 30.117188 9.34375 30.132812 9.289062 C 30.144531 9.25 30.152344 9.207031 30.164062 9.167969 C 30.1875 9.082031 30.214844 8.992188 30.238281 8.90625 C 30.292969 8.691406 30.351562 8.480469 30.410156 8.269531 C 30.433594 8.191406 30.453125 8.117188 30.472656 8.042969 C 30.621094 7.5 30.769531 6.960938 30.921875 6.421875 C 30.949219 6.324219 30.976562 6.226562 31 6.128906 C 31.066406 5.902344 31.128906 5.675781 31.191406 5.449219 C 31.230469 5.308594 31.269531 5.164062 31.308594 5.023438 C 31.335938 4.925781 31.363281 4.828125 31.390625 4.734375 C 31.402344 4.6875 31.414062 4.640625 31.429688 4.59375 C 31.445312 4.53125 31.464844 4.46875 31.480469 4.40625 C 31.488281 4.386719 31.492188 4.367188 31.496094 4.347656 C 31.515625 4.277344 31.535156 4.207031 31.558594 4.136719 C 31.210938 4.074219 30.855469 4.023438 30.503906 3.96875 C 30.503906 3.746094 30.503906 3.523438 30.503906 3.296875 C 30.878906 3.296875 31.253906 3.296875 31.628906 3.296875 C 31.804688 3.292969 31.976562 3.292969 32.152344 3.292969 C 32.304688 3.292969 32.457031 3.292969 32.605469 3.292969 C 32.6875 3.292969 32.769531 3.292969 32.847656 3.292969 C 32.9375 3.292969 33.027344 3.292969 33.117188 3.292969 C 33.144531 3.292969 33.171875 3.292969 33.199219 3.292969 C 33.222656 3.292969 33.246094 3.292969 33.273438 3.292969 C 33.304688 3.292969 33.304688 3.292969 33.335938 3.292969 C 33.382812 3.296875 33.382812 3.296875 33.40625 3.320312 C 33.410156 3.367188 33.410156 3.414062 33.410156 3.460938 C 33.410156 3.488281 33.410156 3.515625 33.410156 3.542969 C 33.410156 3.574219 33.410156 3.605469 33.410156 3.632812 C 33.410156 3.664062 33.410156 3.695312 33.410156 3.726562 C 33.410156 3.796875 33.410156 3.871094 33.40625 3.945312 C 33.292969 3.964844 33.175781 3.984375 33.0625 4.007812 C 33.023438 4.011719 32.984375 4.019531 32.945312 4.027344 C 32.738281 4.0625 32.535156 4.097656 32.328125 4.113281 C 32.320312 4.144531 32.320312 4.144531 32.3125 4.179688 C 32.238281 4.480469 32.15625 4.78125 32.070312 5.082031 C 32.058594 5.128906 32.042969 5.171875 32.03125 5.21875 C 31.875 5.78125 31.714844 6.347656 31.550781 6.910156 C 31.375 7.535156 31.195312 8.160156 31.019531 8.785156 C 30.992188 8.871094 30.96875 8.957031 30.945312 9.042969 C 30.835938 9.433594 30.722656 9.820312 30.613281 10.210938 C 30.566406 10.378906 30.519531 10.542969 30.472656 10.707031 C 30.445312 10.804688 30.417969 10.902344 30.390625 11 C 30.277344 11.390625 30.167969 11.785156 30.046875 12.175781 C 29.730469 12.175781 29.414062 12.175781 29.089844 12.175781 C 29.03125 12.003906 29.03125 12.003906 28.976562 11.832031 C 28.925781 11.675781 28.878906 11.523438 28.828125 11.367188 C 28.820312 11.347656 28.8125 11.328125 28.808594 11.304688 C 28.632812 10.769531 28.460938 10.230469 28.285156 9.695312 C 28.144531 9.273438 28.007812 8.847656 27.875 8.425781 C 27.695312 7.867188 27.515625 7.308594 27.332031 6.753906 C 27.304688 6.679688 27.28125 6.605469 27.257812 6.53125 C 27.238281 6.476562 27.222656 6.425781 27.207031 6.375 C 27.046875 5.894531 27.046875 5.894531 27.046875 5.796875 C 27.03125 5.796875 27.015625 5.796875 27 5.796875 C 26.996094 5.8125 26.996094 5.828125 26.992188 5.84375 C 26.964844 5.988281 26.925781 6.132812 26.882812 6.273438 C 26.875 6.296875 26.867188 6.316406 26.859375 6.339844 C 26.84375 6.390625 26.828125 6.4375 26.8125 6.488281 C 26.769531 6.625 26.726562 6.761719 26.683594 6.898438 C 26.675781 6.929688 26.664062 6.957031 26.65625 6.988281 C 26.546875 7.328125 26.445312 7.667969 26.339844 8.007812 C 26.316406 8.078125 26.296875 8.144531 26.273438 8.214844 C 26.230469 8.355469 26.1875 8.496094 26.144531 8.636719 C 26.074219 8.863281 26.007812 9.089844 25.9375 9.3125 C 25.933594 9.328125 25.925781 9.347656 25.921875 9.363281 C 25.894531 9.449219 25.871094 9.535156 25.84375 9.617188 C 25.796875 9.769531 25.75 9.921875 25.703125 10.074219 C 25.675781 10.15625 25.652344 10.242188 25.625 10.328125 C 25.613281 10.363281 25.605469 10.394531 25.59375 10.429688 C 25.414062 11.011719 25.234375 11.59375 25.054688 12.175781 C 24.738281 12.175781 24.421875 12.175781 24.097656 12.175781 C 23.816406 11.230469 23.535156 10.285156 23.261719 9.339844 C 23.253906 9.320312 23.25 9.304688 23.246094 9.285156 C 23.195312 9.117188 23.144531 8.949219 23.097656 8.78125 C 22.960938 8.3125 22.824219 7.84375 22.6875 7.375 C 22.664062 7.304688 22.644531 7.234375 22.625 7.164062 C 22.414062 6.449219 22.207031 5.738281 22 5.027344 C 21.976562 4.953125 21.953125 4.878906 21.933594 4.804688 C 21.898438 4.683594 21.859375 4.5625 21.824219 4.441406 C 21.820312 4.421875 21.8125 4.402344 21.808594 4.382812 C 21.796875 4.347656 21.785156 4.3125 21.777344 4.28125 C 21.753906 4.203125 21.742188 4.148438 21.742188 4.066406 C 21.726562 4.066406 21.710938 4.0625 21.691406 4.0625 C 21.382812 4.042969 21.070312 4.003906 20.761719 3.96875 C 20.757812 3.863281 20.757812 3.753906 20.757812 3.648438 C 20.757812 3.617188 20.757812 3.585938 20.757812 3.554688 C 20.757812 3.523438 20.757812 3.496094 20.757812 3.464844 C 20.757812 3.4375 20.757812 3.410156 20.757812 3.382812 C 20.761719 3.296875 20.761719 3.296875 20.847656 3.292969 Z M 20.847656 3.292969 " + }, + "children": [] + }, + { + "type": "element", + "name": "path", + "attributes": { + "style": " stroke:none;fill-rule:nonzero;fill:rgb(10.196079%,11.372549%,14.117648%);fill-opacity:1;", + "d": "M 82.488281 3.25 C 83.046875 3.246094 83.605469 3.246094 84.167969 3.246094 C 84.425781 3.246094 84.6875 3.246094 84.945312 3.246094 C 85.171875 3.242188 85.398438 3.242188 85.625 3.242188 C 85.746094 3.242188 85.867188 3.242188 85.984375 3.242188 C 88.15625 3.238281 88.15625 3.238281 88.894531 3.898438 C 88.914062 3.914062 88.9375 3.929688 88.957031 3.945312 C 89.191406 4.144531 89.363281 4.402344 89.472656 4.691406 C 89.480469 4.714844 89.492188 4.742188 89.5 4.765625 C 89.65625 5.25 89.601562 5.785156 89.382812 6.234375 C 89.117188 6.753906 88.695312 7.078125 88.152344 7.265625 C 87.984375 7.320312 87.816406 7.367188 87.648438 7.410156 C 87.664062 7.414062 87.679688 7.417969 87.699219 7.421875 C 88.523438 7.605469 89.300781 7.851562 89.78125 8.597656 C 90.0625 9.0625 90.125 9.636719 90.003906 10.164062 C 89.808594 10.804688 89.363281 11.304688 88.78125 11.621094 C 88.324219 11.863281 87.820312 11.988281 87.3125 12.054688 C 87.28125 12.058594 87.253906 12.0625 87.222656 12.066406 C 86.777344 12.121094 86.332031 12.109375 85.882812 12.105469 C 85.765625 12.105469 85.644531 12.105469 85.523438 12.105469 C 85.300781 12.105469 85.074219 12.105469 84.847656 12.105469 C 84.589844 12.105469 84.332031 12.105469 84.074219 12.105469 C 83.546875 12.105469 83.015625 12.101562 82.488281 12.101562 C 82.488281 11.878906 82.488281 11.65625 82.488281 11.429688 C 82.859375 11.390625 83.234375 11.347656 83.617188 11.308594 C 83.617188 8.910156 83.617188 6.511719 83.617188 4.042969 C 83.488281 4.035156 83.363281 4.027344 83.230469 4.019531 C 83.117188 4.007812 83.003906 3.996094 82.890625 3.980469 C 82.863281 3.980469 82.832031 3.976562 82.804688 3.972656 C 82.695312 3.960938 82.59375 3.949219 82.488281 3.921875 C 82.488281 3.699219 82.488281 3.476562 82.488281 3.25 Z M 85.390625 3.96875 C 85.390625 4.242188 85.386719 4.515625 85.382812 4.785156 C 85.382812 4.914062 85.378906 5.039062 85.378906 5.164062 C 85.371094 5.824219 85.367188 6.484375 85.367188 7.144531 C 86.488281 7.183594 86.488281 7.183594 87.457031 6.691406 C 87.796875 6.320312 87.859375 5.832031 87.847656 5.351562 C 87.832031 4.992188 87.71875 4.644531 87.460938 4.378906 C 87 3.96875 86.363281 3.964844 85.78125 3.96875 C 85.742188 3.96875 85.703125 3.96875 85.667969 3.96875 C 85.574219 3.96875 85.484375 3.96875 85.390625 3.96875 Z M 85.390625 7.84375 C 85.390625 9.003906 85.390625 10.160156 85.390625 11.355469 C 86.28125 11.386719 86.28125 11.386719 87.152344 11.21875 C 87.171875 11.214844 87.1875 11.207031 87.207031 11.199219 C 87.578125 11.066406 87.886719 10.824219 88.066406 10.46875 C 88.28125 9.988281 88.289062 9.417969 88.125 8.921875 C 87.960938 8.492188 87.664062 8.234375 87.257812 8.046875 C 86.664062 7.804688 86.023438 7.84375 85.390625 7.84375 Z M 85.390625 7.84375 " + }, + "children": [] + }, + { + "type": "element", + "name": "path", + "attributes": { + "style": " stroke:none;fill-rule:nonzero;fill:rgb(10.196079%,11.372549%,14.117648%);fill-opacity:1;", + "d": "M 76.167969 3.476562 C 76.367188 3.671875 76.507812 3.917969 76.585938 4.1875 C 76.589844 4.203125 76.59375 4.222656 76.601562 4.242188 C 76.707031 4.675781 76.621094 5.144531 76.414062 5.53125 C 76.34375 5.644531 76.265625 5.746094 76.175781 5.847656 C 76.15625 5.867188 76.136719 5.886719 76.117188 5.910156 C 75.71875 6.332031 75.199219 6.617188 74.6875 6.882812 C 74.707031 6.902344 74.726562 6.921875 74.746094 6.941406 C 74.972656 7.191406 74.972656 7.191406 75.066406 7.296875 C 75.140625 7.382812 75.21875 7.464844 75.300781 7.542969 C 75.351562 7.59375 75.394531 7.640625 75.4375 7.695312 C 75.527344 7.796875 75.621094 7.894531 75.714844 7.992188 C 76.089844 8.394531 76.089844 8.394531 76.253906 8.585938 C 76.351562 8.695312 76.449219 8.800781 76.546875 8.90625 C 76.621094 8.980469 76.691406 9.058594 76.761719 9.136719 C 76.773438 9.152344 76.789062 9.164062 76.800781 9.179688 C 76.824219 9.207031 76.851562 9.234375 76.875 9.261719 C 76.933594 9.324219 76.992188 9.382812 77.0625 9.429688 C 77.070312 9.410156 77.070312 9.410156 77.082031 9.386719 C 77.113281 9.304688 77.152344 9.230469 77.195312 9.15625 C 77.5625 8.476562 77.800781 7.753906 77.976562 7 C 77.953125 7 77.933594 6.996094 77.910156 6.996094 C 77.707031 6.96875 77.5 6.9375 77.296875 6.902344 C 77.273438 6.898438 77.25 6.894531 77.222656 6.890625 C 77.050781 6.859375 77.050781 6.859375 76.96875 6.832031 C 76.960938 6.328125 76.960938 6.328125 77.015625 6.160156 C 77.949219 6.160156 78.886719 6.160156 79.847656 6.160156 C 79.847656 6.367188 79.847656 6.574219 79.847656 6.785156 C 79.53125 6.839844 79.214844 6.894531 78.886719 6.953125 C 78.859375 7.046875 78.832031 7.140625 78.804688 7.234375 C 78.539062 8.09375 78.164062 9.035156 77.601562 9.746094 C 77.5625 9.792969 77.5625 9.792969 77.566406 9.851562 C 77.601562 9.933594 77.648438 9.980469 77.714844 10.039062 C 77.792969 10.113281 77.867188 10.1875 77.9375 10.269531 C 78.027344 10.375 78.125 10.46875 78.222656 10.566406 C 78.308594 10.65625 78.390625 10.742188 78.472656 10.839844 C 78.539062 10.914062 78.601562 10.933594 78.695312 10.949219 C 78.71875 10.953125 78.746094 10.957031 78.769531 10.960938 C 78.796875 10.964844 78.824219 10.96875 78.851562 10.972656 C 78.875 10.980469 78.902344 10.984375 78.933594 10.988281 C 79.019531 11.003906 79.105469 11.019531 79.191406 11.03125 C 79.277344 11.046875 79.363281 11.0625 79.449219 11.078125 C 79.503906 11.085938 79.558594 11.097656 79.613281 11.105469 C 79.648438 11.113281 79.648438 11.113281 79.6875 11.117188 C 79.707031 11.121094 79.730469 11.125 79.75 11.128906 C 79.800781 11.140625 79.800781 11.140625 79.824219 11.164062 C 79.820312 11.421875 79.785156 11.679688 79.753906 11.933594 C 79.691406 11.949219 79.632812 11.964844 79.570312 11.980469 C 79.546875 11.984375 79.546875 11.984375 79.519531 11.992188 C 79.214844 12.066406 78.910156 12.085938 78.597656 12.085938 C 78.539062 12.085938 78.484375 12.085938 78.425781 12.085938 C 77.847656 12.089844 77.332031 11.917969 76.894531 11.523438 C 76.855469 11.484375 76.816406 11.445312 76.777344 11.40625 C 76.71875 11.347656 76.660156 11.296875 76.601562 11.242188 C 76.578125 11.21875 76.578125 11.21875 76.554688 11.195312 C 76.515625 11.160156 76.476562 11.125 76.441406 11.089844 C 76.429688 11.101562 76.417969 11.109375 76.410156 11.117188 C 76.140625 11.351562 75.859375 11.554688 75.542969 11.71875 C 75.511719 11.738281 75.476562 11.757812 75.445312 11.777344 C 75.3125 11.847656 75.179688 11.894531 75.039062 11.9375 C 75.011719 11.945312 75.011719 11.945312 74.984375 11.953125 C 74.632812 12.058594 74.269531 12.089844 73.90625 12.085938 C 73.84375 12.085938 73.785156 12.085938 73.722656 12.089844 C 72.941406 12.089844 72.222656 11.824219 71.652344 11.28125 C 71.203125 10.820312 71.023438 10.246094 71.03125 9.609375 C 71.042969 9.058594 71.230469 8.546875 71.59375 8.132812 C 71.609375 8.113281 71.625 8.09375 71.644531 8.070312 C 71.980469 7.683594 72.398438 7.421875 72.839844 7.171875 C 72.871094 7.152344 72.902344 7.132812 72.9375 7.113281 C 72.960938 7.101562 72.984375 7.085938 73.007812 7.074219 C 72.996094 7.0625 72.988281 7.050781 72.976562 7.042969 C 72.398438 6.425781 72.09375 5.613281 72.113281 4.773438 C 72.128906 4.371094 72.257812 3.988281 72.527344 3.679688 C 72.542969 3.660156 72.558594 3.644531 72.570312 3.625 C 72.917969 3.210938 73.496094 2.996094 74.015625 2.933594 C 74.050781 2.929688 74.050781 2.929688 74.082031 2.925781 C 74.804688 2.847656 75.621094 2.964844 76.167969 3.476562 Z M 73.671875 3.796875 C 73.433594 4.113281 73.414062 4.4375 73.457031 4.820312 C 73.550781 5.460938 73.921875 5.9375 74.328125 6.425781 C 74.398438 6.390625 74.449219 6.355469 74.503906 6.300781 C 74.527344 6.28125 74.527344 6.28125 74.550781 6.257812 C 74.566406 6.242188 74.582031 6.226562 74.597656 6.210938 C 74.613281 6.191406 74.628906 6.175781 74.644531 6.160156 C 74.773438 6.03125 74.890625 5.894531 75 5.75 C 75.019531 5.726562 75.035156 5.699219 75.054688 5.675781 C 75.335938 5.292969 75.5 4.859375 75.457031 4.378906 C 75.40625 4.078125 75.289062 3.820312 75.035156 3.636719 C 74.59375 3.363281 74.03125 3.410156 73.671875 3.796875 Z M 73.046875 7.828125 C 72.664062 8.226562 72.519531 8.789062 72.519531 9.332031 C 72.53125 9.800781 72.71875 10.257812 73.039062 10.601562 C 73.46875 10.996094 73.980469 11.140625 74.550781 11.125 C 74.960938 11.105469 75.339844 11.003906 75.703125 10.8125 C 75.71875 10.804688 75.738281 10.796875 75.753906 10.785156 C 75.84375 10.738281 75.90625 10.699219 75.960938 10.609375 C 75.949219 10.601562 75.941406 10.589844 75.933594 10.582031 C 75.460938 10.074219 75.460938 10.074219 75.25 9.8125 C 75.1875 9.738281 75.125 9.664062 75.0625 9.59375 C 74.972656 9.484375 74.882812 9.375 74.796875 9.265625 C 74.695312 9.132812 74.589844 9.003906 74.480469 8.878906 C 74.390625 8.773438 74.304688 8.667969 74.214844 8.5625 C 74.152344 8.484375 74.085938 8.40625 74.019531 8.328125 C 73.921875 8.214844 73.828125 8.101562 73.734375 7.984375 C 73.726562 7.96875 73.714844 7.957031 73.703125 7.941406 C 73.683594 7.914062 73.660156 7.886719 73.640625 7.859375 C 73.589844 7.792969 73.539062 7.730469 73.488281 7.667969 C 73.460938 7.632812 73.460938 7.632812 73.433594 7.601562 C 73.414062 7.578125 73.414062 7.578125 73.390625 7.554688 C 73.265625 7.554688 73.132812 7.742188 73.046875 7.828125 Z M 73.046875 7.828125 " + }, + "children": [] + }, + { + "type": "element", + "name": "path", + "attributes": { + "style": " stroke:none;fill-rule:nonzero;fill:rgb(10.196079%,11.372549%,14.117648%);fill-opacity:1;", + "d": "M 49.992188 5.535156 C 50.101562 5.609375 50.101562 5.609375 50.136719 5.679688 C 50.136719 5.746094 50.140625 5.8125 50.140625 5.882812 C 50.140625 5.914062 50.140625 5.914062 50.140625 5.941406 C 50.140625 5.984375 50.140625 6.027344 50.140625 6.070312 C 50.140625 6.136719 50.140625 6.203125 50.140625 6.269531 C 50.140625 6.308594 50.140625 6.351562 50.140625 6.390625 C 50.140625 6.410156 50.140625 6.429688 50.140625 6.453125 C 50.140625 6.589844 50.140625 6.589844 50.113281 6.617188 C 50.074219 6.617188 50.035156 6.621094 49.996094 6.621094 C 49.972656 6.621094 49.949219 6.621094 49.921875 6.621094 C 49.894531 6.621094 49.871094 6.617188 49.84375 6.617188 C 49.816406 6.617188 49.789062 6.617188 49.757812 6.617188 C 49.671875 6.617188 49.585938 6.617188 49.5 6.617188 C 49.441406 6.617188 49.378906 6.617188 49.320312 6.617188 C 49.175781 6.617188 49.03125 6.617188 48.886719 6.617188 C 48.898438 6.640625 48.90625 6.660156 48.917969 6.683594 C 48.929688 6.714844 48.945312 6.746094 48.957031 6.773438 C 48.964844 6.789062 48.96875 6.804688 48.976562 6.820312 C 49.203125 7.339844 49.195312 8 48.988281 8.523438 C 48.75 9.0625 48.355469 9.457031 47.804688 9.671875 C 47.066406 9.941406 46.210938 9.941406 45.457031 9.746094 C 45.277344 10.003906 45.214844 10.273438 45.238281 10.585938 C 45.269531 10.699219 45.316406 10.761719 45.402344 10.835938 C 45.617188 10.945312 45.851562 10.949219 46.089844 10.949219 C 46.113281 10.953125 46.132812 10.953125 46.15625 10.953125 C 46.203125 10.953125 46.25 10.953125 46.292969 10.953125 C 46.367188 10.953125 46.441406 10.953125 46.515625 10.953125 C 46.726562 10.953125 46.9375 10.957031 47.144531 10.957031 C 47.273438 10.957031 47.402344 10.957031 47.53125 10.960938 C 47.582031 10.960938 47.628906 10.960938 47.675781 10.960938 C 48.324219 10.960938 49.039062 11.019531 49.53125 11.492188 C 49.546875 11.511719 49.566406 11.53125 49.585938 11.550781 C 49.601562 11.566406 49.617188 11.582031 49.636719 11.597656 C 49.957031 11.929688 50.0625 12.394531 50.066406 12.84375 C 50.054688 13.351562 49.847656 13.800781 49.511719 14.171875 C 49.496094 14.191406 49.480469 14.207031 49.460938 14.226562 C 48.8125 14.921875 47.769531 15.179688 46.855469 15.210938 C 45.890625 15.234375 44.761719 15.230469 44.015625 14.523438 C 43.738281 14.222656 43.660156 13.886719 43.671875 13.488281 C 43.679688 13.363281 43.699219 13.253906 43.753906 13.136719 C 43.761719 13.117188 43.769531 13.09375 43.78125 13.074219 C 43.996094 12.644531 44.386719 12.410156 44.785156 12.175781 C 44.765625 12.167969 44.746094 12.160156 44.730469 12.152344 C 44.398438 11.996094 44.222656 11.808594 44.089844 11.476562 C 43.988281 11.136719 44.070312 10.757812 44.222656 10.453125 C 44.421875 10.109375 44.695312 9.824219 44.976562 9.550781 C 44.960938 9.542969 44.945312 9.53125 44.925781 9.523438 C 44.757812 9.417969 44.613281 9.304688 44.472656 9.167969 C 44.457031 9.152344 44.441406 9.136719 44.425781 9.121094 C 44.214844 8.902344 44.085938 8.597656 44.015625 8.300781 C 44.011719 8.28125 44.003906 8.257812 44 8.238281 C 43.882812 7.675781 43.964844 7.042969 44.277344 6.558594 C 44.621094 6.070312 45.09375 5.773438 45.671875 5.628906 C 45.6875 5.625 45.703125 5.621094 45.71875 5.617188 C 46.25 5.492188 46.917969 5.496094 47.449219 5.628906 C 47.464844 5.632812 47.480469 5.636719 47.496094 5.640625 C 47.6875 5.691406 47.867188 5.761719 48.046875 5.84375 C 48.0625 5.851562 48.078125 5.859375 48.09375 5.867188 C 48.164062 5.902344 48.226562 5.933594 48.289062 5.980469 C 48.390625 6.066406 48.390625 6.066406 48.515625 6.082031 C 48.582031 6.0625 48.644531 6.035156 48.707031 6.003906 C 48.730469 5.996094 48.753906 5.984375 48.78125 5.976562 C 48.855469 5.941406 48.929688 5.910156 49.003906 5.875 C 49.054688 5.851562 49.101562 5.832031 49.152344 5.808594 C 49.320312 5.738281 49.488281 5.664062 49.652344 5.585938 C 49.679688 5.574219 49.703125 5.566406 49.730469 5.554688 C 49.75 5.542969 49.769531 5.535156 49.789062 5.523438 C 49.867188 5.503906 49.917969 5.515625 49.992188 5.535156 Z M 45.835938 6.507812 C 45.472656 6.984375 45.421875 7.597656 45.492188 8.175781 C 45.550781 8.542969 45.6875 8.890625 45.980469 9.132812 C 46.207031 9.285156 46.46875 9.3125 46.734375 9.277344 C 47.015625 9.21875 47.210938 9.089844 47.375 8.855469 C 47.683594 8.375 47.742188 7.746094 47.640625 7.191406 C 47.5625 6.859375 47.402344 6.507812 47.117188 6.308594 C 46.703125 6.074219 46.152344 6.148438 45.835938 6.507812 Z M 45.238281 12.367188 C 44.957031 12.734375 44.867188 13.113281 44.902344 13.570312 C 44.957031 13.84375 45.09375 14.058594 45.316406 14.226562 C 45.613281 14.417969 46.015625 14.496094 46.367188 14.507812 C 46.394531 14.507812 46.394531 14.507812 46.417969 14.507812 C 47.132812 14.527344 47.90625 14.457031 48.453125 13.945312 C 48.652344 13.738281 48.710938 13.515625 48.703125 13.230469 C 48.683594 12.992188 48.570312 12.800781 48.394531 12.644531 C 48.113281 12.441406 47.726562 12.449219 47.398438 12.449219 C 47.355469 12.449219 47.3125 12.449219 47.269531 12.449219 C 47.15625 12.449219 47.046875 12.449219 46.933594 12.449219 C 46.753906 12.449219 46.574219 12.445312 46.394531 12.445312 C 46.332031 12.445312 46.269531 12.445312 46.210938 12.445312 C 45.882812 12.445312 45.5625 12.414062 45.238281 12.367188 Z M 45.238281 12.367188 " + }, + "children": [] + }, + { + "type": "element", + "name": "path", + "attributes": { + "style": " stroke:none;fill-rule:nonzero;fill:rgb(10.196079%,11.372549%,14.117648%);fill-opacity:1;", + "d": "M 53.039062 2.382812 C 53.0625 2.390625 53.0625 2.390625 53.085938 2.398438 C 53.140625 2.429688 53.171875 2.453125 53.207031 2.503906 C 53.230469 2.617188 53.21875 2.730469 53.214844 2.84375 C 53.210938 2.878906 53.210938 2.914062 53.210938 2.953125 C 53.207031 3.027344 53.203125 3.105469 53.199219 3.183594 C 53.191406 3.371094 53.183594 3.558594 53.179688 3.746094 C 53.175781 3.792969 53.175781 3.835938 53.175781 3.882812 C 53.15625 4.441406 53.15625 5 53.15625 5.5625 C 53.15625 5.640625 53.15625 5.71875 53.15625 5.792969 C 53.15625 6.085938 53.160156 6.375 53.160156 6.664062 C 53.179688 6.644531 53.195312 6.625 53.214844 6.605469 C 53.238281 6.578125 53.261719 6.550781 53.285156 6.523438 C 53.296875 6.511719 53.3125 6.5 53.324219 6.484375 C 53.78125 5.984375 54.445312 5.601562 55.128906 5.550781 C 55.640625 5.535156 56.128906 5.578125 56.527344 5.929688 C 56.566406 5.964844 56.601562 6 56.640625 6.039062 C 56.664062 6.0625 56.664062 6.0625 56.691406 6.089844 C 57.246094 6.660156 57.203125 7.570312 57.203125 8.304688 C 57.203125 8.414062 57.203125 8.523438 57.207031 8.632812 C 57.207031 8.839844 57.207031 9.042969 57.207031 9.25 C 57.207031 9.484375 57.210938 9.722656 57.210938 9.957031 C 57.210938 10.4375 57.214844 10.921875 57.214844 11.40625 C 57.246094 11.410156 57.246094 11.410156 57.28125 11.414062 C 57.308594 11.417969 57.335938 11.421875 57.363281 11.425781 C 57.40625 11.433594 57.40625 11.433594 57.445312 11.441406 C 57.558594 11.457031 57.671875 11.480469 57.785156 11.503906 C 57.808594 11.507812 57.828125 11.511719 57.851562 11.515625 C 57.878906 11.519531 57.878906 11.519531 57.910156 11.527344 C 57.929688 11.53125 57.949219 11.535156 57.964844 11.539062 C 58.007812 11.550781 58.007812 11.550781 58.03125 11.574219 C 58.035156 11.613281 58.035156 11.65625 58.035156 11.695312 C 58.035156 11.71875 58.035156 11.746094 58.035156 11.769531 C 58.035156 11.796875 58.035156 11.824219 58.035156 11.851562 C 58.035156 11.875 58.035156 11.902344 58.035156 11.929688 C 58.035156 11.964844 58.035156 11.964844 58.035156 12.003906 C 58.035156 12.027344 58.035156 12.050781 58.035156 12.074219 C 58.03125 12.125 58.03125 12.125 58.007812 12.148438 C 57.964844 12.152344 57.921875 12.152344 57.882812 12.152344 C 57.839844 12.152344 57.839844 12.152344 57.796875 12.152344 C 57.769531 12.152344 57.738281 12.152344 57.707031 12.152344 C 57.675781 12.152344 57.640625 12.152344 57.609375 12.152344 C 57.523438 12.152344 57.433594 12.152344 57.347656 12.152344 C 57.257812 12.152344 57.164062 12.152344 57.074219 12.152344 C 56.917969 12.152344 56.765625 12.152344 56.613281 12.152344 C 56.433594 12.152344 56.257812 12.152344 56.082031 12.152344 C 55.929688 12.152344 55.777344 12.152344 55.625 12.152344 C 55.53125 12.152344 55.441406 12.152344 55.351562 12.152344 C 55.265625 12.152344 55.179688 12.152344 55.09375 12.152344 C 55.046875 12.152344 55 12.152344 54.953125 12.152344 C 54.925781 12.152344 54.898438 12.152344 54.871094 12.152344 C 54.847656 12.152344 54.824219 12.152344 54.796875 12.152344 C 54.742188 12.148438 54.742188 12.148438 54.71875 12.125 C 54.71875 12.085938 54.71875 12.042969 54.71875 12.003906 C 54.71875 11.976562 54.71875 11.953125 54.71875 11.925781 C 54.71875 11.902344 54.71875 11.875 54.71875 11.847656 C 54.71875 11.820312 54.71875 11.796875 54.71875 11.769531 C 54.71875 11.703125 54.71875 11.636719 54.71875 11.574219 C 54.8125 11.53125 54.902344 11.507812 55.003906 11.488281 C 55.03125 11.480469 55.0625 11.476562 55.09375 11.46875 C 55.113281 11.464844 55.128906 11.460938 55.144531 11.460938 C 55.191406 11.449219 55.242188 11.441406 55.289062 11.429688 C 55.527344 11.378906 55.527344 11.378906 55.585938 11.378906 C 55.582031 10.90625 55.582031 10.433594 55.578125 9.960938 C 55.578125 9.742188 55.578125 9.523438 55.574219 9.304688 C 55.574219 9.109375 55.574219 8.917969 55.574219 8.726562 C 55.574219 8.625 55.570312 8.527344 55.570312 8.425781 C 55.570312 8.328125 55.570312 8.234375 55.570312 8.136719 C 55.570312 8.101562 55.570312 8.066406 55.570312 8.03125 C 55.570312 7.664062 55.554688 7.199219 55.289062 6.917969 C 55.054688 6.722656 54.742188 6.746094 54.457031 6.761719 C 54.101562 6.800781 53.738281 7.007812 53.464844 7.234375 C 53.425781 7.265625 53.425781 7.265625 53.371094 7.300781 C 53.273438 7.371094 53.214844 7.421875 53.1875 7.539062 C 53.179688 7.640625 53.179688 7.742188 53.183594 7.84375 C 53.183594 7.882812 53.183594 7.921875 53.183594 7.960938 C 53.183594 8.066406 53.183594 8.167969 53.1875 8.273438 C 53.1875 8.386719 53.1875 8.496094 53.1875 8.605469 C 53.1875 8.8125 53.191406 9.023438 53.191406 9.230469 C 53.195312 9.46875 53.195312 9.703125 53.195312 9.941406 C 53.199219 10.429688 53.203125 10.917969 53.207031 11.40625 C 53.238281 11.410156 53.238281 11.410156 53.265625 11.414062 C 53.351562 11.429688 53.4375 11.445312 53.523438 11.464844 C 53.554688 11.46875 53.585938 11.472656 53.613281 11.480469 C 53.644531 11.484375 53.671875 11.492188 53.703125 11.496094 C 53.730469 11.5 53.753906 11.507812 53.78125 11.511719 C 53.847656 11.523438 53.910156 11.535156 53.976562 11.550781 C 53.976562 11.746094 53.976562 11.945312 53.976562 12.148438 C 52.890625 12.148438 51.804688 12.148438 50.6875 12.148438 C 50.6875 11.953125 50.6875 11.753906 50.6875 11.550781 C 50.964844 11.492188 51.242188 11.4375 51.527344 11.378906 C 51.542969 11.160156 51.554688 10.945312 51.554688 10.722656 C 51.554688 10.691406 51.554688 10.660156 51.554688 10.628906 C 51.554688 10.546875 51.558594 10.464844 51.558594 10.378906 C 51.558594 10.289062 51.558594 10.199219 51.558594 10.109375 C 51.558594 9.953125 51.558594 9.796875 51.558594 9.640625 C 51.558594 9.414062 51.558594 9.1875 51.5625 8.960938 C 51.5625 8.59375 51.5625 8.230469 51.5625 7.863281 C 51.566406 7.507812 51.566406 7.152344 51.566406 6.792969 C 51.566406 6.773438 51.566406 6.75 51.566406 6.726562 C 51.566406 6.617188 51.566406 6.507812 51.566406 6.398438 C 51.570312 5.484375 51.574219 4.570312 51.574219 3.65625 C 51.554688 3.65625 51.535156 3.652344 51.515625 3.652344 C 51.476562 3.648438 51.476562 3.648438 51.4375 3.644531 C 51.414062 3.644531 51.386719 3.640625 51.359375 3.640625 C 51.277344 3.632812 51.195312 3.621094 51.113281 3.609375 C 51.082031 3.605469 51.054688 3.601562 51.023438 3.597656 C 50.996094 3.59375 50.964844 3.589844 50.933594 3.582031 C 50.902344 3.578125 50.871094 3.574219 50.839844 3.570312 C 50.765625 3.558594 50.691406 3.546875 50.617188 3.535156 C 50.617188 3.347656 50.617188 3.15625 50.617188 2.960938 C 50.910156 2.875 51.207031 2.796875 51.5 2.722656 C 51.523438 2.714844 51.542969 2.710938 51.5625 2.707031 C 51.671875 2.679688 51.777344 2.652344 51.886719 2.625 C 51.972656 2.601562 52.0625 2.578125 52.152344 2.554688 C 52.257812 2.527344 52.367188 2.5 52.472656 2.472656 C 52.515625 2.460938 52.554688 2.453125 52.597656 2.441406 C 52.652344 2.425781 52.710938 2.410156 52.765625 2.398438 C 52.78125 2.394531 52.800781 2.386719 52.816406 2.382812 C 52.898438 2.363281 52.960938 2.351562 53.039062 2.382812 Z M 53.039062 2.382812 " + }, + "children": [] + }, + { + "type": "element", + "name": "path", + "attributes": { + "style": " stroke:none;fill-rule:nonzero;fill:rgb(10.196079%,11.372549%,14.117648%);fill-opacity:1;", + "d": "M 99.691406 6.011719 C 100.109375 6.40625 100.234375 7.003906 100.273438 7.554688 C 100.277344 7.667969 100.277344 7.785156 100.277344 7.898438 C 100.277344 7.933594 100.277344 7.964844 100.277344 8 C 100.277344 8.074219 100.277344 8.144531 100.277344 8.21875 C 100.277344 8.332031 100.277344 8.445312 100.277344 8.558594 C 100.28125 8.886719 100.28125 9.210938 100.28125 9.535156 C 100.28125 9.714844 100.28125 9.894531 100.285156 10.074219 C 100.285156 10.171875 100.285156 10.265625 100.285156 10.359375 C 100.285156 10.449219 100.285156 10.539062 100.285156 10.628906 C 100.285156 10.675781 100.285156 10.726562 100.285156 10.773438 C 100.289062 10.964844 100.292969 11.167969 100.417969 11.320312 C 100.53125 11.378906 100.636719 11.398438 100.757812 11.367188 C 100.898438 11.308594 101.003906 11.21875 101.113281 11.117188 C 101.226562 11.199219 101.339844 11.289062 101.449219 11.378906 C 101.394531 11.527344 101.300781 11.640625 101.207031 11.765625 C 101.179688 11.804688 101.179688 11.804688 101.152344 11.84375 C 100.859375 12.152344 100.476562 12.265625 100.058594 12.277344 C 99.699219 12.269531 99.332031 12.164062 99.066406 11.910156 C 98.933594 11.757812 98.761719 11.519531 98.761719 11.308594 C 98.582031 11.449219 98.582031 11.449219 98.417969 11.605469 C 98.289062 11.738281 98.140625 11.847656 97.992188 11.957031 C 97.96875 11.972656 97.949219 11.992188 97.925781 12.007812 C 97.488281 12.3125 96.855469 12.339844 96.34375 12.25 C 95.917969 12.15625 95.527344 11.929688 95.28125 11.558594 C 95.035156 11.136719 94.964844 10.617188 95.082031 10.140625 C 95.289062 9.527344 95.746094 9.175781 96.300781 8.894531 C 96.941406 8.582031 97.644531 8.375 98.328125 8.179688 C 98.34375 8.175781 98.359375 8.171875 98.375 8.167969 C 98.472656 8.140625 98.566406 8.113281 98.664062 8.085938 C 98.695312 7.195312 98.695312 7.195312 98.328125 6.425781 C 98.121094 6.230469 97.828125 6.203125 97.558594 6.203125 C 97.53125 6.203125 97.53125 6.203125 97.503906 6.203125 C 97.339844 6.207031 97.171875 6.21875 97.007812 6.230469 C 97.003906 6.257812 97.003906 6.289062 97 6.316406 C 96.984375 6.46875 96.957031 6.617188 96.925781 6.765625 C 96.917969 6.816406 96.910156 6.863281 96.902344 6.910156 C 96.832031 7.273438 96.738281 7.585938 96.425781 7.808594 C 96.191406 7.933594 95.945312 7.933594 95.6875 7.867188 C 95.515625 7.808594 95.40625 7.707031 95.320312 7.546875 C 95.234375 7.347656 95.238281 7.183594 95.308594 6.980469 C 95.316406 6.957031 95.316406 6.957031 95.324219 6.929688 C 95.421875 6.644531 95.574219 6.441406 95.785156 6.230469 C 95.800781 6.214844 95.816406 6.195312 95.835938 6.179688 C 96.734375 5.308594 98.742188 5.21875 99.691406 6.011719 Z M 98.394531 8.742188 C 98.292969 8.777344 98.1875 8.8125 98.082031 8.847656 C 97.574219 9.007812 97.011719 9.230469 96.746094 9.722656 C 96.582031 10.042969 96.554688 10.355469 96.648438 10.703125 C 96.71875 10.914062 96.816406 11.042969 97.011719 11.152344 C 97.296875 11.292969 97.609375 11.304688 97.910156 11.199219 C 98.058594 11.132812 98.207031 11.050781 98.34375 10.960938 C 98.398438 10.921875 98.398438 10.921875 98.46875 10.890625 C 98.558594 10.839844 98.644531 10.789062 98.679688 10.683594 C 98.703125 10.542969 98.695312 10.402344 98.691406 10.257812 C 98.6875 10.214844 98.6875 10.167969 98.6875 10.121094 C 98.6875 10 98.683594 9.878906 98.683594 9.757812 C 98.679688 9.636719 98.679688 9.511719 98.675781 9.386719 C 98.675781 9.144531 98.667969 8.902344 98.664062 8.660156 C 98.578125 8.660156 98.476562 8.714844 98.394531 8.742188 Z M 98.394531 8.742188 " + }, + "children": [] + }, + { + "type": "element", + "name": "path", + "attributes": { + "style": " stroke:none;fill-rule:nonzero;fill:rgb(10.196079%,11.372549%,14.117648%);fill-opacity:1;", + "d": "M 38.035156 6.242188 C 38.207031 6.40625 38.332031 6.578125 38.449219 6.785156 C 38.460938 6.808594 38.460938 6.808594 38.472656 6.832031 C 38.835938 7.472656 38.875 8.257812 38.761719 8.972656 C 38.734375 9 38.734375 9 38.671875 9 C 38.625 9 38.625 9 38.578125 9 C 38.5625 9 38.546875 9 38.53125 9 C 38.472656 9 38.417969 9 38.363281 9 C 38.324219 9 38.285156 9 38.242188 9 C 38.136719 9 38.027344 9 37.917969 9 C 37.804688 9 37.695312 9 37.582031 9 C 37.367188 9 37.152344 9 36.9375 9 C 36.695312 9 36.453125 9 36.207031 9 C 35.707031 9 35.207031 9 34.703125 9 C 34.714844 9.089844 34.726562 9.183594 34.738281 9.273438 C 34.742188 9.300781 34.742188 9.328125 34.746094 9.351562 C 34.8125 9.898438 35.007812 10.441406 35.4375 10.808594 C 35.933594 11.1875 36.476562 11.269531 37.089844 11.203125 C 37.539062 11.128906 37.90625 10.847656 38.222656 10.535156 C 38.347656 10.417969 38.347656 10.417969 38.425781 10.417969 C 38.464844 10.445312 38.464844 10.445312 38.503906 10.488281 C 38.589844 10.585938 38.683594 10.671875 38.785156 10.753906 C 38.679688 11.046875 38.46875 11.28125 38.257812 11.5 C 38.242188 11.519531 38.222656 11.535156 38.207031 11.554688 C 37.792969 12 37.171875 12.246094 36.574219 12.320312 C 36.554688 12.320312 36.535156 12.324219 36.515625 12.328125 C 35.640625 12.425781 34.773438 12.210938 34.074219 11.671875 C 33.421875 11.125 33.078125 10.363281 32.976562 9.527344 C 32.972656 9.496094 32.972656 9.496094 32.96875 9.460938 C 32.871094 8.5 33.074219 7.515625 33.675781 6.746094 C 33.707031 6.710938 33.738281 6.675781 33.769531 6.640625 C 33.78125 6.621094 33.796875 6.605469 33.8125 6.585938 C 34.316406 5.988281 35.136719 5.640625 35.902344 5.566406 C 36.699219 5.511719 37.429688 5.699219 38.035156 6.242188 Z M 35.226562 6.652344 C 34.949219 7.007812 34.820312 7.386719 34.746094 7.824219 C 34.742188 7.851562 34.738281 7.875 34.734375 7.898438 C 34.730469 7.921875 34.726562 7.949219 34.722656 7.972656 C 34.71875 7.992188 34.714844 8.015625 34.710938 8.035156 C 34.703125 8.097656 34.703125 8.15625 34.703125 8.222656 C 34.703125 8.242188 34.703125 8.261719 34.703125 8.28125 C 34.703125 8.292969 34.703125 8.308594 34.703125 8.324219 C 34.972656 8.328125 35.242188 8.328125 35.507812 8.328125 C 35.632812 8.328125 35.757812 8.328125 35.882812 8.332031 C 36.003906 8.332031 36.125 8.332031 36.246094 8.332031 C 36.289062 8.332031 36.335938 8.332031 36.382812 8.332031 C 36.445312 8.332031 36.511719 8.332031 36.574219 8.332031 C 36.59375 8.332031 36.613281 8.332031 36.632812 8.332031 C 36.800781 8.332031 36.964844 8.304688 37.085938 8.183594 C 37.273438 7.9375 37.277344 7.609375 37.246094 7.3125 C 37.195312 6.96875 37.015625 6.636719 36.730469 6.425781 C 36.226562 6.113281 35.617188 6.195312 35.226562 6.652344 Z M 35.226562 6.652344 " + }, + "children": [] + }, + { + "type": "element", + "name": "path", + "attributes": { + "style": " stroke:none;fill-rule:nonzero;fill:rgb(10.196079%,11.372549%,14.117648%);fill-opacity:1;", + "d": "M 112.726562 6.085938 C 112.90625 6.242188 113.058594 6.414062 113.183594 6.617188 C 113.199219 6.636719 113.210938 6.65625 113.226562 6.679688 C 113.628906 7.320312 113.699219 8.167969 113.566406 8.902344 C 113.523438 8.945312 113.449219 8.929688 113.386719 8.929688 C 113.371094 8.929688 113.355469 8.929688 113.339844 8.929688 C 113.28125 8.929688 113.226562 8.929688 113.171875 8.929688 C 113.132812 8.929688 113.089844 8.933594 113.050781 8.933594 C 112.945312 8.933594 112.835938 8.933594 112.726562 8.933594 C 112.613281 8.933594 112.5 8.933594 112.386719 8.933594 C 112.175781 8.9375 111.960938 8.9375 111.746094 8.9375 C 111.503906 8.941406 111.257812 8.941406 111.015625 8.941406 C 110.515625 8.945312 110.011719 8.949219 109.511719 8.949219 C 109.519531 9.023438 109.527344 9.097656 109.535156 9.171875 C 109.535156 9.191406 109.539062 9.210938 109.539062 9.230469 C 109.554688 9.378906 109.578125 9.523438 109.613281 9.667969 C 109.621094 9.6875 109.625 9.710938 109.628906 9.730469 C 109.703125 10.011719 109.808594 10.261719 109.992188 10.488281 C 110.003906 10.507812 110.019531 10.527344 110.035156 10.542969 C 110.320312 10.898438 110.765625 11.117188 111.214844 11.164062 C 111.839844 11.203125 112.339844 11.078125 112.820312 10.671875 C 112.9375 10.570312 113.050781 10.457031 113.160156 10.347656 C 113.230469 10.378906 113.28125 10.414062 113.339844 10.46875 C 113.355469 10.480469 113.367188 10.496094 113.382812 10.507812 C 113.398438 10.523438 113.414062 10.539062 113.429688 10.554688 C 113.445312 10.566406 113.460938 10.582031 113.476562 10.597656 C 113.515625 10.632812 113.554688 10.671875 113.59375 10.707031 C 113.324219 11.316406 112.769531 11.816406 112.15625 12.066406 C 112.054688 12.105469 111.953125 12.136719 111.847656 12.171875 C 111.832031 12.175781 111.816406 12.179688 111.800781 12.183594 C 111.507812 12.265625 111.214844 12.28125 110.914062 12.28125 C 110.898438 12.28125 110.878906 12.28125 110.859375 12.28125 C 110.554688 12.277344 110.261719 12.257812 109.96875 12.175781 C 109.941406 12.167969 109.941406 12.167969 109.914062 12.160156 C 109.203125 11.953125 108.628906 11.503906 108.238281 10.875 C 108.230469 10.859375 108.222656 10.847656 108.210938 10.832031 C 107.699219 9.980469 107.648438 8.855469 107.878906 7.90625 C 108.074219 7.136719 108.570312 6.417969 109.253906 6 C 110.304688 5.378906 111.75 5.261719 112.726562 6.085938 Z M 110.105469 6.496094 C 109.710938 6.9375 109.507812 7.546875 109.511719 8.136719 C 109.511719 8.160156 109.511719 8.179688 109.511719 8.203125 C 109.511719 8.21875 109.511719 8.234375 109.511719 8.253906 C 109.78125 8.253906 110.050781 8.253906 110.316406 8.257812 C 110.441406 8.257812 110.566406 8.257812 110.691406 8.257812 C 110.8125 8.257812 110.933594 8.257812 111.054688 8.257812 C 111.097656 8.257812 111.144531 8.261719 111.191406 8.261719 C 111.253906 8.261719 111.320312 8.261719 111.382812 8.261719 C 111.402344 8.261719 111.421875 8.261719 111.441406 8.261719 C 111.59375 8.261719 111.746094 8.234375 111.871094 8.140625 C 112.082031 7.886719 112.078125 7.605469 112.054688 7.289062 C 112.011719 6.949219 111.867188 6.6875 111.625 6.449219 C 111.609375 6.433594 111.59375 6.417969 111.582031 6.40625 C 111.164062 6.015625 110.496094 6.121094 110.105469 6.496094 Z M 110.105469 6.496094 " + }, + "children": [] + }, + { + "type": "element", + "name": "path", + "attributes": { + "style": " stroke:none;fill-rule:nonzero;fill:rgb(10.196079%,11.372549%,14.117648%);fill-opacity:1;", + "d": "M 119.207031 6.039062 C 119.210938 6.308594 119.203125 6.578125 119.1875 6.847656 C 119.183594 6.910156 119.183594 6.972656 119.179688 7.035156 C 119.175781 7.078125 119.175781 7.117188 119.171875 7.160156 C 119.171875 7.175781 119.171875 7.195312 119.171875 7.214844 C 119.164062 7.308594 119.152344 7.390625 119.136719 7.484375 C 118.835938 7.484375 118.535156 7.484375 118.222656 7.484375 C 118.207031 7.40625 118.191406 7.328125 118.171875 7.246094 C 118.15625 7.171875 118.140625 7.097656 118.121094 7.023438 C 118.109375 6.96875 118.101562 6.917969 118.089844 6.867188 C 118.070312 6.792969 118.054688 6.714844 118.039062 6.640625 C 118.035156 6.617188 118.027344 6.59375 118.023438 6.570312 C 118.019531 6.550781 118.015625 6.527344 118.007812 6.503906 C 118.003906 6.484375 118 6.464844 117.996094 6.445312 C 117.984375 6.398438 117.984375 6.398438 117.960938 6.351562 C 117.902344 6.332031 117.847656 6.316406 117.789062 6.300781 C 117.765625 6.296875 117.765625 6.296875 117.738281 6.289062 C 117.339844 6.1875 116.835938 6.167969 116.464844 6.375 C 116.296875 6.480469 116.203125 6.609375 116.128906 6.789062 C 116.082031 7.042969 116.105469 7.261719 116.234375 7.484375 C 116.496094 7.828125 117.082031 7.953125 117.46875 8.070312 C 118.183594 8.289062 118.960938 8.597656 119.359375 9.277344 C 119.578125 9.714844 119.621094 10.257812 119.496094 10.734375 C 119.316406 11.277344 118.957031 11.703125 118.449219 11.964844 C 117.460938 12.445312 116.246094 12.394531 115.222656 12.054688 C 115.007812 11.972656 114.804688 11.867188 114.601562 11.765625 C 114.570312 11.234375 114.574219 10.707031 114.574219 10.175781 C 114.886719 10.175781 115.195312 10.175781 115.511719 10.175781 C 115.539062 10.304688 115.539062 10.304688 115.5625 10.433594 C 115.582031 10.515625 115.597656 10.597656 115.613281 10.679688 C 115.625 10.734375 115.636719 10.792969 115.648438 10.847656 C 115.664062 10.929688 115.679688 11.011719 115.695312 11.09375 C 115.703125 11.121094 115.707031 11.144531 115.710938 11.171875 C 115.71875 11.195312 115.722656 11.21875 115.726562 11.242188 C 115.730469 11.261719 115.734375 11.285156 115.738281 11.304688 C 115.75 11.355469 115.75 11.355469 115.777344 11.40625 C 116.324219 11.617188 117.03125 11.667969 117.578125 11.4375 C 117.800781 11.332031 117.949219 11.207031 118.039062 10.972656 C 118.089844 10.761719 118.082031 10.527344 117.992188 10.328125 C 117.910156 10.191406 117.820312 10.105469 117.6875 10.023438 C 117.664062 10.003906 117.636719 9.988281 117.609375 9.972656 C 117.265625 9.769531 116.875 9.65625 116.496094 9.527344 C 116.066406 9.386719 115.683594 9.222656 115.320312 8.949219 C 115.296875 8.933594 115.273438 8.917969 115.25 8.898438 C 115.226562 8.875 115.226562 8.875 115.199219 8.855469 C 115.199219 8.839844 115.199219 8.824219 115.199219 8.804688 C 115.1875 8.800781 115.171875 8.796875 115.160156 8.789062 C 114.933594 8.65625 114.792969 8.285156 114.726562 8.046875 C 114.605469 7.507812 114.6875 6.964844 114.984375 6.496094 C 115.347656 5.957031 115.902344 5.671875 116.523438 5.542969 C 117.460938 5.367188 118.386719 5.574219 119.207031 6.039062 Z M 119.207031 6.039062 " + }, + "children": [] + }, + { + "type": "element", + "name": "path", + "attributes": { + "style": " stroke:none;fill-rule:nonzero;fill:rgb(10.196079%,11.372549%,14.117648%);fill-opacity:1;", + "d": "M 67.945312 6.109375 C 67.96875 6.136719 67.96875 6.136719 67.96875 6.1875 C 67.96875 6.210938 67.96875 6.234375 67.964844 6.257812 C 67.964844 6.285156 67.964844 6.3125 67.964844 6.339844 C 67.960938 6.367188 67.960938 6.398438 67.960938 6.425781 C 67.960938 6.457031 67.957031 6.484375 67.957031 6.515625 C 67.941406 6.863281 67.917969 7.207031 67.894531 7.554688 C 67.59375 7.554688 67.292969 7.554688 66.984375 7.554688 C 66.921875 7.3125 66.863281 7.070312 66.808594 6.828125 C 66.804688 6.796875 66.796875 6.769531 66.789062 6.742188 C 66.785156 6.714844 66.777344 6.6875 66.773438 6.660156 C 66.765625 6.632812 66.761719 6.609375 66.753906 6.585938 C 66.742188 6.519531 66.742188 6.519531 66.742188 6.425781 C 66.707031 6.414062 66.667969 6.402344 66.628906 6.390625 C 66.605469 6.386719 66.585938 6.378906 66.5625 6.371094 C 66.355469 6.320312 66.15625 6.296875 65.941406 6.296875 C 65.917969 6.296875 65.894531 6.296875 65.871094 6.296875 C 65.5625 6.300781 65.296875 6.351562 65.0625 6.566406 C 64.894531 6.742188 64.859375 6.90625 64.863281 7.148438 C 64.867188 7.285156 64.890625 7.390625 64.96875 7.507812 C 64.976562 7.519531 64.984375 7.535156 64.996094 7.550781 C 65.261719 7.921875 65.914062 8.0625 66.328125 8.179688 C 67.054688 8.390625 67.703125 8.726562 68.089844 9.40625 C 68.214844 9.648438 68.289062 9.90625 68.304688 10.175781 C 68.304688 10.199219 68.304688 10.21875 68.308594 10.242188 C 68.324219 10.753906 68.15625 11.1875 67.824219 11.574219 C 67.808594 11.589844 67.796875 11.605469 67.78125 11.621094 C 67.28125 12.164062 66.515625 12.347656 65.808594 12.390625 C 65.144531 12.414062 64.535156 12.34375 63.910156 12.117188 C 63.886719 12.109375 63.886719 12.109375 63.859375 12.097656 C 63.675781 12.03125 63.507812 11.929688 63.335938 11.835938 C 63.335938 11.632812 63.335938 11.429688 63.335938 11.226562 C 63.335938 11.132812 63.335938 11.039062 63.335938 10.945312 C 63.332031 10.851562 63.332031 10.761719 63.332031 10.671875 C 63.332031 10.636719 63.332031 10.601562 63.332031 10.566406 C 63.332031 10.515625 63.332031 10.46875 63.332031 10.421875 C 63.332031 10.390625 63.332031 10.363281 63.332031 10.335938 C 63.335938 10.273438 63.335938 10.273438 63.359375 10.25 C 63.421875 10.246094 63.484375 10.246094 63.550781 10.246094 C 63.570312 10.246094 63.585938 10.246094 63.605469 10.246094 C 63.648438 10.246094 63.6875 10.246094 63.726562 10.246094 C 63.789062 10.246094 63.851562 10.246094 63.914062 10.246094 C 63.953125 10.246094 63.992188 10.246094 64.03125 10.246094 C 64.050781 10.246094 64.066406 10.246094 64.085938 10.246094 C 64.21875 10.246094 64.21875 10.246094 64.273438 10.273438 C 64.285156 10.316406 64.285156 10.316406 64.296875 10.375 C 64.300781 10.394531 64.304688 10.414062 64.308594 10.4375 C 64.316406 10.460938 64.320312 10.484375 64.324219 10.507812 C 64.328125 10.53125 64.332031 10.554688 64.339844 10.578125 C 64.347656 10.628906 64.359375 10.679688 64.367188 10.730469 C 64.382812 10.808594 64.398438 10.882812 64.414062 10.960938 C 64.421875 11.007812 64.433594 11.058594 64.441406 11.105469 C 64.445312 11.128906 64.453125 11.152344 64.457031 11.175781 C 64.476562 11.285156 64.492188 11.386719 64.488281 11.5 C 64.53125 11.511719 64.53125 11.511719 64.578125 11.519531 C 64.691406 11.546875 64.804688 11.574219 64.921875 11.605469 C 65.117188 11.648438 65.308594 11.648438 65.507812 11.652344 C 65.539062 11.652344 65.570312 11.652344 65.601562 11.652344 C 65.964844 11.652344 66.320312 11.59375 66.605469 11.359375 C 66.761719 11.199219 66.820312 11.003906 66.828125 10.789062 C 66.820312 10.566406 66.761719 10.382812 66.601562 10.226562 C 66.214844 9.933594 65.765625 9.789062 65.3125 9.640625 C 64.621094 9.414062 63.949219 9.125 63.59375 8.445312 C 63.378906 8 63.375 7.464844 63.53125 6.996094 C 63.761719 6.410156 64.183594 6.027344 64.753906 5.773438 C 65.792969 5.351562 66.988281 5.59375 67.945312 6.109375 Z M 67.945312 6.109375 " + }, + "children": [] + }, + { + "type": "element", + "name": "path", + "attributes": { + "style": " stroke:none;fill-rule:nonzero;fill:rgb(10.196079%,11.372549%,14.117648%);fill-opacity:1;", + "d": "M 105.941406 5.769531 C 105.960938 5.777344 105.976562 5.785156 105.996094 5.792969 C 106.1875 5.867188 106.351562 5.964844 106.535156 6.0625 C 106.511719 6.539062 106.488281 7.015625 106.464844 7.507812 C 106.164062 7.507812 105.863281 7.507812 105.550781 7.507812 C 105.445312 7.101562 105.445312 7.101562 105.410156 6.941406 C 105.40625 6.925781 105.402344 6.910156 105.398438 6.890625 C 105.386719 6.839844 105.375 6.789062 105.367188 6.738281 C 105.359375 6.703125 105.351562 6.667969 105.34375 6.632812 C 105.324219 6.546875 105.304688 6.460938 105.289062 6.375 C 105.234375 6.359375 105.183594 6.34375 105.128906 6.328125 C 105.097656 6.320312 105.070312 6.3125 105.039062 6.304688 C 104.859375 6.253906 104.6875 6.25 104.503906 6.25 C 104.464844 6.25 104.464844 6.25 104.421875 6.25 C 104.136719 6.246094 103.90625 6.3125 103.664062 6.464844 C 103.507812 6.621094 103.417969 6.816406 103.414062 7.042969 C 103.425781 7.25 103.492188 7.433594 103.632812 7.585938 C 103.976562 7.886719 104.484375 8.003906 104.910156 8.132812 C 105.628906 8.351562 106.285156 8.667969 106.667969 9.355469 C 106.878906 9.800781 106.9375 10.371094 106.785156 10.84375 C 106.554688 11.417969 106.144531 11.8125 105.585938 12.070312 C 104.601562 12.488281 103.335938 12.402344 102.359375 12.007812 C 102.203125 11.9375 102.046875 11.859375 101.902344 11.765625 C 101.894531 11.699219 101.894531 11.699219 101.894531 11.617188 C 101.894531 11.585938 101.894531 11.554688 101.890625 11.523438 C 101.890625 11.488281 101.890625 11.453125 101.890625 11.417969 C 101.890625 11.382812 101.890625 11.347656 101.890625 11.3125 C 101.890625 11.222656 101.886719 11.128906 101.886719 11.039062 C 101.886719 10.925781 101.886719 10.816406 101.882812 10.707031 C 101.882812 10.539062 101.882812 10.371094 101.878906 10.203125 C 102.1875 10.203125 102.496094 10.203125 102.816406 10.203125 C 102.871094 10.363281 102.871094 10.363281 102.886719 10.449219 C 102.890625 10.46875 102.894531 10.488281 102.898438 10.507812 C 102.902344 10.527344 102.90625 10.546875 102.910156 10.566406 C 102.914062 10.585938 102.917969 10.609375 102.921875 10.628906 C 102.933594 10.671875 102.941406 10.71875 102.949219 10.761719 C 102.964844 10.828125 102.976562 10.894531 102.988281 10.960938 C 103 11.003906 103.007812 11.046875 103.015625 11.089844 C 103.019531 11.109375 103.023438 11.132812 103.027344 11.152344 C 103.046875 11.246094 103.0625 11.332031 103.054688 11.429688 C 103.699219 11.59375 104.421875 11.726562 105.03125 11.386719 C 105.21875 11.265625 105.316406 11.125 105.375 10.914062 C 105.402344 10.691406 105.378906 10.496094 105.273438 10.292969 C 104.921875 9.867188 104.363281 9.730469 103.859375 9.5625 C 103.1875 9.339844 102.511719 9.058594 102.167969 8.398438 C 101.949219 7.929688 101.9375 7.414062 102.097656 6.929688 C 102.101562 6.90625 102.109375 6.886719 102.117188 6.863281 C 102.269531 6.417969 102.628906 6.066406 103.03125 5.847656 C 103.054688 5.832031 103.078125 5.820312 103.101562 5.808594 C 103.382812 5.65625 103.699219 5.574219 104.015625 5.535156 C 104.035156 5.53125 104.054688 5.527344 104.074219 5.527344 C 104.714844 5.449219 105.34375 5.535156 105.941406 5.769531 Z M 105.941406 5.769531 " + }, + "children": [] + }, + { + "type": "element", + "name": "path", + "attributes": { + "style": " stroke:none;fill-rule:nonzero;fill:rgb(10.196079%,11.372549%,14.117648%);fill-opacity:1;", + "d": "M 59.953125 3.921875 C 60.316406 3.921875 60.679688 3.921875 61.054688 3.921875 C 61.058594 4.292969 61.054688 4.667969 61.046875 5.039062 C 61.042969 5.066406 61.042969 5.066406 61.042969 5.09375 C 61.042969 5.144531 61.042969 5.195312 61.039062 5.246094 C 61.039062 5.277344 61.039062 5.304688 61.035156 5.335938 C 61.03125 5.472656 61.019531 5.613281 61.007812 5.75 C 61.53125 5.75 62.054688 5.75 62.59375 5.75 C 62.59375 6.035156 62.59375 6.320312 62.59375 6.617188 C 62.0625 6.617188 61.53125 6.617188 60.984375 6.617188 C 60.984375 7.128906 60.988281 7.644531 60.988281 8.160156 C 60.992188 8.398438 60.992188 8.636719 60.992188 8.875 C 60.992188 9.082031 60.992188 9.292969 60.996094 9.5 C 60.996094 9.609375 60.996094 9.71875 60.996094 9.832031 C 60.996094 9.933594 60.996094 10.039062 60.996094 10.140625 C 60.996094 10.179688 60.996094 10.21875 60.996094 10.253906 C 60.992188 10.765625 60.992188 10.765625 61.199219 11.210938 C 61.34375 11.347656 61.507812 11.40625 61.703125 11.40625 C 61.941406 11.371094 62.144531 11.289062 62.355469 11.175781 C 62.457031 11.125 62.457031 11.125 62.519531 11.117188 C 62.558594 11.140625 62.558594 11.140625 62.597656 11.183594 C 62.613281 11.195312 62.625 11.210938 62.640625 11.226562 C 62.652344 11.238281 62.667969 11.253906 62.683594 11.269531 C 62.695312 11.285156 62.710938 11.300781 62.726562 11.316406 C 62.761719 11.351562 62.796875 11.390625 62.832031 11.429688 C 62.785156 11.5625 62.707031 11.65625 62.617188 11.765625 C 62.605469 11.78125 62.59375 11.792969 62.578125 11.808594 C 62.375 12.03125 62.085938 12.183594 61.800781 12.269531 C 61.777344 12.277344 61.75 12.285156 61.726562 12.292969 C 61.511719 12.347656 61.296875 12.351562 61.078125 12.351562 C 61.046875 12.351562 61.019531 12.351562 60.988281 12.351562 C 60.523438 12.351562 60.085938 12.210938 59.730469 11.898438 C 59.542969 11.699219 59.425781 11.40625 59.375 11.140625 C 59.371094 11.117188 59.367188 11.097656 59.363281 11.074219 C 59.351562 10.988281 59.347656 10.902344 59.347656 10.8125 C 59.347656 10.792969 59.347656 10.777344 59.347656 10.757812 C 59.347656 10.695312 59.347656 10.636719 59.347656 10.578125 C 59.347656 10.535156 59.347656 10.492188 59.347656 10.449219 C 59.347656 10.332031 59.347656 10.214844 59.347656 10.097656 C 59.351562 9.972656 59.351562 9.851562 59.351562 9.730469 C 59.351562 9.496094 59.351562 9.265625 59.351562 9.035156 C 59.351562 8.769531 59.351562 8.507812 59.351562 8.242188 C 59.351562 7.703125 59.351562 7.160156 59.351562 6.617188 C 59.035156 6.617188 58.71875 6.617188 58.390625 6.617188 C 58.390625 6.371094 58.390625 6.125 58.390625 5.871094 C 58.914062 5.800781 58.914062 5.800781 59.449219 5.726562 C 59.480469 5.601562 59.515625 5.476562 59.550781 5.351562 C 59.574219 5.269531 59.59375 5.191406 59.617188 5.113281 C 59.652344 4.988281 59.6875 4.863281 59.722656 4.738281 C 59.75 4.640625 59.777344 4.539062 59.804688 4.4375 C 59.816406 4.398438 59.824219 4.359375 59.835938 4.324219 C 59.851562 4.269531 59.867188 4.214844 59.882812 4.160156 C 59.886719 4.144531 59.890625 4.128906 59.894531 4.113281 C 59.914062 4.046875 59.929688 3.984375 59.953125 3.921875 Z M 59.953125 3.921875 " + }, + "children": [] + }, + { + "type": "element", + "name": "path", + "attributes": { + "style": " stroke:none;fill-rule:nonzero;fill:rgb(10.196079%,11.372549%,14.117648%);fill-opacity:1;", + "d": "M 93.351562 5.554688 C 93.40625 5.59375 93.429688 5.617188 93.445312 5.679688 C 93.449219 5.75 93.445312 5.8125 93.441406 5.878906 C 93.441406 5.90625 93.441406 5.929688 93.441406 5.957031 C 93.4375 6.015625 93.4375 6.070312 93.433594 6.128906 C 93.429688 6.273438 93.425781 6.417969 93.421875 6.5625 C 93.417969 6.617188 93.417969 6.671875 93.417969 6.726562 C 93.402344 7.175781 93.40625 7.625 93.40625 8.074219 C 93.40625 8.195312 93.40625 8.3125 93.40625 8.429688 C 93.40625 8.644531 93.40625 8.859375 93.40625 9.074219 C 93.40625 9.320312 93.40625 9.570312 93.40625 9.816406 C 93.40625 10.320312 93.40625 10.828125 93.40625 11.332031 C 93.425781 11.335938 93.449219 11.339844 93.46875 11.34375 C 93.542969 11.355469 93.613281 11.371094 93.6875 11.382812 C 93.734375 11.390625 93.785156 11.398438 93.832031 11.40625 C 93.859375 11.414062 93.890625 11.417969 93.921875 11.425781 C 93.949219 11.429688 93.976562 11.433594 94.003906 11.4375 C 94.070312 11.449219 94.136719 11.464844 94.199219 11.476562 C 94.199219 11.675781 94.199219 11.875 94.199219 12.078125 C 93.105469 12.078125 92.015625 12.078125 90.886719 12.078125 C 90.886719 11.878906 90.886719 11.679688 90.886719 11.476562 C 90.988281 11.457031 91.085938 11.433594 91.1875 11.414062 C 91.222656 11.40625 91.253906 11.402344 91.289062 11.394531 C 91.339844 11.382812 91.386719 11.375 91.4375 11.363281 C 91.480469 11.355469 91.480469 11.355469 91.527344 11.34375 C 91.601562 11.332031 91.675781 11.332031 91.753906 11.332031 C 91.753906 10.894531 91.753906 10.457031 91.753906 10.023438 C 91.753906 9.820312 91.753906 9.617188 91.753906 9.414062 C 91.753906 9.234375 91.753906 9.058594 91.753906 8.882812 C 91.753906 8.789062 91.753906 8.695312 91.753906 8.601562 C 91.753906 8.066406 91.746094 7.535156 91.726562 7 C 91.683594 6.996094 91.683594 6.996094 91.636719 6.988281 C 91.539062 6.976562 91.4375 6.964844 91.339844 6.953125 C 91.296875 6.945312 91.25 6.941406 91.207031 6.933594 C 91.144531 6.925781 91.082031 6.917969 91.019531 6.910156 C 90.988281 6.90625 90.988281 6.90625 90.957031 6.902344 C 90.820312 6.882812 90.820312 6.882812 90.792969 6.855469 C 90.789062 6.816406 90.789062 6.777344 90.789062 6.738281 C 90.789062 6.714844 90.789062 6.691406 90.789062 6.667969 C 90.789062 6.640625 90.789062 6.617188 90.789062 6.589844 C 90.789062 6.566406 90.789062 6.539062 90.789062 6.515625 C 90.792969 6.453125 90.792969 6.390625 90.792969 6.328125 C 90.96875 6.253906 91.148438 6.1875 91.328125 6.125 C 91.355469 6.117188 91.382812 6.105469 91.414062 6.097656 C 91.503906 6.066406 91.59375 6.03125 91.683594 6 C 91.808594 5.957031 91.929688 5.914062 92.054688 5.871094 C 92.070312 5.867188 92.085938 5.859375 92.101562 5.855469 C 92.285156 5.792969 92.46875 5.726562 92.652344 5.660156 C 92.667969 5.652344 92.6875 5.644531 92.703125 5.640625 C 92.78125 5.609375 92.859375 5.582031 92.9375 5.554688 C 92.976562 5.539062 92.976562 5.539062 93.019531 5.523438 C 93.050781 5.511719 93.050781 5.511719 93.082031 5.5 C 93.1875 5.476562 93.261719 5.503906 93.351562 5.554688 Z M 93.351562 5.554688 " + }, + "children": [] + }, + { + "type": "element", + "name": "path", + "attributes": { + "style": " stroke:none;fill-rule:nonzero;fill:rgb(10.196079%,11.372549%,14.117648%);fill-opacity:1;", + "d": "M 42.214844 5.652344 C 42.214844 7.542969 42.214844 9.433594 42.214844 11.378906 C 42.535156 11.441406 42.535156 11.441406 42.863281 11.5 C 42.917969 11.515625 42.976562 11.53125 43.03125 11.550781 C 43.03125 11.738281 43.03125 11.929688 43.03125 12.125 C 41.929688 12.125 40.832031 12.125 39.695312 12.125 C 39.695312 11.9375 39.695312 11.746094 39.695312 11.550781 C 39.875 11.5 40.054688 11.460938 40.234375 11.425781 C 40.265625 11.417969 40.265625 11.417969 40.296875 11.410156 C 40.316406 11.40625 40.335938 11.402344 40.355469 11.398438 C 40.375 11.394531 40.394531 11.390625 40.410156 11.386719 C 40.46875 11.378906 40.523438 11.378906 40.585938 11.378906 C 40.582031 10.886719 40.578125 10.390625 40.574219 9.898438 C 40.574219 9.667969 40.574219 9.4375 40.570312 9.207031 C 40.570312 9.007812 40.570312 8.808594 40.570312 8.605469 C 40.566406 8.5 40.566406 8.394531 40.566406 8.289062 C 40.566406 8.191406 40.566406 8.089844 40.566406 7.988281 C 40.566406 7.953125 40.566406 7.917969 40.566406 7.878906 C 40.5625 7.683594 40.558594 7.488281 40.550781 7.292969 C 40.546875 7.273438 40.546875 7.253906 40.546875 7.234375 C 40.542969 7.140625 40.542969 7.140625 40.511719 7.050781 C 40.445312 7.035156 40.378906 7.027344 40.308594 7.019531 C 40.289062 7.015625 40.269531 7.011719 40.25 7.011719 C 40.183594 7.003906 40.121094 6.996094 40.054688 6.988281 C 40.011719 6.980469 39.96875 6.976562 39.921875 6.96875 C 39.816406 6.957031 39.707031 6.941406 39.601562 6.929688 C 39.601562 6.746094 39.601562 6.5625 39.601562 6.375 C 39.964844 6.242188 40.328125 6.109375 40.695312 5.980469 C 40.777344 5.953125 40.859375 5.921875 40.945312 5.894531 C 41 5.875 41.054688 5.855469 41.109375 5.835938 C 41.246094 5.789062 41.386719 5.738281 41.523438 5.6875 C 41.550781 5.675781 41.578125 5.667969 41.605469 5.65625 C 41.660156 5.636719 41.710938 5.617188 41.761719 5.597656 C 41.785156 5.589844 41.808594 5.578125 41.835938 5.570312 C 41.863281 5.558594 41.863281 5.558594 41.894531 5.546875 C 42.027344 5.515625 42.09375 5.585938 42.214844 5.652344 Z M 42.214844 5.652344 " + }, + "children": [] + }, + { + "type": "element", + "name": "path", + "attributes": { + "style": " stroke:none;fill-rule:nonzero;fill:rgb(98.823529%,73.725492%,19.607843%);fill-opacity:1;", + "d": "M 8.640625 9.261719 C 8.972656 9.519531 9.191406 9.859375 9.277344 10.273438 C 9.328125 10.675781 9.265625 11.089844 9.019531 11.421875 C 8.734375 11.769531 8.371094 12.007812 7.917969 12.058594 C 7.476562 12.09375 7.078125 11.957031 6.742188 11.667969 C 6.71875 11.648438 6.71875 11.648438 6.695312 11.628906 C 6.421875 11.378906 6.261719 10.992188 6.234375 10.628906 C 6.226562 10.179688 6.355469 9.789062 6.664062 9.457031 C 7.191406 8.921875 8.019531 8.84375 8.640625 9.261719 Z M 8.640625 9.261719 " + }, + "children": [] + }, + { + "type": "element", + "name": "path", + "attributes": { + "style": " stroke:none;fill-rule:nonzero;fill:rgb(98.823529%,73.725492%,19.607843%);fill-opacity:1;", + "d": "M 2.855469 4.089844 C 2.941406 4.15625 3.019531 4.230469 3.097656 4.308594 C 3.113281 4.324219 3.128906 4.339844 3.148438 4.359375 C 3.414062 4.640625 3.542969 5.027344 3.539062 5.410156 C 3.519531 5.851562 3.332031 6.21875 3.015625 6.527344 C 2.707031 6.792969 2.304688 6.921875 1.898438 6.898438 C 1.578125 6.871094 1.308594 6.769531 1.054688 6.570312 C 1.03125 6.546875 1.03125 6.546875 1.003906 6.527344 C 0.699219 6.277344 0.527344 5.898438 0.484375 5.511719 C 0.453125 5.121094 0.558594 4.730469 0.804688 4.425781 C 1.003906 4.191406 1.226562 4.03125 1.511719 3.921875 C 1.53125 3.914062 1.550781 3.90625 1.570312 3.898438 C 1.988281 3.757812 2.496094 3.84375 2.855469 4.089844 Z M 2.855469 4.089844 " + }, + "children": [] + }, + { + "type": "element", + "name": "path", + "attributes": { + "style": " stroke:none;fill-rule:nonzero;fill:rgb(98.823529%,73.725492%,19.607843%);fill-opacity:1;", + "d": "M 2.960938 11.683594 C 3.269531 11.949219 3.492188 12.316406 3.53125 12.726562 C 3.558594 13.152344 3.449219 13.550781 3.171875 13.878906 C 2.875 14.203125 2.519531 14.375 2.082031 14.417969 C 1.671875 14.433594 1.28125 14.28125 0.972656 14.011719 C 0.660156 13.714844 0.488281 13.324219 0.476562 12.890625 C 0.480469 12.53125 0.59375 12.214844 0.816406 11.933594 C 0.828125 11.917969 0.839844 11.902344 0.851562 11.882812 C 1.078125 11.597656 1.433594 11.421875 1.785156 11.363281 C 2.207031 11.316406 2.628906 11.417969 2.960938 11.683594 Z M 2.960938 11.683594 " + }, + "children": [] + }, + { + "type": "element", + "name": "path", + "attributes": { + "style": " stroke:none;fill-rule:nonzero;fill:rgb(98.823529%,73.725492%,19.607843%);fill-opacity:1;", + "d": "M 14.449219 4.167969 C 14.507812 4.222656 14.5625 4.273438 14.617188 4.332031 C 14.628906 4.34375 14.640625 4.355469 14.65625 4.367188 C 14.914062 4.632812 15.015625 5.023438 15.03125 5.378906 C 15.019531 5.734375 14.902344 6.046875 14.6875 6.328125 C 14.675781 6.34375 14.664062 6.359375 14.652344 6.378906 C 14.410156 6.679688 14.042969 6.851562 13.664062 6.898438 C 13.242188 6.925781 12.84375 6.816406 12.523438 6.535156 C 12.484375 6.5 12.445312 6.460938 12.40625 6.425781 C 12.394531 6.410156 12.378906 6.394531 12.363281 6.378906 C 12.085938 6.089844 11.988281 5.707031 11.992188 5.316406 C 12.003906 4.914062 12.167969 4.53125 12.457031 4.25 C 13.023438 3.75 13.847656 3.714844 14.449219 4.167969 Z M 14.449219 4.167969 " + }, + "children": [] + }, + { + "type": "element", + "name": "path", + "attributes": { + "style": " stroke:none;fill-rule:nonzero;fill:rgb(10.196079%,11.372549%,14.117648%);fill-opacity:1;", + "d": "M 42.046875 2.648438 C 42.253906 2.816406 42.375 3.03125 42.40625 3.296875 C 42.433594 3.558594 42.351562 3.808594 42.191406 4.019531 C 42.007812 4.214844 41.785156 4.351562 41.507812 4.363281 C 41.46875 4.363281 41.429688 4.363281 41.390625 4.363281 C 41.371094 4.363281 41.351562 4.363281 41.332031 4.363281 C 41.058594 4.355469 40.832031 4.273438 40.625 4.089844 C 40.476562 3.933594 40.359375 3.734375 40.34375 3.511719 C 40.34375 3.496094 40.339844 3.480469 40.339844 3.460938 C 40.328125 3.230469 40.390625 2.992188 40.542969 2.8125 C 40.554688 2.796875 40.570312 2.78125 40.585938 2.765625 C 40.597656 2.75 40.613281 2.734375 40.632812 2.714844 C 41.019531 2.339844 41.621094 2.332031 42.046875 2.648438 Z M 42.046875 2.648438 " + }, + "children": [] + }, + { + "type": "element", + "name": "path", + "attributes": { + "style": " stroke:none;fill-rule:nonzero;fill:rgb(10.196079%,11.372549%,14.117648%);fill-opacity:1;", + "d": "M 93.210938 2.566406 C 93.453125 2.757812 93.570312 2.96875 93.609375 3.273438 C 93.621094 3.53125 93.550781 3.773438 93.378906 3.972656 C 93.367188 3.988281 93.351562 4.003906 93.335938 4.019531 C 93.316406 4.039062 93.316406 4.039062 93.296875 4.058594 C 93.066406 4.28125 92.78125 4.316406 92.476562 4.3125 C 92.355469 4.308594 92.25 4.285156 92.136719 4.234375 C 92.117188 4.226562 92.101562 4.21875 92.082031 4.210938 C 91.871094 4.105469 91.703125 3.9375 91.605469 3.722656 C 91.515625 3.449219 91.515625 3.171875 91.632812 2.90625 C 91.773438 2.652344 92 2.484375 92.277344 2.402344 C 92.605469 2.324219 92.933594 2.375 93.210938 2.566406 Z M 93.210938 2.566406 " + }, + "children": [] + }, + { + "type": "element", + "name": "path", + "attributes": { + "style": " stroke:none;fill-rule:nonzero;fill:rgb(98.823529%,73.725492%,19.607843%);fill-opacity:1;", + "d": "M 8.320312 5.960938 C 8.539062 6.117188 8.664062 6.320312 8.726562 6.582031 C 8.769531 6.839844 8.710938 7.089844 8.574219 7.3125 C 8.410156 7.535156 8.207031 7.65625 7.945312 7.722656 C 7.621094 7.753906 7.355469 7.6875 7.105469 7.484375 C 6.921875 7.3125 6.8125 7.078125 6.792969 6.832031 C 6.789062 6.535156 6.871094 6.28125 7.078125 6.0625 C 7.4375 5.738281 7.914062 5.710938 8.320312 5.960938 Z M 8.320312 5.960938 " + }, + "children": [] + }, + { + "type": "element", + "name": "path", + "attributes": { + "style": " stroke:none;fill-rule:nonzero;fill:rgb(98.431373%,73.725492%,19.607843%);fill-opacity:1;", + "d": "M 14.09375 0.820312 C 14.289062 0.96875 14.421875 1.179688 14.472656 1.417969 C 14.5 1.714844 14.464844 1.980469 14.273438 2.214844 C 14.082031 2.421875 13.890625 2.558594 13.605469 2.582031 C 13.320312 2.585938 13.078125 2.535156 12.863281 2.332031 C 12.660156 2.128906 12.550781 1.910156 12.542969 1.621094 C 12.546875 1.332031 12.644531 1.117188 12.839844 0.910156 C 13.199219 0.574219 13.6875 0.5625 14.09375 0.820312 Z M 14.09375 0.820312 " + }, + "children": [] + }, + { + "type": "element", + "name": "path", + "attributes": { + "style": " stroke:none;fill-rule:nonzero;fill:rgb(98.823529%,73.725492%,19.607843%);fill-opacity:1;", + "d": "M 2.574219 0.808594 C 2.769531 0.972656 2.925781 1.164062 2.976562 1.417969 C 2.996094 1.71875 2.972656 1.976562 2.777344 2.214844 C 2.585938 2.421875 2.394531 2.558594 2.109375 2.582031 C 1.824219 2.585938 1.582031 2.53125 1.367188 2.332031 C 1.226562 2.195312 1.136719 2.066406 1.078125 1.875 C 1.074219 1.863281 1.070312 1.847656 1.066406 1.832031 C 1.015625 1.570312 1.054688 1.3125 1.195312 1.089844 C 1.515625 0.644531 2.101562 0.484375 2.574219 0.808594 Z M 2.574219 0.808594 " + }, + "children": [] + }, + { + "type": "element", + "name": "path", + "attributes": { + "style": " stroke:none;fill-rule:nonzero;fill:rgb(98.823529%,73.725492%,19.607843%);fill-opacity:1;", + "d": "M 2.550781 8.316406 C 2.582031 8.34375 2.609375 8.371094 2.640625 8.398438 C 2.65625 8.414062 2.671875 8.429688 2.691406 8.445312 C 2.878906 8.640625 2.960938 8.847656 2.964844 9.121094 C 2.960938 9.398438 2.882812 9.613281 2.6875 9.816406 C 2.5 9.996094 2.257812 10.109375 1.992188 10.105469 C 1.695312 10.085938 1.449219 9.972656 1.246094 9.753906 C 1.074219 9.535156 1.003906 9.277344 1.03125 9 C 1.089844 8.710938 1.214844 8.484375 1.453125 8.3125 C 1.789062 8.105469 2.222656 8.085938 2.550781 8.316406 Z M 2.550781 8.316406 " + }, + "children": [] + }, + { + "type": "element", + "name": "path", + "attributes": { + "style": " stroke:none;fill-rule:nonzero;fill:rgb(98.823529%,73.725492%,19.607843%);fill-opacity:1;", + "d": "M 14.058594 8.34375 C 14.269531 8.496094 14.425781 8.714844 14.472656 8.972656 C 14.496094 9.300781 14.441406 9.542969 14.238281 9.804688 C 14.148438 9.90625 14.042969 9.972656 13.921875 10.03125 C 13.894531 10.046875 13.894531 10.046875 13.867188 10.058594 C 13.660156 10.144531 13.386719 10.136719 13.175781 10.066406 C 12.929688 9.960938 12.714844 9.769531 12.613281 9.519531 C 12.601562 9.484375 12.585938 9.445312 12.574219 9.40625 C 12.570312 9.390625 12.566406 9.378906 12.5625 9.363281 C 12.511719 9.109375 12.550781 8.855469 12.679688 8.632812 C 12.824219 8.421875 13.003906 8.277344 13.246094 8.203125 C 13.261719 8.199219 13.277344 8.195312 13.292969 8.191406 C 13.570312 8.136719 13.820312 8.195312 14.058594 8.34375 Z M 14.058594 8.34375 " + }, + "children": [] + }, + { + "type": "element", + "name": "path", + "attributes": { + "style": " stroke:none;fill-rule:nonzero;fill:rgb(98.823529%,73.725492%,19.607843%);fill-opacity:1;", + "d": "M 8.375 13.523438 C 8.605469 13.730469 8.71875 13.984375 8.742188 14.289062 C 8.734375 14.554688 8.621094 14.789062 8.445312 14.984375 C 8.25 15.164062 7.996094 15.25 7.734375 15.253906 C 7.46875 15.242188 7.25 15.136719 7.0625 14.953125 C 6.863281 14.730469 6.789062 14.488281 6.792969 14.195312 C 6.832031 13.894531 6.964844 13.664062 7.199219 13.472656 C 7.582031 13.230469 8.015625 13.25 8.375 13.523438 Z M 8.375 13.523438 " + }, + "children": [] + }, + { + "type": "element", + "name": "path", + "attributes": { + "style": " stroke:none;fill-rule:nonzero;fill:rgb(98.823529%,73.725492%,19.607843%);fill-opacity:1;", + "d": "M 14.027344 12.066406 C 14.25 12.234375 14.421875 12.449219 14.472656 12.726562 C 14.492188 13.019531 14.464844 13.269531 14.273438 13.5 C 14.089844 13.699219 13.886719 13.84375 13.609375 13.863281 C 13.3125 13.867188 13.058594 13.800781 12.832031 13.589844 C 12.730469 13.480469 12.65625 13.371094 12.601562 13.234375 C 12.589844 13.207031 12.582031 13.183594 12.570312 13.15625 C 12.519531 12.886719 12.539062 12.625 12.679688 12.386719 C 12.984375 11.945312 13.558594 11.765625 14.027344 12.066406 Z M 14.027344 12.066406 " + }, + "children": [] + }, + { + "type": "element", + "name": "path", + "attributes": { + "style": " stroke:none;fill-rule:nonzero;fill:rgb(98.823529%,73.725492%,19.607843%);fill-opacity:1;", + "d": "M 8.269531 2.203125 C 8.492188 2.371094 8.652344 2.574219 8.703125 2.855469 C 8.738281 3.113281 8.695312 3.371094 8.535156 3.582031 C 8.355469 3.796875 8.136719 3.949219 7.851562 3.976562 C 7.539062 3.988281 7.289062 3.894531 7.054688 3.679688 C 6.851562 3.449219 6.796875 3.222656 6.808594 2.914062 C 6.824219 2.671875 6.929688 2.480469 7.105469 2.308594 C 7.445312 2.039062 7.886719 1.964844 8.269531 2.203125 Z M 8.269531 2.203125 " + }, + "children": [] + } + ] + } + ] + }, + "name": "WeaveIcon" +} diff --git a/web/app/components/base/icons/src/public/tracing/WeaveIcon.tsx b/web/app/components/base/icons/src/public/tracing/WeaveIcon.tsx new file mode 100644 index 0000000000..fd66bd79ae --- /dev/null +++ b/web/app/components/base/icons/src/public/tracing/WeaveIcon.tsx @@ -0,0 +1,16 @@ +// GENERATE BY script +// DON NOT EDIT IT MANUALLY + +import * as React from 'react' +import data from './WeaveIcon.json' +import IconBase from '@/app/components/base/icons/IconBase' +import type { IconBaseProps, IconData } from '@/app/components/base/icons/IconBase' + +const Icon = React.forwardRef<React.MutableRefObject<SVGElement>, Omit<IconBaseProps, 'data'>>(( + props, + ref, +) => <IconBase {...props} ref={ref} data={data as IconData} />) + +Icon.displayName = 'WeaveIcon' + +export default Icon diff --git a/web/app/components/base/icons/src/public/tracing/WeaveIconBig.json b/web/app/components/base/icons/src/public/tracing/WeaveIconBig.json new file mode 100644 index 0000000000..5557e237e0 --- /dev/null +++ b/web/app/components/base/icons/src/public/tracing/WeaveIconBig.json @@ -0,0 +1,279 @@ +{ + "icon": { + "type": "element", + "isRootNode": true, + "name": "svg", + "attributes": { + "xmlns": "http://www.w3.org/2000/svg", + "xmlns:xlink": "http://www.w3.org/1999/xlink", + "width": "124px", + "height": "16px", + "viewBox": "0 0 120 16", + "version": "1.1" + }, + "children": [ + { + "type": "element", + "name": "g", + "attributes": { + "id": "surface1" + }, + "children": [ + { + "type": "element", + "name": "path", + "attributes": { + "style": " stroke:none;fill-rule:nonzero;fill:rgb(10.196079%,11.372549%,14.117648%);fill-opacity:1;", + "d": "M 20.847656 3.292969 C 20.875 3.292969 20.902344 3.292969 20.933594 3.292969 C 20.949219 3.292969 20.964844 3.292969 20.980469 3.292969 C 21.035156 3.292969 21.089844 3.292969 21.140625 3.292969 C 21.179688 3.292969 21.21875 3.292969 21.253906 3.292969 C 21.359375 3.292969 21.464844 3.292969 21.566406 3.292969 C 21.675781 3.292969 21.78125 3.292969 21.890625 3.292969 C 22.097656 3.292969 22.300781 3.292969 22.507812 3.292969 C 22.738281 3.292969 22.972656 3.292969 23.207031 3.296875 C 23.6875 3.296875 24.167969 3.296875 24.648438 3.296875 C 24.648438 3.519531 24.648438 3.742188 24.648438 3.96875 C 24.113281 4.042969 24.113281 4.042969 23.566406 4.113281 C 23.667969 4.496094 23.769531 4.882812 23.867188 5.265625 C 23.878906 5.308594 23.878906 5.308594 23.890625 5.351562 C 24.128906 6.269531 24.371094 7.183594 24.609375 8.097656 C 24.675781 8.339844 24.738281 8.582031 24.800781 8.824219 C 24.816406 8.878906 24.832031 8.933594 24.84375 8.992188 C 24.867188 9.078125 24.890625 9.167969 24.914062 9.257812 C 24.921875 9.289062 24.933594 9.320312 24.941406 9.355469 C 24.953125 9.398438 24.964844 9.441406 24.976562 9.484375 C 24.984375 9.523438 24.984375 9.523438 24.996094 9.558594 C 25.007812 9.625 25.007812 9.625 25.007812 9.71875 C 25.023438 9.71875 25.039062 9.71875 25.054688 9.71875 C 25.058594 9.707031 25.058594 9.695312 25.0625 9.679688 C 25.097656 9.492188 25.152344 9.3125 25.210938 9.128906 C 25.222656 9.097656 25.234375 9.0625 25.246094 9.027344 C 25.269531 8.953125 25.292969 8.882812 25.316406 8.808594 C 25.355469 8.691406 25.390625 8.574219 25.429688 8.457031 C 25.464844 8.339844 25.503906 8.21875 25.542969 8.097656 C 25.660156 7.738281 25.773438 7.375 25.890625 7.011719 C 25.902344 6.96875 25.917969 6.921875 25.933594 6.875 C 26.226562 5.945312 26.519531 5.019531 26.808594 4.089844 C 26.785156 4.089844 26.765625 4.089844 26.742188 4.085938 C 26.507812 4.074219 26.273438 4.046875 26.042969 4.015625 C 26.007812 4.011719 25.972656 4.007812 25.933594 4.003906 C 25.851562 3.992188 25.765625 3.980469 25.679688 3.96875 C 25.679688 3.746094 25.679688 3.523438 25.679688 3.296875 C 26.175781 3.296875 26.667969 3.296875 27.160156 3.296875 C 27.390625 3.292969 27.621094 3.292969 27.851562 3.292969 C 28.050781 3.292969 28.25 3.292969 28.449219 3.292969 C 28.554688 3.292969 28.660156 3.292969 28.765625 3.292969 C 28.867188 3.292969 28.964844 3.292969 29.066406 3.292969 C 29.101562 3.292969 29.140625 3.292969 29.175781 3.292969 C 29.226562 3.292969 29.273438 3.292969 29.324219 3.292969 C 29.367188 3.292969 29.367188 3.292969 29.410156 3.292969 C 29.472656 3.296875 29.472656 3.296875 29.496094 3.320312 C 29.5 3.367188 29.5 3.417969 29.5 3.464844 C 29.5 3.492188 29.5 3.515625 29.5 3.542969 C 29.496094 3.59375 29.496094 3.59375 29.496094 3.648438 C 29.496094 3.753906 29.496094 3.859375 29.496094 3.96875 C 29.09375 4.015625 28.6875 4.066406 28.273438 4.113281 C 28.679688 5.460938 28.679688 5.460938 29.089844 6.808594 C 29.105469 6.859375 29.121094 6.910156 29.136719 6.960938 C 29.234375 7.292969 29.335938 7.625 29.4375 7.960938 C 29.484375 8.113281 29.53125 8.265625 29.578125 8.417969 C 29.605469 8.507812 29.632812 8.597656 29.660156 8.691406 C 29.878906 9.40625 29.878906 9.40625 29.976562 9.746094 C 30.027344 9.664062 30.046875 9.601562 30.070312 9.507812 C 30.078125 9.484375 30.078125 9.484375 30.085938 9.457031 C 30.101562 9.402344 30.117188 9.34375 30.132812 9.289062 C 30.144531 9.25 30.152344 9.207031 30.164062 9.167969 C 30.1875 9.082031 30.214844 8.992188 30.238281 8.90625 C 30.292969 8.691406 30.351562 8.480469 30.410156 8.269531 C 30.433594 8.191406 30.453125 8.117188 30.472656 8.042969 C 30.621094 7.5 30.769531 6.960938 30.921875 6.421875 C 30.949219 6.324219 30.976562 6.226562 31 6.128906 C 31.066406 5.902344 31.128906 5.675781 31.191406 5.449219 C 31.230469 5.308594 31.269531 5.164062 31.308594 5.023438 C 31.335938 4.925781 31.363281 4.828125 31.390625 4.734375 C 31.402344 4.6875 31.414062 4.640625 31.429688 4.59375 C 31.445312 4.53125 31.464844 4.46875 31.480469 4.40625 C 31.488281 4.386719 31.492188 4.367188 31.496094 4.347656 C 31.515625 4.277344 31.535156 4.207031 31.558594 4.136719 C 31.210938 4.074219 30.855469 4.023438 30.503906 3.96875 C 30.503906 3.746094 30.503906 3.523438 30.503906 3.296875 C 30.878906 3.296875 31.253906 3.296875 31.628906 3.296875 C 31.804688 3.292969 31.976562 3.292969 32.152344 3.292969 C 32.304688 3.292969 32.457031 3.292969 32.605469 3.292969 C 32.6875 3.292969 32.769531 3.292969 32.847656 3.292969 C 32.9375 3.292969 33.027344 3.292969 33.117188 3.292969 C 33.144531 3.292969 33.171875 3.292969 33.199219 3.292969 C 33.222656 3.292969 33.246094 3.292969 33.273438 3.292969 C 33.304688 3.292969 33.304688 3.292969 33.335938 3.292969 C 33.382812 3.296875 33.382812 3.296875 33.40625 3.320312 C 33.410156 3.367188 33.410156 3.414062 33.410156 3.460938 C 33.410156 3.488281 33.410156 3.515625 33.410156 3.542969 C 33.410156 3.574219 33.410156 3.605469 33.410156 3.632812 C 33.410156 3.664062 33.410156 3.695312 33.410156 3.726562 C 33.410156 3.796875 33.410156 3.871094 33.40625 3.945312 C 33.292969 3.964844 33.175781 3.984375 33.0625 4.007812 C 33.023438 4.011719 32.984375 4.019531 32.945312 4.027344 C 32.738281 4.0625 32.535156 4.097656 32.328125 4.113281 C 32.320312 4.144531 32.320312 4.144531 32.3125 4.179688 C 32.238281 4.480469 32.15625 4.78125 32.070312 5.082031 C 32.058594 5.128906 32.042969 5.171875 32.03125 5.21875 C 31.875 5.78125 31.714844 6.347656 31.550781 6.910156 C 31.375 7.535156 31.195312 8.160156 31.019531 8.785156 C 30.992188 8.871094 30.96875 8.957031 30.945312 9.042969 C 30.835938 9.433594 30.722656 9.820312 30.613281 10.210938 C 30.566406 10.378906 30.519531 10.542969 30.472656 10.707031 C 30.445312 10.804688 30.417969 10.902344 30.390625 11 C 30.277344 11.390625 30.167969 11.785156 30.046875 12.175781 C 29.730469 12.175781 29.414062 12.175781 29.089844 12.175781 C 29.03125 12.003906 29.03125 12.003906 28.976562 11.832031 C 28.925781 11.675781 28.878906 11.523438 28.828125 11.367188 C 28.820312 11.347656 28.8125 11.328125 28.808594 11.304688 C 28.632812 10.769531 28.460938 10.230469 28.285156 9.695312 C 28.144531 9.273438 28.007812 8.847656 27.875 8.425781 C 27.695312 7.867188 27.515625 7.308594 27.332031 6.753906 C 27.304688 6.679688 27.28125 6.605469 27.257812 6.53125 C 27.238281 6.476562 27.222656 6.425781 27.207031 6.375 C 27.046875 5.894531 27.046875 5.894531 27.046875 5.796875 C 27.03125 5.796875 27.015625 5.796875 27 5.796875 C 26.996094 5.8125 26.996094 5.828125 26.992188 5.84375 C 26.964844 5.988281 26.925781 6.132812 26.882812 6.273438 C 26.875 6.296875 26.867188 6.316406 26.859375 6.339844 C 26.84375 6.390625 26.828125 6.4375 26.8125 6.488281 C 26.769531 6.625 26.726562 6.761719 26.683594 6.898438 C 26.675781 6.929688 26.664062 6.957031 26.65625 6.988281 C 26.546875 7.328125 26.445312 7.667969 26.339844 8.007812 C 26.316406 8.078125 26.296875 8.144531 26.273438 8.214844 C 26.230469 8.355469 26.1875 8.496094 26.144531 8.636719 C 26.074219 8.863281 26.007812 9.089844 25.9375 9.3125 C 25.933594 9.328125 25.925781 9.347656 25.921875 9.363281 C 25.894531 9.449219 25.871094 9.535156 25.84375 9.617188 C 25.796875 9.769531 25.75 9.921875 25.703125 10.074219 C 25.675781 10.15625 25.652344 10.242188 25.625 10.328125 C 25.613281 10.363281 25.605469 10.394531 25.59375 10.429688 C 25.414062 11.011719 25.234375 11.59375 25.054688 12.175781 C 24.738281 12.175781 24.421875 12.175781 24.097656 12.175781 C 23.816406 11.230469 23.535156 10.285156 23.261719 9.339844 C 23.253906 9.320312 23.25 9.304688 23.246094 9.285156 C 23.195312 9.117188 23.144531 8.949219 23.097656 8.78125 C 22.960938 8.3125 22.824219 7.84375 22.6875 7.375 C 22.664062 7.304688 22.644531 7.234375 22.625 7.164062 C 22.414062 6.449219 22.207031 5.738281 22 5.027344 C 21.976562 4.953125 21.953125 4.878906 21.933594 4.804688 C 21.898438 4.683594 21.859375 4.5625 21.824219 4.441406 C 21.820312 4.421875 21.8125 4.402344 21.808594 4.382812 C 21.796875 4.347656 21.785156 4.3125 21.777344 4.28125 C 21.753906 4.203125 21.742188 4.148438 21.742188 4.066406 C 21.726562 4.066406 21.710938 4.0625 21.691406 4.0625 C 21.382812 4.042969 21.070312 4.003906 20.761719 3.96875 C 20.757812 3.863281 20.757812 3.753906 20.757812 3.648438 C 20.757812 3.617188 20.757812 3.585938 20.757812 3.554688 C 20.757812 3.523438 20.757812 3.496094 20.757812 3.464844 C 20.757812 3.4375 20.757812 3.410156 20.757812 3.382812 C 20.761719 3.296875 20.761719 3.296875 20.847656 3.292969 Z M 20.847656 3.292969 " + }, + "children": [] + }, + { + "type": "element", + "name": "path", + "attributes": { + "style": " stroke:none;fill-rule:nonzero;fill:rgb(10.196079%,11.372549%,14.117648%);fill-opacity:1;", + "d": "M 82.488281 3.25 C 83.046875 3.246094 83.605469 3.246094 84.167969 3.246094 C 84.425781 3.246094 84.6875 3.246094 84.945312 3.246094 C 85.171875 3.242188 85.398438 3.242188 85.625 3.242188 C 85.746094 3.242188 85.867188 3.242188 85.984375 3.242188 C 88.15625 3.238281 88.15625 3.238281 88.894531 3.898438 C 88.914062 3.914062 88.9375 3.929688 88.957031 3.945312 C 89.191406 4.144531 89.363281 4.402344 89.472656 4.691406 C 89.480469 4.714844 89.492188 4.742188 89.5 4.765625 C 89.65625 5.25 89.601562 5.785156 89.382812 6.234375 C 89.117188 6.753906 88.695312 7.078125 88.152344 7.265625 C 87.984375 7.320312 87.816406 7.367188 87.648438 7.410156 C 87.664062 7.414062 87.679688 7.417969 87.699219 7.421875 C 88.523438 7.605469 89.300781 7.851562 89.78125 8.597656 C 90.0625 9.0625 90.125 9.636719 90.003906 10.164062 C 89.808594 10.804688 89.363281 11.304688 88.78125 11.621094 C 88.324219 11.863281 87.820312 11.988281 87.3125 12.054688 C 87.28125 12.058594 87.253906 12.0625 87.222656 12.066406 C 86.777344 12.121094 86.332031 12.109375 85.882812 12.105469 C 85.765625 12.105469 85.644531 12.105469 85.523438 12.105469 C 85.300781 12.105469 85.074219 12.105469 84.847656 12.105469 C 84.589844 12.105469 84.332031 12.105469 84.074219 12.105469 C 83.546875 12.105469 83.015625 12.101562 82.488281 12.101562 C 82.488281 11.878906 82.488281 11.65625 82.488281 11.429688 C 82.859375 11.390625 83.234375 11.347656 83.617188 11.308594 C 83.617188 8.910156 83.617188 6.511719 83.617188 4.042969 C 83.488281 4.035156 83.363281 4.027344 83.230469 4.019531 C 83.117188 4.007812 83.003906 3.996094 82.890625 3.980469 C 82.863281 3.980469 82.832031 3.976562 82.804688 3.972656 C 82.695312 3.960938 82.59375 3.949219 82.488281 3.921875 C 82.488281 3.699219 82.488281 3.476562 82.488281 3.25 Z M 85.390625 3.96875 C 85.390625 4.242188 85.386719 4.515625 85.382812 4.785156 C 85.382812 4.914062 85.378906 5.039062 85.378906 5.164062 C 85.371094 5.824219 85.367188 6.484375 85.367188 7.144531 C 86.488281 7.183594 86.488281 7.183594 87.457031 6.691406 C 87.796875 6.320312 87.859375 5.832031 87.847656 5.351562 C 87.832031 4.992188 87.71875 4.644531 87.460938 4.378906 C 87 3.96875 86.363281 3.964844 85.78125 3.96875 C 85.742188 3.96875 85.703125 3.96875 85.667969 3.96875 C 85.574219 3.96875 85.484375 3.96875 85.390625 3.96875 Z M 85.390625 7.84375 C 85.390625 9.003906 85.390625 10.160156 85.390625 11.355469 C 86.28125 11.386719 86.28125 11.386719 87.152344 11.21875 C 87.171875 11.214844 87.1875 11.207031 87.207031 11.199219 C 87.578125 11.066406 87.886719 10.824219 88.066406 10.46875 C 88.28125 9.988281 88.289062 9.417969 88.125 8.921875 C 87.960938 8.492188 87.664062 8.234375 87.257812 8.046875 C 86.664062 7.804688 86.023438 7.84375 85.390625 7.84375 Z M 85.390625 7.84375 " + }, + "children": [] + }, + { + "type": "element", + "name": "path", + "attributes": { + "style": " stroke:none;fill-rule:nonzero;fill:rgb(10.196079%,11.372549%,14.117648%);fill-opacity:1;", + "d": "M 76.167969 3.476562 C 76.367188 3.671875 76.507812 3.917969 76.585938 4.1875 C 76.589844 4.203125 76.59375 4.222656 76.601562 4.242188 C 76.707031 4.675781 76.621094 5.144531 76.414062 5.53125 C 76.34375 5.644531 76.265625 5.746094 76.175781 5.847656 C 76.15625 5.867188 76.136719 5.886719 76.117188 5.910156 C 75.71875 6.332031 75.199219 6.617188 74.6875 6.882812 C 74.707031 6.902344 74.726562 6.921875 74.746094 6.941406 C 74.972656 7.191406 74.972656 7.191406 75.066406 7.296875 C 75.140625 7.382812 75.21875 7.464844 75.300781 7.542969 C 75.351562 7.59375 75.394531 7.640625 75.4375 7.695312 C 75.527344 7.796875 75.621094 7.894531 75.714844 7.992188 C 76.089844 8.394531 76.089844 8.394531 76.253906 8.585938 C 76.351562 8.695312 76.449219 8.800781 76.546875 8.90625 C 76.621094 8.980469 76.691406 9.058594 76.761719 9.136719 C 76.773438 9.152344 76.789062 9.164062 76.800781 9.179688 C 76.824219 9.207031 76.851562 9.234375 76.875 9.261719 C 76.933594 9.324219 76.992188 9.382812 77.0625 9.429688 C 77.070312 9.410156 77.070312 9.410156 77.082031 9.386719 C 77.113281 9.304688 77.152344 9.230469 77.195312 9.15625 C 77.5625 8.476562 77.800781 7.753906 77.976562 7 C 77.953125 7 77.933594 6.996094 77.910156 6.996094 C 77.707031 6.96875 77.5 6.9375 77.296875 6.902344 C 77.273438 6.898438 77.25 6.894531 77.222656 6.890625 C 77.050781 6.859375 77.050781 6.859375 76.96875 6.832031 C 76.960938 6.328125 76.960938 6.328125 77.015625 6.160156 C 77.949219 6.160156 78.886719 6.160156 79.847656 6.160156 C 79.847656 6.367188 79.847656 6.574219 79.847656 6.785156 C 79.53125 6.839844 79.214844 6.894531 78.886719 6.953125 C 78.859375 7.046875 78.832031 7.140625 78.804688 7.234375 C 78.539062 8.09375 78.164062 9.035156 77.601562 9.746094 C 77.5625 9.792969 77.5625 9.792969 77.566406 9.851562 C 77.601562 9.933594 77.648438 9.980469 77.714844 10.039062 C 77.792969 10.113281 77.867188 10.1875 77.9375 10.269531 C 78.027344 10.375 78.125 10.46875 78.222656 10.566406 C 78.308594 10.65625 78.390625 10.742188 78.472656 10.839844 C 78.539062 10.914062 78.601562 10.933594 78.695312 10.949219 C 78.71875 10.953125 78.746094 10.957031 78.769531 10.960938 C 78.796875 10.964844 78.824219 10.96875 78.851562 10.972656 C 78.875 10.980469 78.902344 10.984375 78.933594 10.988281 C 79.019531 11.003906 79.105469 11.019531 79.191406 11.03125 C 79.277344 11.046875 79.363281 11.0625 79.449219 11.078125 C 79.503906 11.085938 79.558594 11.097656 79.613281 11.105469 C 79.648438 11.113281 79.648438 11.113281 79.6875 11.117188 C 79.707031 11.121094 79.730469 11.125 79.75 11.128906 C 79.800781 11.140625 79.800781 11.140625 79.824219 11.164062 C 79.820312 11.421875 79.785156 11.679688 79.753906 11.933594 C 79.691406 11.949219 79.632812 11.964844 79.570312 11.980469 C 79.546875 11.984375 79.546875 11.984375 79.519531 11.992188 C 79.214844 12.066406 78.910156 12.085938 78.597656 12.085938 C 78.539062 12.085938 78.484375 12.085938 78.425781 12.085938 C 77.847656 12.089844 77.332031 11.917969 76.894531 11.523438 C 76.855469 11.484375 76.816406 11.445312 76.777344 11.40625 C 76.71875 11.347656 76.660156 11.296875 76.601562 11.242188 C 76.578125 11.21875 76.578125 11.21875 76.554688 11.195312 C 76.515625 11.160156 76.476562 11.125 76.441406 11.089844 C 76.429688 11.101562 76.417969 11.109375 76.410156 11.117188 C 76.140625 11.351562 75.859375 11.554688 75.542969 11.71875 C 75.511719 11.738281 75.476562 11.757812 75.445312 11.777344 C 75.3125 11.847656 75.179688 11.894531 75.039062 11.9375 C 75.011719 11.945312 75.011719 11.945312 74.984375 11.953125 C 74.632812 12.058594 74.269531 12.089844 73.90625 12.085938 C 73.84375 12.085938 73.785156 12.085938 73.722656 12.089844 C 72.941406 12.089844 72.222656 11.824219 71.652344 11.28125 C 71.203125 10.820312 71.023438 10.246094 71.03125 9.609375 C 71.042969 9.058594 71.230469 8.546875 71.59375 8.132812 C 71.609375 8.113281 71.625 8.09375 71.644531 8.070312 C 71.980469 7.683594 72.398438 7.421875 72.839844 7.171875 C 72.871094 7.152344 72.902344 7.132812 72.9375 7.113281 C 72.960938 7.101562 72.984375 7.085938 73.007812 7.074219 C 72.996094 7.0625 72.988281 7.050781 72.976562 7.042969 C 72.398438 6.425781 72.09375 5.613281 72.113281 4.773438 C 72.128906 4.371094 72.257812 3.988281 72.527344 3.679688 C 72.542969 3.660156 72.558594 3.644531 72.570312 3.625 C 72.917969 3.210938 73.496094 2.996094 74.015625 2.933594 C 74.050781 2.929688 74.050781 2.929688 74.082031 2.925781 C 74.804688 2.847656 75.621094 2.964844 76.167969 3.476562 Z M 73.671875 3.796875 C 73.433594 4.113281 73.414062 4.4375 73.457031 4.820312 C 73.550781 5.460938 73.921875 5.9375 74.328125 6.425781 C 74.398438 6.390625 74.449219 6.355469 74.503906 6.300781 C 74.527344 6.28125 74.527344 6.28125 74.550781 6.257812 C 74.566406 6.242188 74.582031 6.226562 74.597656 6.210938 C 74.613281 6.191406 74.628906 6.175781 74.644531 6.160156 C 74.773438 6.03125 74.890625 5.894531 75 5.75 C 75.019531 5.726562 75.035156 5.699219 75.054688 5.675781 C 75.335938 5.292969 75.5 4.859375 75.457031 4.378906 C 75.40625 4.078125 75.289062 3.820312 75.035156 3.636719 C 74.59375 3.363281 74.03125 3.410156 73.671875 3.796875 Z M 73.046875 7.828125 C 72.664062 8.226562 72.519531 8.789062 72.519531 9.332031 C 72.53125 9.800781 72.71875 10.257812 73.039062 10.601562 C 73.46875 10.996094 73.980469 11.140625 74.550781 11.125 C 74.960938 11.105469 75.339844 11.003906 75.703125 10.8125 C 75.71875 10.804688 75.738281 10.796875 75.753906 10.785156 C 75.84375 10.738281 75.90625 10.699219 75.960938 10.609375 C 75.949219 10.601562 75.941406 10.589844 75.933594 10.582031 C 75.460938 10.074219 75.460938 10.074219 75.25 9.8125 C 75.1875 9.738281 75.125 9.664062 75.0625 9.59375 C 74.972656 9.484375 74.882812 9.375 74.796875 9.265625 C 74.695312 9.132812 74.589844 9.003906 74.480469 8.878906 C 74.390625 8.773438 74.304688 8.667969 74.214844 8.5625 C 74.152344 8.484375 74.085938 8.40625 74.019531 8.328125 C 73.921875 8.214844 73.828125 8.101562 73.734375 7.984375 C 73.726562 7.96875 73.714844 7.957031 73.703125 7.941406 C 73.683594 7.914062 73.660156 7.886719 73.640625 7.859375 C 73.589844 7.792969 73.539062 7.730469 73.488281 7.667969 C 73.460938 7.632812 73.460938 7.632812 73.433594 7.601562 C 73.414062 7.578125 73.414062 7.578125 73.390625 7.554688 C 73.265625 7.554688 73.132812 7.742188 73.046875 7.828125 Z M 73.046875 7.828125 " + }, + "children": [] + }, + { + "type": "element", + "name": "path", + "attributes": { + "style": " stroke:none;fill-rule:nonzero;fill:rgb(10.196079%,11.372549%,14.117648%);fill-opacity:1;", + "d": "M 49.992188 5.535156 C 50.101562 5.609375 50.101562 5.609375 50.136719 5.679688 C 50.136719 5.746094 50.140625 5.8125 50.140625 5.882812 C 50.140625 5.914062 50.140625 5.914062 50.140625 5.941406 C 50.140625 5.984375 50.140625 6.027344 50.140625 6.070312 C 50.140625 6.136719 50.140625 6.203125 50.140625 6.269531 C 50.140625 6.308594 50.140625 6.351562 50.140625 6.390625 C 50.140625 6.410156 50.140625 6.429688 50.140625 6.453125 C 50.140625 6.589844 50.140625 6.589844 50.113281 6.617188 C 50.074219 6.617188 50.035156 6.621094 49.996094 6.621094 C 49.972656 6.621094 49.949219 6.621094 49.921875 6.621094 C 49.894531 6.621094 49.871094 6.617188 49.84375 6.617188 C 49.816406 6.617188 49.789062 6.617188 49.757812 6.617188 C 49.671875 6.617188 49.585938 6.617188 49.5 6.617188 C 49.441406 6.617188 49.378906 6.617188 49.320312 6.617188 C 49.175781 6.617188 49.03125 6.617188 48.886719 6.617188 C 48.898438 6.640625 48.90625 6.660156 48.917969 6.683594 C 48.929688 6.714844 48.945312 6.746094 48.957031 6.773438 C 48.964844 6.789062 48.96875 6.804688 48.976562 6.820312 C 49.203125 7.339844 49.195312 8 48.988281 8.523438 C 48.75 9.0625 48.355469 9.457031 47.804688 9.671875 C 47.066406 9.941406 46.210938 9.941406 45.457031 9.746094 C 45.277344 10.003906 45.214844 10.273438 45.238281 10.585938 C 45.269531 10.699219 45.316406 10.761719 45.402344 10.835938 C 45.617188 10.945312 45.851562 10.949219 46.089844 10.949219 C 46.113281 10.953125 46.132812 10.953125 46.15625 10.953125 C 46.203125 10.953125 46.25 10.953125 46.292969 10.953125 C 46.367188 10.953125 46.441406 10.953125 46.515625 10.953125 C 46.726562 10.953125 46.9375 10.957031 47.144531 10.957031 C 47.273438 10.957031 47.402344 10.957031 47.53125 10.960938 C 47.582031 10.960938 47.628906 10.960938 47.675781 10.960938 C 48.324219 10.960938 49.039062 11.019531 49.53125 11.492188 C 49.546875 11.511719 49.566406 11.53125 49.585938 11.550781 C 49.601562 11.566406 49.617188 11.582031 49.636719 11.597656 C 49.957031 11.929688 50.0625 12.394531 50.066406 12.84375 C 50.054688 13.351562 49.847656 13.800781 49.511719 14.171875 C 49.496094 14.191406 49.480469 14.207031 49.460938 14.226562 C 48.8125 14.921875 47.769531 15.179688 46.855469 15.210938 C 45.890625 15.234375 44.761719 15.230469 44.015625 14.523438 C 43.738281 14.222656 43.660156 13.886719 43.671875 13.488281 C 43.679688 13.363281 43.699219 13.253906 43.753906 13.136719 C 43.761719 13.117188 43.769531 13.09375 43.78125 13.074219 C 43.996094 12.644531 44.386719 12.410156 44.785156 12.175781 C 44.765625 12.167969 44.746094 12.160156 44.730469 12.152344 C 44.398438 11.996094 44.222656 11.808594 44.089844 11.476562 C 43.988281 11.136719 44.070312 10.757812 44.222656 10.453125 C 44.421875 10.109375 44.695312 9.824219 44.976562 9.550781 C 44.960938 9.542969 44.945312 9.53125 44.925781 9.523438 C 44.757812 9.417969 44.613281 9.304688 44.472656 9.167969 C 44.457031 9.152344 44.441406 9.136719 44.425781 9.121094 C 44.214844 8.902344 44.085938 8.597656 44.015625 8.300781 C 44.011719 8.28125 44.003906 8.257812 44 8.238281 C 43.882812 7.675781 43.964844 7.042969 44.277344 6.558594 C 44.621094 6.070312 45.09375 5.773438 45.671875 5.628906 C 45.6875 5.625 45.703125 5.621094 45.71875 5.617188 C 46.25 5.492188 46.917969 5.496094 47.449219 5.628906 C 47.464844 5.632812 47.480469 5.636719 47.496094 5.640625 C 47.6875 5.691406 47.867188 5.761719 48.046875 5.84375 C 48.0625 5.851562 48.078125 5.859375 48.09375 5.867188 C 48.164062 5.902344 48.226562 5.933594 48.289062 5.980469 C 48.390625 6.066406 48.390625 6.066406 48.515625 6.082031 C 48.582031 6.0625 48.644531 6.035156 48.707031 6.003906 C 48.730469 5.996094 48.753906 5.984375 48.78125 5.976562 C 48.855469 5.941406 48.929688 5.910156 49.003906 5.875 C 49.054688 5.851562 49.101562 5.832031 49.152344 5.808594 C 49.320312 5.738281 49.488281 5.664062 49.652344 5.585938 C 49.679688 5.574219 49.703125 5.566406 49.730469 5.554688 C 49.75 5.542969 49.769531 5.535156 49.789062 5.523438 C 49.867188 5.503906 49.917969 5.515625 49.992188 5.535156 Z M 45.835938 6.507812 C 45.472656 6.984375 45.421875 7.597656 45.492188 8.175781 C 45.550781 8.542969 45.6875 8.890625 45.980469 9.132812 C 46.207031 9.285156 46.46875 9.3125 46.734375 9.277344 C 47.015625 9.21875 47.210938 9.089844 47.375 8.855469 C 47.683594 8.375 47.742188 7.746094 47.640625 7.191406 C 47.5625 6.859375 47.402344 6.507812 47.117188 6.308594 C 46.703125 6.074219 46.152344 6.148438 45.835938 6.507812 Z M 45.238281 12.367188 C 44.957031 12.734375 44.867188 13.113281 44.902344 13.570312 C 44.957031 13.84375 45.09375 14.058594 45.316406 14.226562 C 45.613281 14.417969 46.015625 14.496094 46.367188 14.507812 C 46.394531 14.507812 46.394531 14.507812 46.417969 14.507812 C 47.132812 14.527344 47.90625 14.457031 48.453125 13.945312 C 48.652344 13.738281 48.710938 13.515625 48.703125 13.230469 C 48.683594 12.992188 48.570312 12.800781 48.394531 12.644531 C 48.113281 12.441406 47.726562 12.449219 47.398438 12.449219 C 47.355469 12.449219 47.3125 12.449219 47.269531 12.449219 C 47.15625 12.449219 47.046875 12.449219 46.933594 12.449219 C 46.753906 12.449219 46.574219 12.445312 46.394531 12.445312 C 46.332031 12.445312 46.269531 12.445312 46.210938 12.445312 C 45.882812 12.445312 45.5625 12.414062 45.238281 12.367188 Z M 45.238281 12.367188 " + }, + "children": [] + }, + { + "type": "element", + "name": "path", + "attributes": { + "style": " stroke:none;fill-rule:nonzero;fill:rgb(10.196079%,11.372549%,14.117648%);fill-opacity:1;", + "d": "M 53.039062 2.382812 C 53.0625 2.390625 53.0625 2.390625 53.085938 2.398438 C 53.140625 2.429688 53.171875 2.453125 53.207031 2.503906 C 53.230469 2.617188 53.21875 2.730469 53.214844 2.84375 C 53.210938 2.878906 53.210938 2.914062 53.210938 2.953125 C 53.207031 3.027344 53.203125 3.105469 53.199219 3.183594 C 53.191406 3.371094 53.183594 3.558594 53.179688 3.746094 C 53.175781 3.792969 53.175781 3.835938 53.175781 3.882812 C 53.15625 4.441406 53.15625 5 53.15625 5.5625 C 53.15625 5.640625 53.15625 5.71875 53.15625 5.792969 C 53.15625 6.085938 53.160156 6.375 53.160156 6.664062 C 53.179688 6.644531 53.195312 6.625 53.214844 6.605469 C 53.238281 6.578125 53.261719 6.550781 53.285156 6.523438 C 53.296875 6.511719 53.3125 6.5 53.324219 6.484375 C 53.78125 5.984375 54.445312 5.601562 55.128906 5.550781 C 55.640625 5.535156 56.128906 5.578125 56.527344 5.929688 C 56.566406 5.964844 56.601562 6 56.640625 6.039062 C 56.664062 6.0625 56.664062 6.0625 56.691406 6.089844 C 57.246094 6.660156 57.203125 7.570312 57.203125 8.304688 C 57.203125 8.414062 57.203125 8.523438 57.207031 8.632812 C 57.207031 8.839844 57.207031 9.042969 57.207031 9.25 C 57.207031 9.484375 57.210938 9.722656 57.210938 9.957031 C 57.210938 10.4375 57.214844 10.921875 57.214844 11.40625 C 57.246094 11.410156 57.246094 11.410156 57.28125 11.414062 C 57.308594 11.417969 57.335938 11.421875 57.363281 11.425781 C 57.40625 11.433594 57.40625 11.433594 57.445312 11.441406 C 57.558594 11.457031 57.671875 11.480469 57.785156 11.503906 C 57.808594 11.507812 57.828125 11.511719 57.851562 11.515625 C 57.878906 11.519531 57.878906 11.519531 57.910156 11.527344 C 57.929688 11.53125 57.949219 11.535156 57.964844 11.539062 C 58.007812 11.550781 58.007812 11.550781 58.03125 11.574219 C 58.035156 11.613281 58.035156 11.65625 58.035156 11.695312 C 58.035156 11.71875 58.035156 11.746094 58.035156 11.769531 C 58.035156 11.796875 58.035156 11.824219 58.035156 11.851562 C 58.035156 11.875 58.035156 11.902344 58.035156 11.929688 C 58.035156 11.964844 58.035156 11.964844 58.035156 12.003906 C 58.035156 12.027344 58.035156 12.050781 58.035156 12.074219 C 58.03125 12.125 58.03125 12.125 58.007812 12.148438 C 57.964844 12.152344 57.921875 12.152344 57.882812 12.152344 C 57.839844 12.152344 57.839844 12.152344 57.796875 12.152344 C 57.769531 12.152344 57.738281 12.152344 57.707031 12.152344 C 57.675781 12.152344 57.640625 12.152344 57.609375 12.152344 C 57.523438 12.152344 57.433594 12.152344 57.347656 12.152344 C 57.257812 12.152344 57.164062 12.152344 57.074219 12.152344 C 56.917969 12.152344 56.765625 12.152344 56.613281 12.152344 C 56.433594 12.152344 56.257812 12.152344 56.082031 12.152344 C 55.929688 12.152344 55.777344 12.152344 55.625 12.152344 C 55.53125 12.152344 55.441406 12.152344 55.351562 12.152344 C 55.265625 12.152344 55.179688 12.152344 55.09375 12.152344 C 55.046875 12.152344 55 12.152344 54.953125 12.152344 C 54.925781 12.152344 54.898438 12.152344 54.871094 12.152344 C 54.847656 12.152344 54.824219 12.152344 54.796875 12.152344 C 54.742188 12.148438 54.742188 12.148438 54.71875 12.125 C 54.71875 12.085938 54.71875 12.042969 54.71875 12.003906 C 54.71875 11.976562 54.71875 11.953125 54.71875 11.925781 C 54.71875 11.902344 54.71875 11.875 54.71875 11.847656 C 54.71875 11.820312 54.71875 11.796875 54.71875 11.769531 C 54.71875 11.703125 54.71875 11.636719 54.71875 11.574219 C 54.8125 11.53125 54.902344 11.507812 55.003906 11.488281 C 55.03125 11.480469 55.0625 11.476562 55.09375 11.46875 C 55.113281 11.464844 55.128906 11.460938 55.144531 11.460938 C 55.191406 11.449219 55.242188 11.441406 55.289062 11.429688 C 55.527344 11.378906 55.527344 11.378906 55.585938 11.378906 C 55.582031 10.90625 55.582031 10.433594 55.578125 9.960938 C 55.578125 9.742188 55.578125 9.523438 55.574219 9.304688 C 55.574219 9.109375 55.574219 8.917969 55.574219 8.726562 C 55.574219 8.625 55.570312 8.527344 55.570312 8.425781 C 55.570312 8.328125 55.570312 8.234375 55.570312 8.136719 C 55.570312 8.101562 55.570312 8.066406 55.570312 8.03125 C 55.570312 7.664062 55.554688 7.199219 55.289062 6.917969 C 55.054688 6.722656 54.742188 6.746094 54.457031 6.761719 C 54.101562 6.800781 53.738281 7.007812 53.464844 7.234375 C 53.425781 7.265625 53.425781 7.265625 53.371094 7.300781 C 53.273438 7.371094 53.214844 7.421875 53.1875 7.539062 C 53.179688 7.640625 53.179688 7.742188 53.183594 7.84375 C 53.183594 7.882812 53.183594 7.921875 53.183594 7.960938 C 53.183594 8.066406 53.183594 8.167969 53.1875 8.273438 C 53.1875 8.386719 53.1875 8.496094 53.1875 8.605469 C 53.1875 8.8125 53.191406 9.023438 53.191406 9.230469 C 53.195312 9.46875 53.195312 9.703125 53.195312 9.941406 C 53.199219 10.429688 53.203125 10.917969 53.207031 11.40625 C 53.238281 11.410156 53.238281 11.410156 53.265625 11.414062 C 53.351562 11.429688 53.4375 11.445312 53.523438 11.464844 C 53.554688 11.46875 53.585938 11.472656 53.613281 11.480469 C 53.644531 11.484375 53.671875 11.492188 53.703125 11.496094 C 53.730469 11.5 53.753906 11.507812 53.78125 11.511719 C 53.847656 11.523438 53.910156 11.535156 53.976562 11.550781 C 53.976562 11.746094 53.976562 11.945312 53.976562 12.148438 C 52.890625 12.148438 51.804688 12.148438 50.6875 12.148438 C 50.6875 11.953125 50.6875 11.753906 50.6875 11.550781 C 50.964844 11.492188 51.242188 11.4375 51.527344 11.378906 C 51.542969 11.160156 51.554688 10.945312 51.554688 10.722656 C 51.554688 10.691406 51.554688 10.660156 51.554688 10.628906 C 51.554688 10.546875 51.558594 10.464844 51.558594 10.378906 C 51.558594 10.289062 51.558594 10.199219 51.558594 10.109375 C 51.558594 9.953125 51.558594 9.796875 51.558594 9.640625 C 51.558594 9.414062 51.558594 9.1875 51.5625 8.960938 C 51.5625 8.59375 51.5625 8.230469 51.5625 7.863281 C 51.566406 7.507812 51.566406 7.152344 51.566406 6.792969 C 51.566406 6.773438 51.566406 6.75 51.566406 6.726562 C 51.566406 6.617188 51.566406 6.507812 51.566406 6.398438 C 51.570312 5.484375 51.574219 4.570312 51.574219 3.65625 C 51.554688 3.65625 51.535156 3.652344 51.515625 3.652344 C 51.476562 3.648438 51.476562 3.648438 51.4375 3.644531 C 51.414062 3.644531 51.386719 3.640625 51.359375 3.640625 C 51.277344 3.632812 51.195312 3.621094 51.113281 3.609375 C 51.082031 3.605469 51.054688 3.601562 51.023438 3.597656 C 50.996094 3.59375 50.964844 3.589844 50.933594 3.582031 C 50.902344 3.578125 50.871094 3.574219 50.839844 3.570312 C 50.765625 3.558594 50.691406 3.546875 50.617188 3.535156 C 50.617188 3.347656 50.617188 3.15625 50.617188 2.960938 C 50.910156 2.875 51.207031 2.796875 51.5 2.722656 C 51.523438 2.714844 51.542969 2.710938 51.5625 2.707031 C 51.671875 2.679688 51.777344 2.652344 51.886719 2.625 C 51.972656 2.601562 52.0625 2.578125 52.152344 2.554688 C 52.257812 2.527344 52.367188 2.5 52.472656 2.472656 C 52.515625 2.460938 52.554688 2.453125 52.597656 2.441406 C 52.652344 2.425781 52.710938 2.410156 52.765625 2.398438 C 52.78125 2.394531 52.800781 2.386719 52.816406 2.382812 C 52.898438 2.363281 52.960938 2.351562 53.039062 2.382812 Z M 53.039062 2.382812 " + }, + "children": [] + }, + { + "type": "element", + "name": "path", + "attributes": { + "style": " stroke:none;fill-rule:nonzero;fill:rgb(10.196079%,11.372549%,14.117648%);fill-opacity:1;", + "d": "M 99.691406 6.011719 C 100.109375 6.40625 100.234375 7.003906 100.273438 7.554688 C 100.277344 7.667969 100.277344 7.785156 100.277344 7.898438 C 100.277344 7.933594 100.277344 7.964844 100.277344 8 C 100.277344 8.074219 100.277344 8.144531 100.277344 8.21875 C 100.277344 8.332031 100.277344 8.445312 100.277344 8.558594 C 100.28125 8.886719 100.28125 9.210938 100.28125 9.535156 C 100.28125 9.714844 100.28125 9.894531 100.285156 10.074219 C 100.285156 10.171875 100.285156 10.265625 100.285156 10.359375 C 100.285156 10.449219 100.285156 10.539062 100.285156 10.628906 C 100.285156 10.675781 100.285156 10.726562 100.285156 10.773438 C 100.289062 10.964844 100.292969 11.167969 100.417969 11.320312 C 100.53125 11.378906 100.636719 11.398438 100.757812 11.367188 C 100.898438 11.308594 101.003906 11.21875 101.113281 11.117188 C 101.226562 11.199219 101.339844 11.289062 101.449219 11.378906 C 101.394531 11.527344 101.300781 11.640625 101.207031 11.765625 C 101.179688 11.804688 101.179688 11.804688 101.152344 11.84375 C 100.859375 12.152344 100.476562 12.265625 100.058594 12.277344 C 99.699219 12.269531 99.332031 12.164062 99.066406 11.910156 C 98.933594 11.757812 98.761719 11.519531 98.761719 11.308594 C 98.582031 11.449219 98.582031 11.449219 98.417969 11.605469 C 98.289062 11.738281 98.140625 11.847656 97.992188 11.957031 C 97.96875 11.972656 97.949219 11.992188 97.925781 12.007812 C 97.488281 12.3125 96.855469 12.339844 96.34375 12.25 C 95.917969 12.15625 95.527344 11.929688 95.28125 11.558594 C 95.035156 11.136719 94.964844 10.617188 95.082031 10.140625 C 95.289062 9.527344 95.746094 9.175781 96.300781 8.894531 C 96.941406 8.582031 97.644531 8.375 98.328125 8.179688 C 98.34375 8.175781 98.359375 8.171875 98.375 8.167969 C 98.472656 8.140625 98.566406 8.113281 98.664062 8.085938 C 98.695312 7.195312 98.695312 7.195312 98.328125 6.425781 C 98.121094 6.230469 97.828125 6.203125 97.558594 6.203125 C 97.53125 6.203125 97.53125 6.203125 97.503906 6.203125 C 97.339844 6.207031 97.171875 6.21875 97.007812 6.230469 C 97.003906 6.257812 97.003906 6.289062 97 6.316406 C 96.984375 6.46875 96.957031 6.617188 96.925781 6.765625 C 96.917969 6.816406 96.910156 6.863281 96.902344 6.910156 C 96.832031 7.273438 96.738281 7.585938 96.425781 7.808594 C 96.191406 7.933594 95.945312 7.933594 95.6875 7.867188 C 95.515625 7.808594 95.40625 7.707031 95.320312 7.546875 C 95.234375 7.347656 95.238281 7.183594 95.308594 6.980469 C 95.316406 6.957031 95.316406 6.957031 95.324219 6.929688 C 95.421875 6.644531 95.574219 6.441406 95.785156 6.230469 C 95.800781 6.214844 95.816406 6.195312 95.835938 6.179688 C 96.734375 5.308594 98.742188 5.21875 99.691406 6.011719 Z M 98.394531 8.742188 C 98.292969 8.777344 98.1875 8.8125 98.082031 8.847656 C 97.574219 9.007812 97.011719 9.230469 96.746094 9.722656 C 96.582031 10.042969 96.554688 10.355469 96.648438 10.703125 C 96.71875 10.914062 96.816406 11.042969 97.011719 11.152344 C 97.296875 11.292969 97.609375 11.304688 97.910156 11.199219 C 98.058594 11.132812 98.207031 11.050781 98.34375 10.960938 C 98.398438 10.921875 98.398438 10.921875 98.46875 10.890625 C 98.558594 10.839844 98.644531 10.789062 98.679688 10.683594 C 98.703125 10.542969 98.695312 10.402344 98.691406 10.257812 C 98.6875 10.214844 98.6875 10.167969 98.6875 10.121094 C 98.6875 10 98.683594 9.878906 98.683594 9.757812 C 98.679688 9.636719 98.679688 9.511719 98.675781 9.386719 C 98.675781 9.144531 98.667969 8.902344 98.664062 8.660156 C 98.578125 8.660156 98.476562 8.714844 98.394531 8.742188 Z M 98.394531 8.742188 " + }, + "children": [] + }, + { + "type": "element", + "name": "path", + "attributes": { + "style": " stroke:none;fill-rule:nonzero;fill:rgb(10.196079%,11.372549%,14.117648%);fill-opacity:1;", + "d": "M 38.035156 6.242188 C 38.207031 6.40625 38.332031 6.578125 38.449219 6.785156 C 38.460938 6.808594 38.460938 6.808594 38.472656 6.832031 C 38.835938 7.472656 38.875 8.257812 38.761719 8.972656 C 38.734375 9 38.734375 9 38.671875 9 C 38.625 9 38.625 9 38.578125 9 C 38.5625 9 38.546875 9 38.53125 9 C 38.472656 9 38.417969 9 38.363281 9 C 38.324219 9 38.285156 9 38.242188 9 C 38.136719 9 38.027344 9 37.917969 9 C 37.804688 9 37.695312 9 37.582031 9 C 37.367188 9 37.152344 9 36.9375 9 C 36.695312 9 36.453125 9 36.207031 9 C 35.707031 9 35.207031 9 34.703125 9 C 34.714844 9.089844 34.726562 9.183594 34.738281 9.273438 C 34.742188 9.300781 34.742188 9.328125 34.746094 9.351562 C 34.8125 9.898438 35.007812 10.441406 35.4375 10.808594 C 35.933594 11.1875 36.476562 11.269531 37.089844 11.203125 C 37.539062 11.128906 37.90625 10.847656 38.222656 10.535156 C 38.347656 10.417969 38.347656 10.417969 38.425781 10.417969 C 38.464844 10.445312 38.464844 10.445312 38.503906 10.488281 C 38.589844 10.585938 38.683594 10.671875 38.785156 10.753906 C 38.679688 11.046875 38.46875 11.28125 38.257812 11.5 C 38.242188 11.519531 38.222656 11.535156 38.207031 11.554688 C 37.792969 12 37.171875 12.246094 36.574219 12.320312 C 36.554688 12.320312 36.535156 12.324219 36.515625 12.328125 C 35.640625 12.425781 34.773438 12.210938 34.074219 11.671875 C 33.421875 11.125 33.078125 10.363281 32.976562 9.527344 C 32.972656 9.496094 32.972656 9.496094 32.96875 9.460938 C 32.871094 8.5 33.074219 7.515625 33.675781 6.746094 C 33.707031 6.710938 33.738281 6.675781 33.769531 6.640625 C 33.78125 6.621094 33.796875 6.605469 33.8125 6.585938 C 34.316406 5.988281 35.136719 5.640625 35.902344 5.566406 C 36.699219 5.511719 37.429688 5.699219 38.035156 6.242188 Z M 35.226562 6.652344 C 34.949219 7.007812 34.820312 7.386719 34.746094 7.824219 C 34.742188 7.851562 34.738281 7.875 34.734375 7.898438 C 34.730469 7.921875 34.726562 7.949219 34.722656 7.972656 C 34.71875 7.992188 34.714844 8.015625 34.710938 8.035156 C 34.703125 8.097656 34.703125 8.15625 34.703125 8.222656 C 34.703125 8.242188 34.703125 8.261719 34.703125 8.28125 C 34.703125 8.292969 34.703125 8.308594 34.703125 8.324219 C 34.972656 8.328125 35.242188 8.328125 35.507812 8.328125 C 35.632812 8.328125 35.757812 8.328125 35.882812 8.332031 C 36.003906 8.332031 36.125 8.332031 36.246094 8.332031 C 36.289062 8.332031 36.335938 8.332031 36.382812 8.332031 C 36.445312 8.332031 36.511719 8.332031 36.574219 8.332031 C 36.59375 8.332031 36.613281 8.332031 36.632812 8.332031 C 36.800781 8.332031 36.964844 8.304688 37.085938 8.183594 C 37.273438 7.9375 37.277344 7.609375 37.246094 7.3125 C 37.195312 6.96875 37.015625 6.636719 36.730469 6.425781 C 36.226562 6.113281 35.617188 6.195312 35.226562 6.652344 Z M 35.226562 6.652344 " + }, + "children": [] + }, + { + "type": "element", + "name": "path", + "attributes": { + "style": " stroke:none;fill-rule:nonzero;fill:rgb(10.196079%,11.372549%,14.117648%);fill-opacity:1;", + "d": "M 112.726562 6.085938 C 112.90625 6.242188 113.058594 6.414062 113.183594 6.617188 C 113.199219 6.636719 113.210938 6.65625 113.226562 6.679688 C 113.628906 7.320312 113.699219 8.167969 113.566406 8.902344 C 113.523438 8.945312 113.449219 8.929688 113.386719 8.929688 C 113.371094 8.929688 113.355469 8.929688 113.339844 8.929688 C 113.28125 8.929688 113.226562 8.929688 113.171875 8.929688 C 113.132812 8.929688 113.089844 8.933594 113.050781 8.933594 C 112.945312 8.933594 112.835938 8.933594 112.726562 8.933594 C 112.613281 8.933594 112.5 8.933594 112.386719 8.933594 C 112.175781 8.9375 111.960938 8.9375 111.746094 8.9375 C 111.503906 8.941406 111.257812 8.941406 111.015625 8.941406 C 110.515625 8.945312 110.011719 8.949219 109.511719 8.949219 C 109.519531 9.023438 109.527344 9.097656 109.535156 9.171875 C 109.535156 9.191406 109.539062 9.210938 109.539062 9.230469 C 109.554688 9.378906 109.578125 9.523438 109.613281 9.667969 C 109.621094 9.6875 109.625 9.710938 109.628906 9.730469 C 109.703125 10.011719 109.808594 10.261719 109.992188 10.488281 C 110.003906 10.507812 110.019531 10.527344 110.035156 10.542969 C 110.320312 10.898438 110.765625 11.117188 111.214844 11.164062 C 111.839844 11.203125 112.339844 11.078125 112.820312 10.671875 C 112.9375 10.570312 113.050781 10.457031 113.160156 10.347656 C 113.230469 10.378906 113.28125 10.414062 113.339844 10.46875 C 113.355469 10.480469 113.367188 10.496094 113.382812 10.507812 C 113.398438 10.523438 113.414062 10.539062 113.429688 10.554688 C 113.445312 10.566406 113.460938 10.582031 113.476562 10.597656 C 113.515625 10.632812 113.554688 10.671875 113.59375 10.707031 C 113.324219 11.316406 112.769531 11.816406 112.15625 12.066406 C 112.054688 12.105469 111.953125 12.136719 111.847656 12.171875 C 111.832031 12.175781 111.816406 12.179688 111.800781 12.183594 C 111.507812 12.265625 111.214844 12.28125 110.914062 12.28125 C 110.898438 12.28125 110.878906 12.28125 110.859375 12.28125 C 110.554688 12.277344 110.261719 12.257812 109.96875 12.175781 C 109.941406 12.167969 109.941406 12.167969 109.914062 12.160156 C 109.203125 11.953125 108.628906 11.503906 108.238281 10.875 C 108.230469 10.859375 108.222656 10.847656 108.210938 10.832031 C 107.699219 9.980469 107.648438 8.855469 107.878906 7.90625 C 108.074219 7.136719 108.570312 6.417969 109.253906 6 C 110.304688 5.378906 111.75 5.261719 112.726562 6.085938 Z M 110.105469 6.496094 C 109.710938 6.9375 109.507812 7.546875 109.511719 8.136719 C 109.511719 8.160156 109.511719 8.179688 109.511719 8.203125 C 109.511719 8.21875 109.511719 8.234375 109.511719 8.253906 C 109.78125 8.253906 110.050781 8.253906 110.316406 8.257812 C 110.441406 8.257812 110.566406 8.257812 110.691406 8.257812 C 110.8125 8.257812 110.933594 8.257812 111.054688 8.257812 C 111.097656 8.257812 111.144531 8.261719 111.191406 8.261719 C 111.253906 8.261719 111.320312 8.261719 111.382812 8.261719 C 111.402344 8.261719 111.421875 8.261719 111.441406 8.261719 C 111.59375 8.261719 111.746094 8.234375 111.871094 8.140625 C 112.082031 7.886719 112.078125 7.605469 112.054688 7.289062 C 112.011719 6.949219 111.867188 6.6875 111.625 6.449219 C 111.609375 6.433594 111.59375 6.417969 111.582031 6.40625 C 111.164062 6.015625 110.496094 6.121094 110.105469 6.496094 Z M 110.105469 6.496094 " + }, + "children": [] + }, + { + "type": "element", + "name": "path", + "attributes": { + "style": " stroke:none;fill-rule:nonzero;fill:rgb(10.196079%,11.372549%,14.117648%);fill-opacity:1;", + "d": "M 119.207031 6.039062 C 119.210938 6.308594 119.203125 6.578125 119.1875 6.847656 C 119.183594 6.910156 119.183594 6.972656 119.179688 7.035156 C 119.175781 7.078125 119.175781 7.117188 119.171875 7.160156 C 119.171875 7.175781 119.171875 7.195312 119.171875 7.214844 C 119.164062 7.308594 119.152344 7.390625 119.136719 7.484375 C 118.835938 7.484375 118.535156 7.484375 118.222656 7.484375 C 118.207031 7.40625 118.191406 7.328125 118.171875 7.246094 C 118.15625 7.171875 118.140625 7.097656 118.121094 7.023438 C 118.109375 6.96875 118.101562 6.917969 118.089844 6.867188 C 118.070312 6.792969 118.054688 6.714844 118.039062 6.640625 C 118.035156 6.617188 118.027344 6.59375 118.023438 6.570312 C 118.019531 6.550781 118.015625 6.527344 118.007812 6.503906 C 118.003906 6.484375 118 6.464844 117.996094 6.445312 C 117.984375 6.398438 117.984375 6.398438 117.960938 6.351562 C 117.902344 6.332031 117.847656 6.316406 117.789062 6.300781 C 117.765625 6.296875 117.765625 6.296875 117.738281 6.289062 C 117.339844 6.1875 116.835938 6.167969 116.464844 6.375 C 116.296875 6.480469 116.203125 6.609375 116.128906 6.789062 C 116.082031 7.042969 116.105469 7.261719 116.234375 7.484375 C 116.496094 7.828125 117.082031 7.953125 117.46875 8.070312 C 118.183594 8.289062 118.960938 8.597656 119.359375 9.277344 C 119.578125 9.714844 119.621094 10.257812 119.496094 10.734375 C 119.316406 11.277344 118.957031 11.703125 118.449219 11.964844 C 117.460938 12.445312 116.246094 12.394531 115.222656 12.054688 C 115.007812 11.972656 114.804688 11.867188 114.601562 11.765625 C 114.570312 11.234375 114.574219 10.707031 114.574219 10.175781 C 114.886719 10.175781 115.195312 10.175781 115.511719 10.175781 C 115.539062 10.304688 115.539062 10.304688 115.5625 10.433594 C 115.582031 10.515625 115.597656 10.597656 115.613281 10.679688 C 115.625 10.734375 115.636719 10.792969 115.648438 10.847656 C 115.664062 10.929688 115.679688 11.011719 115.695312 11.09375 C 115.703125 11.121094 115.707031 11.144531 115.710938 11.171875 C 115.71875 11.195312 115.722656 11.21875 115.726562 11.242188 C 115.730469 11.261719 115.734375 11.285156 115.738281 11.304688 C 115.75 11.355469 115.75 11.355469 115.777344 11.40625 C 116.324219 11.617188 117.03125 11.667969 117.578125 11.4375 C 117.800781 11.332031 117.949219 11.207031 118.039062 10.972656 C 118.089844 10.761719 118.082031 10.527344 117.992188 10.328125 C 117.910156 10.191406 117.820312 10.105469 117.6875 10.023438 C 117.664062 10.003906 117.636719 9.988281 117.609375 9.972656 C 117.265625 9.769531 116.875 9.65625 116.496094 9.527344 C 116.066406 9.386719 115.683594 9.222656 115.320312 8.949219 C 115.296875 8.933594 115.273438 8.917969 115.25 8.898438 C 115.226562 8.875 115.226562 8.875 115.199219 8.855469 C 115.199219 8.839844 115.199219 8.824219 115.199219 8.804688 C 115.1875 8.800781 115.171875 8.796875 115.160156 8.789062 C 114.933594 8.65625 114.792969 8.285156 114.726562 8.046875 C 114.605469 7.507812 114.6875 6.964844 114.984375 6.496094 C 115.347656 5.957031 115.902344 5.671875 116.523438 5.542969 C 117.460938 5.367188 118.386719 5.574219 119.207031 6.039062 Z M 119.207031 6.039062 " + }, + "children": [] + }, + { + "type": "element", + "name": "path", + "attributes": { + "style": " stroke:none;fill-rule:nonzero;fill:rgb(10.196079%,11.372549%,14.117648%);fill-opacity:1;", + "d": "M 67.945312 6.109375 C 67.96875 6.136719 67.96875 6.136719 67.96875 6.1875 C 67.96875 6.210938 67.96875 6.234375 67.964844 6.257812 C 67.964844 6.285156 67.964844 6.3125 67.964844 6.339844 C 67.960938 6.367188 67.960938 6.398438 67.960938 6.425781 C 67.960938 6.457031 67.957031 6.484375 67.957031 6.515625 C 67.941406 6.863281 67.917969 7.207031 67.894531 7.554688 C 67.59375 7.554688 67.292969 7.554688 66.984375 7.554688 C 66.921875 7.3125 66.863281 7.070312 66.808594 6.828125 C 66.804688 6.796875 66.796875 6.769531 66.789062 6.742188 C 66.785156 6.714844 66.777344 6.6875 66.773438 6.660156 C 66.765625 6.632812 66.761719 6.609375 66.753906 6.585938 C 66.742188 6.519531 66.742188 6.519531 66.742188 6.425781 C 66.707031 6.414062 66.667969 6.402344 66.628906 6.390625 C 66.605469 6.386719 66.585938 6.378906 66.5625 6.371094 C 66.355469 6.320312 66.15625 6.296875 65.941406 6.296875 C 65.917969 6.296875 65.894531 6.296875 65.871094 6.296875 C 65.5625 6.300781 65.296875 6.351562 65.0625 6.566406 C 64.894531 6.742188 64.859375 6.90625 64.863281 7.148438 C 64.867188 7.285156 64.890625 7.390625 64.96875 7.507812 C 64.976562 7.519531 64.984375 7.535156 64.996094 7.550781 C 65.261719 7.921875 65.914062 8.0625 66.328125 8.179688 C 67.054688 8.390625 67.703125 8.726562 68.089844 9.40625 C 68.214844 9.648438 68.289062 9.90625 68.304688 10.175781 C 68.304688 10.199219 68.304688 10.21875 68.308594 10.242188 C 68.324219 10.753906 68.15625 11.1875 67.824219 11.574219 C 67.808594 11.589844 67.796875 11.605469 67.78125 11.621094 C 67.28125 12.164062 66.515625 12.347656 65.808594 12.390625 C 65.144531 12.414062 64.535156 12.34375 63.910156 12.117188 C 63.886719 12.109375 63.886719 12.109375 63.859375 12.097656 C 63.675781 12.03125 63.507812 11.929688 63.335938 11.835938 C 63.335938 11.632812 63.335938 11.429688 63.335938 11.226562 C 63.335938 11.132812 63.335938 11.039062 63.335938 10.945312 C 63.332031 10.851562 63.332031 10.761719 63.332031 10.671875 C 63.332031 10.636719 63.332031 10.601562 63.332031 10.566406 C 63.332031 10.515625 63.332031 10.46875 63.332031 10.421875 C 63.332031 10.390625 63.332031 10.363281 63.332031 10.335938 C 63.335938 10.273438 63.335938 10.273438 63.359375 10.25 C 63.421875 10.246094 63.484375 10.246094 63.550781 10.246094 C 63.570312 10.246094 63.585938 10.246094 63.605469 10.246094 C 63.648438 10.246094 63.6875 10.246094 63.726562 10.246094 C 63.789062 10.246094 63.851562 10.246094 63.914062 10.246094 C 63.953125 10.246094 63.992188 10.246094 64.03125 10.246094 C 64.050781 10.246094 64.066406 10.246094 64.085938 10.246094 C 64.21875 10.246094 64.21875 10.246094 64.273438 10.273438 C 64.285156 10.316406 64.285156 10.316406 64.296875 10.375 C 64.300781 10.394531 64.304688 10.414062 64.308594 10.4375 C 64.316406 10.460938 64.320312 10.484375 64.324219 10.507812 C 64.328125 10.53125 64.332031 10.554688 64.339844 10.578125 C 64.347656 10.628906 64.359375 10.679688 64.367188 10.730469 C 64.382812 10.808594 64.398438 10.882812 64.414062 10.960938 C 64.421875 11.007812 64.433594 11.058594 64.441406 11.105469 C 64.445312 11.128906 64.453125 11.152344 64.457031 11.175781 C 64.476562 11.285156 64.492188 11.386719 64.488281 11.5 C 64.53125 11.511719 64.53125 11.511719 64.578125 11.519531 C 64.691406 11.546875 64.804688 11.574219 64.921875 11.605469 C 65.117188 11.648438 65.308594 11.648438 65.507812 11.652344 C 65.539062 11.652344 65.570312 11.652344 65.601562 11.652344 C 65.964844 11.652344 66.320312 11.59375 66.605469 11.359375 C 66.761719 11.199219 66.820312 11.003906 66.828125 10.789062 C 66.820312 10.566406 66.761719 10.382812 66.601562 10.226562 C 66.214844 9.933594 65.765625 9.789062 65.3125 9.640625 C 64.621094 9.414062 63.949219 9.125 63.59375 8.445312 C 63.378906 8 63.375 7.464844 63.53125 6.996094 C 63.761719 6.410156 64.183594 6.027344 64.753906 5.773438 C 65.792969 5.351562 66.988281 5.59375 67.945312 6.109375 Z M 67.945312 6.109375 " + }, + "children": [] + }, + { + "type": "element", + "name": "path", + "attributes": { + "style": " stroke:none;fill-rule:nonzero;fill:rgb(10.196079%,11.372549%,14.117648%);fill-opacity:1;", + "d": "M 105.941406 5.769531 C 105.960938 5.777344 105.976562 5.785156 105.996094 5.792969 C 106.1875 5.867188 106.351562 5.964844 106.535156 6.0625 C 106.511719 6.539062 106.488281 7.015625 106.464844 7.507812 C 106.164062 7.507812 105.863281 7.507812 105.550781 7.507812 C 105.445312 7.101562 105.445312 7.101562 105.410156 6.941406 C 105.40625 6.925781 105.402344 6.910156 105.398438 6.890625 C 105.386719 6.839844 105.375 6.789062 105.367188 6.738281 C 105.359375 6.703125 105.351562 6.667969 105.34375 6.632812 C 105.324219 6.546875 105.304688 6.460938 105.289062 6.375 C 105.234375 6.359375 105.183594 6.34375 105.128906 6.328125 C 105.097656 6.320312 105.070312 6.3125 105.039062 6.304688 C 104.859375 6.253906 104.6875 6.25 104.503906 6.25 C 104.464844 6.25 104.464844 6.25 104.421875 6.25 C 104.136719 6.246094 103.90625 6.3125 103.664062 6.464844 C 103.507812 6.621094 103.417969 6.816406 103.414062 7.042969 C 103.425781 7.25 103.492188 7.433594 103.632812 7.585938 C 103.976562 7.886719 104.484375 8.003906 104.910156 8.132812 C 105.628906 8.351562 106.285156 8.667969 106.667969 9.355469 C 106.878906 9.800781 106.9375 10.371094 106.785156 10.84375 C 106.554688 11.417969 106.144531 11.8125 105.585938 12.070312 C 104.601562 12.488281 103.335938 12.402344 102.359375 12.007812 C 102.203125 11.9375 102.046875 11.859375 101.902344 11.765625 C 101.894531 11.699219 101.894531 11.699219 101.894531 11.617188 C 101.894531 11.585938 101.894531 11.554688 101.890625 11.523438 C 101.890625 11.488281 101.890625 11.453125 101.890625 11.417969 C 101.890625 11.382812 101.890625 11.347656 101.890625 11.3125 C 101.890625 11.222656 101.886719 11.128906 101.886719 11.039062 C 101.886719 10.925781 101.886719 10.816406 101.882812 10.707031 C 101.882812 10.539062 101.882812 10.371094 101.878906 10.203125 C 102.1875 10.203125 102.496094 10.203125 102.816406 10.203125 C 102.871094 10.363281 102.871094 10.363281 102.886719 10.449219 C 102.890625 10.46875 102.894531 10.488281 102.898438 10.507812 C 102.902344 10.527344 102.90625 10.546875 102.910156 10.566406 C 102.914062 10.585938 102.917969 10.609375 102.921875 10.628906 C 102.933594 10.671875 102.941406 10.71875 102.949219 10.761719 C 102.964844 10.828125 102.976562 10.894531 102.988281 10.960938 C 103 11.003906 103.007812 11.046875 103.015625 11.089844 C 103.019531 11.109375 103.023438 11.132812 103.027344 11.152344 C 103.046875 11.246094 103.0625 11.332031 103.054688 11.429688 C 103.699219 11.59375 104.421875 11.726562 105.03125 11.386719 C 105.21875 11.265625 105.316406 11.125 105.375 10.914062 C 105.402344 10.691406 105.378906 10.496094 105.273438 10.292969 C 104.921875 9.867188 104.363281 9.730469 103.859375 9.5625 C 103.1875 9.339844 102.511719 9.058594 102.167969 8.398438 C 101.949219 7.929688 101.9375 7.414062 102.097656 6.929688 C 102.101562 6.90625 102.109375 6.886719 102.117188 6.863281 C 102.269531 6.417969 102.628906 6.066406 103.03125 5.847656 C 103.054688 5.832031 103.078125 5.820312 103.101562 5.808594 C 103.382812 5.65625 103.699219 5.574219 104.015625 5.535156 C 104.035156 5.53125 104.054688 5.527344 104.074219 5.527344 C 104.714844 5.449219 105.34375 5.535156 105.941406 5.769531 Z M 105.941406 5.769531 " + }, + "children": [] + }, + { + "type": "element", + "name": "path", + "attributes": { + "style": " stroke:none;fill-rule:nonzero;fill:rgb(10.196079%,11.372549%,14.117648%);fill-opacity:1;", + "d": "M 59.953125 3.921875 C 60.316406 3.921875 60.679688 3.921875 61.054688 3.921875 C 61.058594 4.292969 61.054688 4.667969 61.046875 5.039062 C 61.042969 5.066406 61.042969 5.066406 61.042969 5.09375 C 61.042969 5.144531 61.042969 5.195312 61.039062 5.246094 C 61.039062 5.277344 61.039062 5.304688 61.035156 5.335938 C 61.03125 5.472656 61.019531 5.613281 61.007812 5.75 C 61.53125 5.75 62.054688 5.75 62.59375 5.75 C 62.59375 6.035156 62.59375 6.320312 62.59375 6.617188 C 62.0625 6.617188 61.53125 6.617188 60.984375 6.617188 C 60.984375 7.128906 60.988281 7.644531 60.988281 8.160156 C 60.992188 8.398438 60.992188 8.636719 60.992188 8.875 C 60.992188 9.082031 60.992188 9.292969 60.996094 9.5 C 60.996094 9.609375 60.996094 9.71875 60.996094 9.832031 C 60.996094 9.933594 60.996094 10.039062 60.996094 10.140625 C 60.996094 10.179688 60.996094 10.21875 60.996094 10.253906 C 60.992188 10.765625 60.992188 10.765625 61.199219 11.210938 C 61.34375 11.347656 61.507812 11.40625 61.703125 11.40625 C 61.941406 11.371094 62.144531 11.289062 62.355469 11.175781 C 62.457031 11.125 62.457031 11.125 62.519531 11.117188 C 62.558594 11.140625 62.558594 11.140625 62.597656 11.183594 C 62.613281 11.195312 62.625 11.210938 62.640625 11.226562 C 62.652344 11.238281 62.667969 11.253906 62.683594 11.269531 C 62.695312 11.285156 62.710938 11.300781 62.726562 11.316406 C 62.761719 11.351562 62.796875 11.390625 62.832031 11.429688 C 62.785156 11.5625 62.707031 11.65625 62.617188 11.765625 C 62.605469 11.78125 62.59375 11.792969 62.578125 11.808594 C 62.375 12.03125 62.085938 12.183594 61.800781 12.269531 C 61.777344 12.277344 61.75 12.285156 61.726562 12.292969 C 61.511719 12.347656 61.296875 12.351562 61.078125 12.351562 C 61.046875 12.351562 61.019531 12.351562 60.988281 12.351562 C 60.523438 12.351562 60.085938 12.210938 59.730469 11.898438 C 59.542969 11.699219 59.425781 11.40625 59.375 11.140625 C 59.371094 11.117188 59.367188 11.097656 59.363281 11.074219 C 59.351562 10.988281 59.347656 10.902344 59.347656 10.8125 C 59.347656 10.792969 59.347656 10.777344 59.347656 10.757812 C 59.347656 10.695312 59.347656 10.636719 59.347656 10.578125 C 59.347656 10.535156 59.347656 10.492188 59.347656 10.449219 C 59.347656 10.332031 59.347656 10.214844 59.347656 10.097656 C 59.351562 9.972656 59.351562 9.851562 59.351562 9.730469 C 59.351562 9.496094 59.351562 9.265625 59.351562 9.035156 C 59.351562 8.769531 59.351562 8.507812 59.351562 8.242188 C 59.351562 7.703125 59.351562 7.160156 59.351562 6.617188 C 59.035156 6.617188 58.71875 6.617188 58.390625 6.617188 C 58.390625 6.371094 58.390625 6.125 58.390625 5.871094 C 58.914062 5.800781 58.914062 5.800781 59.449219 5.726562 C 59.480469 5.601562 59.515625 5.476562 59.550781 5.351562 C 59.574219 5.269531 59.59375 5.191406 59.617188 5.113281 C 59.652344 4.988281 59.6875 4.863281 59.722656 4.738281 C 59.75 4.640625 59.777344 4.539062 59.804688 4.4375 C 59.816406 4.398438 59.824219 4.359375 59.835938 4.324219 C 59.851562 4.269531 59.867188 4.214844 59.882812 4.160156 C 59.886719 4.144531 59.890625 4.128906 59.894531 4.113281 C 59.914062 4.046875 59.929688 3.984375 59.953125 3.921875 Z M 59.953125 3.921875 " + }, + "children": [] + }, + { + "type": "element", + "name": "path", + "attributes": { + "style": " stroke:none;fill-rule:nonzero;fill:rgb(10.196079%,11.372549%,14.117648%);fill-opacity:1;", + "d": "M 93.351562 5.554688 C 93.40625 5.59375 93.429688 5.617188 93.445312 5.679688 C 93.449219 5.75 93.445312 5.8125 93.441406 5.878906 C 93.441406 5.90625 93.441406 5.929688 93.441406 5.957031 C 93.4375 6.015625 93.4375 6.070312 93.433594 6.128906 C 93.429688 6.273438 93.425781 6.417969 93.421875 6.5625 C 93.417969 6.617188 93.417969 6.671875 93.417969 6.726562 C 93.402344 7.175781 93.40625 7.625 93.40625 8.074219 C 93.40625 8.195312 93.40625 8.3125 93.40625 8.429688 C 93.40625 8.644531 93.40625 8.859375 93.40625 9.074219 C 93.40625 9.320312 93.40625 9.570312 93.40625 9.816406 C 93.40625 10.320312 93.40625 10.828125 93.40625 11.332031 C 93.425781 11.335938 93.449219 11.339844 93.46875 11.34375 C 93.542969 11.355469 93.613281 11.371094 93.6875 11.382812 C 93.734375 11.390625 93.785156 11.398438 93.832031 11.40625 C 93.859375 11.414062 93.890625 11.417969 93.921875 11.425781 C 93.949219 11.429688 93.976562 11.433594 94.003906 11.4375 C 94.070312 11.449219 94.136719 11.464844 94.199219 11.476562 C 94.199219 11.675781 94.199219 11.875 94.199219 12.078125 C 93.105469 12.078125 92.015625 12.078125 90.886719 12.078125 C 90.886719 11.878906 90.886719 11.679688 90.886719 11.476562 C 90.988281 11.457031 91.085938 11.433594 91.1875 11.414062 C 91.222656 11.40625 91.253906 11.402344 91.289062 11.394531 C 91.339844 11.382812 91.386719 11.375 91.4375 11.363281 C 91.480469 11.355469 91.480469 11.355469 91.527344 11.34375 C 91.601562 11.332031 91.675781 11.332031 91.753906 11.332031 C 91.753906 10.894531 91.753906 10.457031 91.753906 10.023438 C 91.753906 9.820312 91.753906 9.617188 91.753906 9.414062 C 91.753906 9.234375 91.753906 9.058594 91.753906 8.882812 C 91.753906 8.789062 91.753906 8.695312 91.753906 8.601562 C 91.753906 8.066406 91.746094 7.535156 91.726562 7 C 91.683594 6.996094 91.683594 6.996094 91.636719 6.988281 C 91.539062 6.976562 91.4375 6.964844 91.339844 6.953125 C 91.296875 6.945312 91.25 6.941406 91.207031 6.933594 C 91.144531 6.925781 91.082031 6.917969 91.019531 6.910156 C 90.988281 6.90625 90.988281 6.90625 90.957031 6.902344 C 90.820312 6.882812 90.820312 6.882812 90.792969 6.855469 C 90.789062 6.816406 90.789062 6.777344 90.789062 6.738281 C 90.789062 6.714844 90.789062 6.691406 90.789062 6.667969 C 90.789062 6.640625 90.789062 6.617188 90.789062 6.589844 C 90.789062 6.566406 90.789062 6.539062 90.789062 6.515625 C 90.792969 6.453125 90.792969 6.390625 90.792969 6.328125 C 90.96875 6.253906 91.148438 6.1875 91.328125 6.125 C 91.355469 6.117188 91.382812 6.105469 91.414062 6.097656 C 91.503906 6.066406 91.59375 6.03125 91.683594 6 C 91.808594 5.957031 91.929688 5.914062 92.054688 5.871094 C 92.070312 5.867188 92.085938 5.859375 92.101562 5.855469 C 92.285156 5.792969 92.46875 5.726562 92.652344 5.660156 C 92.667969 5.652344 92.6875 5.644531 92.703125 5.640625 C 92.78125 5.609375 92.859375 5.582031 92.9375 5.554688 C 92.976562 5.539062 92.976562 5.539062 93.019531 5.523438 C 93.050781 5.511719 93.050781 5.511719 93.082031 5.5 C 93.1875 5.476562 93.261719 5.503906 93.351562 5.554688 Z M 93.351562 5.554688 " + }, + "children": [] + }, + { + "type": "element", + "name": "path", + "attributes": { + "style": " stroke:none;fill-rule:nonzero;fill:rgb(10.196079%,11.372549%,14.117648%);fill-opacity:1;", + "d": "M 42.214844 5.652344 C 42.214844 7.542969 42.214844 9.433594 42.214844 11.378906 C 42.535156 11.441406 42.535156 11.441406 42.863281 11.5 C 42.917969 11.515625 42.976562 11.53125 43.03125 11.550781 C 43.03125 11.738281 43.03125 11.929688 43.03125 12.125 C 41.929688 12.125 40.832031 12.125 39.695312 12.125 C 39.695312 11.9375 39.695312 11.746094 39.695312 11.550781 C 39.875 11.5 40.054688 11.460938 40.234375 11.425781 C 40.265625 11.417969 40.265625 11.417969 40.296875 11.410156 C 40.316406 11.40625 40.335938 11.402344 40.355469 11.398438 C 40.375 11.394531 40.394531 11.390625 40.410156 11.386719 C 40.46875 11.378906 40.523438 11.378906 40.585938 11.378906 C 40.582031 10.886719 40.578125 10.390625 40.574219 9.898438 C 40.574219 9.667969 40.574219 9.4375 40.570312 9.207031 C 40.570312 9.007812 40.570312 8.808594 40.570312 8.605469 C 40.566406 8.5 40.566406 8.394531 40.566406 8.289062 C 40.566406 8.191406 40.566406 8.089844 40.566406 7.988281 C 40.566406 7.953125 40.566406 7.917969 40.566406 7.878906 C 40.5625 7.683594 40.558594 7.488281 40.550781 7.292969 C 40.546875 7.273438 40.546875 7.253906 40.546875 7.234375 C 40.542969 7.140625 40.542969 7.140625 40.511719 7.050781 C 40.445312 7.035156 40.378906 7.027344 40.308594 7.019531 C 40.289062 7.015625 40.269531 7.011719 40.25 7.011719 C 40.183594 7.003906 40.121094 6.996094 40.054688 6.988281 C 40.011719 6.980469 39.96875 6.976562 39.921875 6.96875 C 39.816406 6.957031 39.707031 6.941406 39.601562 6.929688 C 39.601562 6.746094 39.601562 6.5625 39.601562 6.375 C 39.964844 6.242188 40.328125 6.109375 40.695312 5.980469 C 40.777344 5.953125 40.859375 5.921875 40.945312 5.894531 C 41 5.875 41.054688 5.855469 41.109375 5.835938 C 41.246094 5.789062 41.386719 5.738281 41.523438 5.6875 C 41.550781 5.675781 41.578125 5.667969 41.605469 5.65625 C 41.660156 5.636719 41.710938 5.617188 41.761719 5.597656 C 41.785156 5.589844 41.808594 5.578125 41.835938 5.570312 C 41.863281 5.558594 41.863281 5.558594 41.894531 5.546875 C 42.027344 5.515625 42.09375 5.585938 42.214844 5.652344 Z M 42.214844 5.652344 " + }, + "children": [] + }, + { + "type": "element", + "name": "path", + "attributes": { + "style": " stroke:none;fill-rule:nonzero;fill:rgb(98.823529%,73.725492%,19.607843%);fill-opacity:1;", + "d": "M 8.640625 9.261719 C 8.972656 9.519531 9.191406 9.859375 9.277344 10.273438 C 9.328125 10.675781 9.265625 11.089844 9.019531 11.421875 C 8.734375 11.769531 8.371094 12.007812 7.917969 12.058594 C 7.476562 12.09375 7.078125 11.957031 6.742188 11.667969 C 6.71875 11.648438 6.71875 11.648438 6.695312 11.628906 C 6.421875 11.378906 6.261719 10.992188 6.234375 10.628906 C 6.226562 10.179688 6.355469 9.789062 6.664062 9.457031 C 7.191406 8.921875 8.019531 8.84375 8.640625 9.261719 Z M 8.640625 9.261719 " + }, + "children": [] + }, + { + "type": "element", + "name": "path", + "attributes": { + "style": " stroke:none;fill-rule:nonzero;fill:rgb(98.823529%,73.725492%,19.607843%);fill-opacity:1;", + "d": "M 2.855469 4.089844 C 2.941406 4.15625 3.019531 4.230469 3.097656 4.308594 C 3.113281 4.324219 3.128906 4.339844 3.148438 4.359375 C 3.414062 4.640625 3.542969 5.027344 3.539062 5.410156 C 3.519531 5.851562 3.332031 6.21875 3.015625 6.527344 C 2.707031 6.792969 2.304688 6.921875 1.898438 6.898438 C 1.578125 6.871094 1.308594 6.769531 1.054688 6.570312 C 1.03125 6.546875 1.03125 6.546875 1.003906 6.527344 C 0.699219 6.277344 0.527344 5.898438 0.484375 5.511719 C 0.453125 5.121094 0.558594 4.730469 0.804688 4.425781 C 1.003906 4.191406 1.226562 4.03125 1.511719 3.921875 C 1.53125 3.914062 1.550781 3.90625 1.570312 3.898438 C 1.988281 3.757812 2.496094 3.84375 2.855469 4.089844 Z M 2.855469 4.089844 " + }, + "children": [] + }, + { + "type": "element", + "name": "path", + "attributes": { + "style": " stroke:none;fill-rule:nonzero;fill:rgb(98.823529%,73.725492%,19.607843%);fill-opacity:1;", + "d": "M 2.960938 11.683594 C 3.269531 11.949219 3.492188 12.316406 3.53125 12.726562 C 3.558594 13.152344 3.449219 13.550781 3.171875 13.878906 C 2.875 14.203125 2.519531 14.375 2.082031 14.417969 C 1.671875 14.433594 1.28125 14.28125 0.972656 14.011719 C 0.660156 13.714844 0.488281 13.324219 0.476562 12.890625 C 0.480469 12.53125 0.59375 12.214844 0.816406 11.933594 C 0.828125 11.917969 0.839844 11.902344 0.851562 11.882812 C 1.078125 11.597656 1.433594 11.421875 1.785156 11.363281 C 2.207031 11.316406 2.628906 11.417969 2.960938 11.683594 Z M 2.960938 11.683594 " + }, + "children": [] + }, + { + "type": "element", + "name": "path", + "attributes": { + "style": " stroke:none;fill-rule:nonzero;fill:rgb(98.823529%,73.725492%,19.607843%);fill-opacity:1;", + "d": "M 14.449219 4.167969 C 14.507812 4.222656 14.5625 4.273438 14.617188 4.332031 C 14.628906 4.34375 14.640625 4.355469 14.65625 4.367188 C 14.914062 4.632812 15.015625 5.023438 15.03125 5.378906 C 15.019531 5.734375 14.902344 6.046875 14.6875 6.328125 C 14.675781 6.34375 14.664062 6.359375 14.652344 6.378906 C 14.410156 6.679688 14.042969 6.851562 13.664062 6.898438 C 13.242188 6.925781 12.84375 6.816406 12.523438 6.535156 C 12.484375 6.5 12.445312 6.460938 12.40625 6.425781 C 12.394531 6.410156 12.378906 6.394531 12.363281 6.378906 C 12.085938 6.089844 11.988281 5.707031 11.992188 5.316406 C 12.003906 4.914062 12.167969 4.53125 12.457031 4.25 C 13.023438 3.75 13.847656 3.714844 14.449219 4.167969 Z M 14.449219 4.167969 " + }, + "children": [] + }, + { + "type": "element", + "name": "path", + "attributes": { + "style": " stroke:none;fill-rule:nonzero;fill:rgb(10.196079%,11.372549%,14.117648%);fill-opacity:1;", + "d": "M 42.046875 2.648438 C 42.253906 2.816406 42.375 3.03125 42.40625 3.296875 C 42.433594 3.558594 42.351562 3.808594 42.191406 4.019531 C 42.007812 4.214844 41.785156 4.351562 41.507812 4.363281 C 41.46875 4.363281 41.429688 4.363281 41.390625 4.363281 C 41.371094 4.363281 41.351562 4.363281 41.332031 4.363281 C 41.058594 4.355469 40.832031 4.273438 40.625 4.089844 C 40.476562 3.933594 40.359375 3.734375 40.34375 3.511719 C 40.34375 3.496094 40.339844 3.480469 40.339844 3.460938 C 40.328125 3.230469 40.390625 2.992188 40.542969 2.8125 C 40.554688 2.796875 40.570312 2.78125 40.585938 2.765625 C 40.597656 2.75 40.613281 2.734375 40.632812 2.714844 C 41.019531 2.339844 41.621094 2.332031 42.046875 2.648438 Z M 42.046875 2.648438 " + }, + "children": [] + }, + { + "type": "element", + "name": "path", + "attributes": { + "style": " stroke:none;fill-rule:nonzero;fill:rgb(10.196079%,11.372549%,14.117648%);fill-opacity:1;", + "d": "M 93.210938 2.566406 C 93.453125 2.757812 93.570312 2.96875 93.609375 3.273438 C 93.621094 3.53125 93.550781 3.773438 93.378906 3.972656 C 93.367188 3.988281 93.351562 4.003906 93.335938 4.019531 C 93.316406 4.039062 93.316406 4.039062 93.296875 4.058594 C 93.066406 4.28125 92.78125 4.316406 92.476562 4.3125 C 92.355469 4.308594 92.25 4.285156 92.136719 4.234375 C 92.117188 4.226562 92.101562 4.21875 92.082031 4.210938 C 91.871094 4.105469 91.703125 3.9375 91.605469 3.722656 C 91.515625 3.449219 91.515625 3.171875 91.632812 2.90625 C 91.773438 2.652344 92 2.484375 92.277344 2.402344 C 92.605469 2.324219 92.933594 2.375 93.210938 2.566406 Z M 93.210938 2.566406 " + }, + "children": [] + }, + { + "type": "element", + "name": "path", + "attributes": { + "style": " stroke:none;fill-rule:nonzero;fill:rgb(98.823529%,73.725492%,19.607843%);fill-opacity:1;", + "d": "M 8.320312 5.960938 C 8.539062 6.117188 8.664062 6.320312 8.726562 6.582031 C 8.769531 6.839844 8.710938 7.089844 8.574219 7.3125 C 8.410156 7.535156 8.207031 7.65625 7.945312 7.722656 C 7.621094 7.753906 7.355469 7.6875 7.105469 7.484375 C 6.921875 7.3125 6.8125 7.078125 6.792969 6.832031 C 6.789062 6.535156 6.871094 6.28125 7.078125 6.0625 C 7.4375 5.738281 7.914062 5.710938 8.320312 5.960938 Z M 8.320312 5.960938 " + }, + "children": [] + }, + { + "type": "element", + "name": "path", + "attributes": { + "style": " stroke:none;fill-rule:nonzero;fill:rgb(98.431373%,73.725492%,19.607843%);fill-opacity:1;", + "d": "M 14.09375 0.820312 C 14.289062 0.96875 14.421875 1.179688 14.472656 1.417969 C 14.5 1.714844 14.464844 1.980469 14.273438 2.214844 C 14.082031 2.421875 13.890625 2.558594 13.605469 2.582031 C 13.320312 2.585938 13.078125 2.535156 12.863281 2.332031 C 12.660156 2.128906 12.550781 1.910156 12.542969 1.621094 C 12.546875 1.332031 12.644531 1.117188 12.839844 0.910156 C 13.199219 0.574219 13.6875 0.5625 14.09375 0.820312 Z M 14.09375 0.820312 " + }, + "children": [] + }, + { + "type": "element", + "name": "path", + "attributes": { + "style": " stroke:none;fill-rule:nonzero;fill:rgb(98.823529%,73.725492%,19.607843%);fill-opacity:1;", + "d": "M 2.574219 0.808594 C 2.769531 0.972656 2.925781 1.164062 2.976562 1.417969 C 2.996094 1.71875 2.972656 1.976562 2.777344 2.214844 C 2.585938 2.421875 2.394531 2.558594 2.109375 2.582031 C 1.824219 2.585938 1.582031 2.53125 1.367188 2.332031 C 1.226562 2.195312 1.136719 2.066406 1.078125 1.875 C 1.074219 1.863281 1.070312 1.847656 1.066406 1.832031 C 1.015625 1.570312 1.054688 1.3125 1.195312 1.089844 C 1.515625 0.644531 2.101562 0.484375 2.574219 0.808594 Z M 2.574219 0.808594 " + }, + "children": [] + }, + { + "type": "element", + "name": "path", + "attributes": { + "style": " stroke:none;fill-rule:nonzero;fill:rgb(98.823529%,73.725492%,19.607843%);fill-opacity:1;", + "d": "M 2.550781 8.316406 C 2.582031 8.34375 2.609375 8.371094 2.640625 8.398438 C 2.65625 8.414062 2.671875 8.429688 2.691406 8.445312 C 2.878906 8.640625 2.960938 8.847656 2.964844 9.121094 C 2.960938 9.398438 2.882812 9.613281 2.6875 9.816406 C 2.5 9.996094 2.257812 10.109375 1.992188 10.105469 C 1.695312 10.085938 1.449219 9.972656 1.246094 9.753906 C 1.074219 9.535156 1.003906 9.277344 1.03125 9 C 1.089844 8.710938 1.214844 8.484375 1.453125 8.3125 C 1.789062 8.105469 2.222656 8.085938 2.550781 8.316406 Z M 2.550781 8.316406 " + }, + "children": [] + }, + { + "type": "element", + "name": "path", + "attributes": { + "style": " stroke:none;fill-rule:nonzero;fill:rgb(98.823529%,73.725492%,19.607843%);fill-opacity:1;", + "d": "M 14.058594 8.34375 C 14.269531 8.496094 14.425781 8.714844 14.472656 8.972656 C 14.496094 9.300781 14.441406 9.542969 14.238281 9.804688 C 14.148438 9.90625 14.042969 9.972656 13.921875 10.03125 C 13.894531 10.046875 13.894531 10.046875 13.867188 10.058594 C 13.660156 10.144531 13.386719 10.136719 13.175781 10.066406 C 12.929688 9.960938 12.714844 9.769531 12.613281 9.519531 C 12.601562 9.484375 12.585938 9.445312 12.574219 9.40625 C 12.570312 9.390625 12.566406 9.378906 12.5625 9.363281 C 12.511719 9.109375 12.550781 8.855469 12.679688 8.632812 C 12.824219 8.421875 13.003906 8.277344 13.246094 8.203125 C 13.261719 8.199219 13.277344 8.195312 13.292969 8.191406 C 13.570312 8.136719 13.820312 8.195312 14.058594 8.34375 Z M 14.058594 8.34375 " + }, + "children": [] + }, + { + "type": "element", + "name": "path", + "attributes": { + "style": " stroke:none;fill-rule:nonzero;fill:rgb(98.823529%,73.725492%,19.607843%);fill-opacity:1;", + "d": "M 8.375 13.523438 C 8.605469 13.730469 8.71875 13.984375 8.742188 14.289062 C 8.734375 14.554688 8.621094 14.789062 8.445312 14.984375 C 8.25 15.164062 7.996094 15.25 7.734375 15.253906 C 7.46875 15.242188 7.25 15.136719 7.0625 14.953125 C 6.863281 14.730469 6.789062 14.488281 6.792969 14.195312 C 6.832031 13.894531 6.964844 13.664062 7.199219 13.472656 C 7.582031 13.230469 8.015625 13.25 8.375 13.523438 Z M 8.375 13.523438 " + }, + "children": [] + }, + { + "type": "element", + "name": "path", + "attributes": { + "style": " stroke:none;fill-rule:nonzero;fill:rgb(98.823529%,73.725492%,19.607843%);fill-opacity:1;", + "d": "M 14.027344 12.066406 C 14.25 12.234375 14.421875 12.449219 14.472656 12.726562 C 14.492188 13.019531 14.464844 13.269531 14.273438 13.5 C 14.089844 13.699219 13.886719 13.84375 13.609375 13.863281 C 13.3125 13.867188 13.058594 13.800781 12.832031 13.589844 C 12.730469 13.480469 12.65625 13.371094 12.601562 13.234375 C 12.589844 13.207031 12.582031 13.183594 12.570312 13.15625 C 12.519531 12.886719 12.539062 12.625 12.679688 12.386719 C 12.984375 11.945312 13.558594 11.765625 14.027344 12.066406 Z M 14.027344 12.066406 " + }, + "children": [] + }, + { + "type": "element", + "name": "path", + "attributes": { + "style": " stroke:none;fill-rule:nonzero;fill:rgb(98.823529%,73.725492%,19.607843%);fill-opacity:1;", + "d": "M 8.269531 2.203125 C 8.492188 2.371094 8.652344 2.574219 8.703125 2.855469 C 8.738281 3.113281 8.695312 3.371094 8.535156 3.582031 C 8.355469 3.796875 8.136719 3.949219 7.851562 3.976562 C 7.539062 3.988281 7.289062 3.894531 7.054688 3.679688 C 6.851562 3.449219 6.796875 3.222656 6.808594 2.914062 C 6.824219 2.671875 6.929688 2.480469 7.105469 2.308594 C 7.445312 2.039062 7.886719 1.964844 8.269531 2.203125 Z M 8.269531 2.203125 " + }, + "children": [] + } + ] + } + ] + }, + "name": "WeaveIconBig" +} diff --git a/web/app/components/base/icons/src/public/tracing/WeaveIconBig.tsx b/web/app/components/base/icons/src/public/tracing/WeaveIconBig.tsx new file mode 100644 index 0000000000..1d2bb9fcc2 --- /dev/null +++ b/web/app/components/base/icons/src/public/tracing/WeaveIconBig.tsx @@ -0,0 +1,16 @@ +// GENERATE BY script +// DON NOT EDIT IT MANUALLY + +import * as React from 'react' +import data from './WeaveIconBig.json' +import IconBase from '@/app/components/base/icons/IconBase' +import type { IconBaseProps, IconData } from '@/app/components/base/icons/IconBase' + +const Icon = React.forwardRef<React.MutableRefObject<SVGElement>, Omit<IconBaseProps, 'data'>>(( + props, + ref, +) => <IconBase {...props} ref={ref} data={data as IconData} />) + +Icon.displayName = 'WeaveIconBig' + +export default Icon diff --git a/web/app/components/base/icons/src/public/tracing/index.ts b/web/app/components/base/icons/src/public/tracing/index.ts index 09ffd54bd4..36b59e479b 100644 --- a/web/app/components/base/icons/src/public/tracing/index.ts +++ b/web/app/components/base/icons/src/public/tracing/index.ts @@ -5,3 +5,5 @@ export { default as LangsmithIcon } from './LangsmithIcon' export { default as OpikIconBig } from './OpikIconBig' export { default as OpikIcon } from './OpikIcon' export { default as TracingIcon } from './TracingIcon' +export { default as WeaveIconBig } from './WeaveIconBig' +export { default as WeaveIcon } from './WeaveIcon' diff --git a/web/app/components/base/icons/src/vender/line/editor/Collapse.json b/web/app/components/base/icons/src/vender/line/editor/Collapse.json new file mode 100644 index 0000000000..5e3cf08ce0 --- /dev/null +++ b/web/app/components/base/icons/src/vender/line/editor/Collapse.json @@ -0,0 +1,62 @@ +{ + "icon": { + "type": "element", + "isRootNode": true, + "name": "svg", + "attributes": { + "width": "16", + "height": "16", + "viewBox": "0 0 16 16", + "fill": "none", + "xmlns": "http://www.w3.org/2000/svg" + }, + "children": [ + { + "type": "element", + "name": "g", + "attributes": { + "id": "Icon L" + }, + "children": [ + { + "type": "element", + "name": "g", + "attributes": { + "id": "Vector" + }, + "children": [ + { + "type": "element", + "name": "path", + "attributes": { + "d": "M2.66602 11.3333H0.666016L3.33268 8.66667L5.99935 11.3333H3.99935L3.99935 14H2.66602L2.66602 11.3333Z", + "fill": "currentColor" + }, + "children": [] + }, + { + "type": "element", + "name": "path", + "attributes": { + "d": "M2.66602 4.66667L2.66602 2L3.99935 2L3.99935 4.66667L5.99935 4.66667L3.33268 7.33333L0.666016 4.66667L2.66602 4.66667Z", + "fill": "currentColor" + }, + "children": [] + }, + { + "type": "element", + "name": "path", + "attributes": { + "d": "M7.33268 2.66667H13.9993V4H7.33268V2.66667ZM7.33268 12H13.9993V13.3333H7.33268V12ZM5.99935 7.33333H13.9993V8.66667H5.99935V7.33333Z", + "fill": "currentColor" + }, + "children": [] + } + ] + } + ] + } + ] + }, + "name": "Collapse" +} \ No newline at end of file diff --git a/web/app/components/base/icons/src/vender/line/editor/Collapse.tsx b/web/app/components/base/icons/src/vender/line/editor/Collapse.tsx new file mode 100644 index 0000000000..6f43dde272 --- /dev/null +++ b/web/app/components/base/icons/src/vender/line/editor/Collapse.tsx @@ -0,0 +1,20 @@ +// GENERATE BY script +// DON NOT EDIT IT MANUALLY + +import * as React from 'react' +import data from './Collapse.json' +import IconBase from '@/app/components/base/icons/IconBase' +import type { IconData } from '@/app/components/base/icons/IconBase' + +const Icon = ( + { + ref, + ...props + }: React.SVGProps<SVGSVGElement> & { + ref?: React.RefObject<React.MutableRefObject<HTMLOrSVGElement>>; + }, +) => <IconBase {...props} ref={ref} data={data as IconData} /> + +Icon.displayName = 'Collapse' + +export default Icon diff --git a/web/app/components/base/icons/src/vender/line/editor/index.ts b/web/app/components/base/icons/src/vender/line/editor/index.ts index f571be03c6..b31c42e390 100644 --- a/web/app/components/base/icons/src/vender/line/editor/index.ts +++ b/web/app/components/base/icons/src/vender/line/editor/index.ts @@ -1,5 +1,6 @@ export { default as AlignLeft } from './AlignLeft' export { default as BezierCurve03 } from './BezierCurve03' +export { default as Collapse } from './Collapse' export { default as Colors } from './Colors' export { default as ImageIndentLeft } from './ImageIndentLeft' export { default as LeftIndent02 } from './LeftIndent02' diff --git a/web/app/components/base/image-gallery/style.module.css b/web/app/components/base/image-gallery/style.module.css index 64756a5d69..2e4c62e456 100644 --- a/web/app/components/base/image-gallery/style.module.css +++ b/web/app/components/base/image-gallery/style.module.css @@ -19,4 +19,4 @@ .img-4 .item:nth-child(3n) { margin-right: 8px; -} \ No newline at end of file +} diff --git a/web/app/components/base/image-uploader/text-generation-image-uploader.tsx b/web/app/components/base/image-uploader/text-generation-image-uploader.tsx index a3a57f1cb4..99aef56250 100644 --- a/web/app/components/base/image-uploader/text-generation-image-uploader.tsx +++ b/web/app/components/base/image-uploader/text-generation-image-uploader.tsx @@ -98,7 +98,7 @@ const TextGenerationImageUploader: FC<TextGenerationImageUploaderProps> = ({ { hovering => ( <div className={` - flex h-8 cursor-pointer items-center justify-center rounded-lg + flex h-8 cursor-pointer items-center justify-center rounded-lg bg-components-button-tertiary-bg px-3 text-xs text-text-tertiary ${hovering && 'hover:bg-components-button-tertiary-bg-hover'} `}> diff --git a/web/app/components/base/install-button/index.tsx b/web/app/components/base/install-button/index.tsx index d8983bb68e..0d9e953d5e 100644 --- a/web/app/components/base/install-button/index.tsx +++ b/web/app/components/base/install-button/index.tsx @@ -10,8 +10,8 @@ type InstallButtonProps = { const InstallButton = ({ loading, onInstall, t }: InstallButtonProps) => { return ( <Button size='small' className='z-[100]' onClick={onInstall}> - <div className={`flex items-center justify-center gap-1 px-[3px] - ${loading ? 'text-components-button-secondary-text-disabled' : 'text-components-button-secondary-text'} + <div className={`flex items-center justify-center gap-1 px-[3px] + ${loading ? 'text-components-button-secondary-text-disabled' : 'text-components-button-secondary-text'} system-xs-medium`} > {loading ? t('workflow.nodes.agent.pluginInstaller.installing') : t('workflow.nodes.agent.pluginInstaller.install')} diff --git a/web/app/components/base/logo/dify-logo.tsx b/web/app/components/base/logo/dify-logo.tsx new file mode 100644 index 0000000000..5369144e1c --- /dev/null +++ b/web/app/components/base/logo/dify-logo.tsx @@ -0,0 +1,44 @@ +'use client' +import type { FC } from 'react' +import classNames from '@/utils/classnames' +import useTheme from '@/hooks/use-theme' +import { basePath } from '@/utils/var' +export type LogoStyle = 'default' | 'monochromeWhite' + +export const logoPathMap: Record<LogoStyle, string> = { + default: '/logo/logo.svg', + monochromeWhite: '/logo/logo-monochrome-white.svg', +} + +export type LogoSize = 'large' | 'medium' | 'small' + +export const logoSizeMap: Record<LogoSize, string> = { + large: 'w-16 h-7', + medium: 'w-12 h-[22px]', + small: 'w-9 h-4', +} + +type DifyLogoProps = { + style?: LogoStyle + size?: LogoSize + className?: string +} + +const DifyLogo: FC<DifyLogoProps> = ({ + style = 'default', + size = 'medium', + className, +}) => { + const { theme } = useTheme() + const themedStyle = (theme === 'dark' && style === 'default') ? 'monochromeWhite' : style + + return ( + <img + src={`${basePath}${logoPathMap[themedStyle]}`} + className={classNames('block object-contain', logoSizeMap[size], className)} + alt='Dify logo' + /> + ) +} + +export default DifyLogo diff --git a/web/app/components/base/logo/logo-embedded-chat-avatar.tsx b/web/app/components/base/logo/logo-embedded-chat-avatar.tsx index 8ea60e1ce8..f09a6a03ed 100644 --- a/web/app/components/base/logo/logo-embedded-chat-avatar.tsx +++ b/web/app/components/base/logo/logo-embedded-chat-avatar.tsx @@ -1,4 +1,5 @@ import type { FC } from 'react' +import { basePath } from '@/utils/var' type LogoEmbeddedChatAvatarProps = { className?: string @@ -8,7 +9,7 @@ const LogoEmbeddedChatAvatar: FC<LogoEmbeddedChatAvatarProps> = ({ }) => { return ( <img - src='/logo/logo-embedded-chat-avatar.png' + src={`${basePath}/logo/logo-embedded-chat-avatar.png`} className={`block h-10 w-10 ${className}`} alt='logo' /> diff --git a/web/app/components/base/markdown-blocks/audio-block.tsx b/web/app/components/base/markdown-blocks/audio-block.tsx new file mode 100644 index 0000000000..09001f105b --- /dev/null +++ b/web/app/components/base/markdown-blocks/audio-block.tsx @@ -0,0 +1,21 @@ +/** + * @fileoverview AudioBlock component for rendering audio elements in Markdown. + * Extracted from the main markdown renderer for modularity. + * Uses the AudioGallery component to display audio players. + */ +import React, { memo } from 'react' +import AudioGallery from '@/app/components/base/audio-gallery' + +const AudioBlock: any = memo(({ node }: any) => { + const srcs = node.children.filter((child: any) => 'properties' in child).map((child: any) => (child as any).properties.src) + if (srcs.length === 0) { + const src = node.properties?.src + if (src) + return <AudioGallery key={src} srcs={[src]} /> + return null + } + return <AudioGallery key={srcs.join()} srcs={srcs} /> +}) +AudioBlock.displayName = 'AudioBlock' + +export default AudioBlock diff --git a/web/app/components/base/markdown-blocks/button.tsx b/web/app/components/base/markdown-blocks/button.tsx index e1bd234f59..315653bcd0 100644 --- a/web/app/components/base/markdown-blocks/button.tsx +++ b/web/app/components/base/markdown-blocks/button.tsx @@ -1,7 +1,7 @@ import { useChatContext } from '@/app/components/base/chat/chat/context' import Button from '@/app/components/base/button' import cn from '@/utils/classnames' - +import { isValidUrl } from './utils' const MarkdownButton = ({ node }: any) => { const { onSend } = useChatContext() const variant = node.properties.dataVariant @@ -9,25 +9,17 @@ const MarkdownButton = ({ node }: any) => { const link = node.properties.dataLink const size = node.properties.dataSize - function is_valid_url(url: string): boolean { - try { - const parsed_url = new URL(url) - return ['http:', 'https:'].includes(parsed_url.protocol) - } - catch { - return false - } - } - return <Button variant={variant} size={size} - className={cn('!h-8 select-none !px-3')} + className={cn('!h-auto min-h-8 select-none whitespace-normal !px-3')} onClick={() => { - if (is_valid_url(link)) { + if (link && isValidUrl(link)) { window.open(link, '_blank') return } + if(!message) + return onSend?.(message) }} > diff --git a/web/app/components/base/markdown-blocks/code-block.tsx b/web/app/components/base/markdown-blocks/code-block.tsx new file mode 100644 index 0000000000..9f8a6a87bb --- /dev/null +++ b/web/app/components/base/markdown-blocks/code-block.tsx @@ -0,0 +1,388 @@ +import { memo, useEffect, useMemo, useRef, useState } from 'react' +import ReactEcharts from 'echarts-for-react' +import SyntaxHighlighter from 'react-syntax-highlighter' +import { + atelierHeathDark, + atelierHeathLight, +} from 'react-syntax-highlighter/dist/esm/styles/hljs' +import ActionButton from '@/app/components/base/action-button' +import CopyIcon from '@/app/components/base/copy-icon' +import SVGBtn from '@/app/components/base/svg' +import Flowchart from '@/app/components/base/mermaid' +import { Theme } from '@/types/app' +import useTheme from '@/hooks/use-theme' +import SVGRenderer from '../svg-gallery' // Assumes svg-gallery.tsx is in /base directory +import MarkdownMusic from '@/app/components/base/markdown-blocks/music' +import ErrorBoundary from '@/app/components/base/markdown/error-boundary' + +// Available language https://github.com/react-syntax-highlighter/react-syntax-highlighter/blob/master/AVAILABLE_LANGUAGES_HLJS.MD +const capitalizationLanguageNameMap: Record<string, string> = { + sql: 'SQL', + javascript: 'JavaScript', + java: 'Java', + typescript: 'TypeScript', + vbscript: 'VBScript', + css: 'CSS', + html: 'HTML', + xml: 'XML', + php: 'PHP', + python: 'Python', + yaml: 'Yaml', + mermaid: 'Mermaid', + markdown: 'MarkDown', + makefile: 'MakeFile', + echarts: 'ECharts', + shell: 'Shell', + powershell: 'PowerShell', + json: 'JSON', + latex: 'Latex', + svg: 'SVG', + abc: 'ABC', +} +const getCorrectCapitalizationLanguageName = (language: string) => { + if (!language) + return 'Plain' + + if (language in capitalizationLanguageNameMap) + return capitalizationLanguageNameMap[language] + + return language.charAt(0).toUpperCase() + language.substring(1) +} + +// **Add code block +// Avoid error #185 (Maximum update depth exceeded. +// This can happen when a component repeatedly calls setState inside componentWillUpdate or componentDidUpdate. +// React limits the number of nested updates to prevent infinite loops.) +// Reference A: https://reactjs.org/docs/error-decoder.html?invariant=185 +// Reference B1: https://react.dev/reference/react/memo +// Reference B2: https://react.dev/reference/react/useMemo +// **** +// The original error that occurred in the streaming response during the conversation: +// Error: Minified React error 185; +// visit https://reactjs.org/docs/error-decoder.html?invariant=185 for the full message +// or use the non-minified dev environment for full errors and additional helpful warnings. + +const CodeBlock: any = memo(({ inline, className, children = '', ...props }: any) => { + const { theme } = useTheme() + const [isSVG, setIsSVG] = useState(true) + const [chartState, setChartState] = useState<'loading' | 'success' | 'error'>('loading') + const [finalChartOption, setFinalChartOption] = useState<any>(null) + const echartsRef = useRef<any>(null) + const contentRef = useRef<string>('') + const processedRef = useRef<boolean>(false) // Track if content was successfully processed + const match = /language-(\w+)/.exec(className || '') + const language = match?.[1] + const languageShowName = getCorrectCapitalizationLanguageName(language || '') + const isDarkMode = theme === Theme.dark + + const echartsStyle = useMemo(() => ({ + height: '350px', + width: '100%', + }), []) + + const echartsOpts = useMemo(() => ({ + renderer: 'canvas', + width: 'auto', + }) as any, []) + + const echartsOnEvents = useMemo(() => ({ + finished: () => { + const instance = echartsRef.current?.getEchartsInstance?.() + if (instance) + instance.resize() + }, + }), [echartsRef]) // echartsRef is stable, so this effectively runs once. + + // Handle container resize for echarts + useEffect(() => { + if (language !== 'echarts' || !echartsRef.current) return + + const handleResize = () => { + // This gets the echarts instance from the component + const instance = echartsRef.current?.getEchartsInstance?.() + if (instance) + instance.resize() + } + + window.addEventListener('resize', handleResize) + + // Also manually trigger resize after a short delay to ensure proper sizing + const resizeTimer = setTimeout(handleResize, 200) + + return () => { + window.removeEventListener('resize', handleResize) + clearTimeout(resizeTimer) + } + }, [language, echartsRef.current]) + + // Process chart data when content changes + useEffect(() => { + // Only process echarts content + if (language !== 'echarts') return + + // Reset state when new content is detected + if (!contentRef.current) { + setChartState('loading') + processedRef.current = false + } + + const newContent = String(children).replace(/\n$/, '') + + // Skip if content hasn't changed + if (contentRef.current === newContent) return + contentRef.current = newContent + + const trimmedContent = newContent.trim() + if (!trimmedContent) return + + // Detect if this is historical data (already complete) + // Historical data typically comes as a complete code block with complete JSON + const isCompleteJson + = (trimmedContent.startsWith('{') && trimmedContent.endsWith('}') + && trimmedContent.split('{').length === trimmedContent.split('}').length) + || (trimmedContent.startsWith('[') && trimmedContent.endsWith(']') + && trimmedContent.split('[').length === trimmedContent.split(']').length) + + // If the JSON structure looks complete, try to parse it right away + if (isCompleteJson && !processedRef.current) { + try { + const parsed = JSON.parse(trimmedContent) + if (typeof parsed === 'object' && parsed !== null) { + setFinalChartOption(parsed) + setChartState('success') + processedRef.current = true + return + } + } + catch { + try { + // eslint-disable-next-line no-new-func, sonarjs/code-eval + const result = new Function(`return ${trimmedContent}`)() + if (typeof result === 'object' && result !== null) { + setFinalChartOption(result) + setChartState('success') + processedRef.current = true + return + } + } + catch { + // If we have a complete JSON structure but it doesn't parse, + // it's likely an error rather than incomplete data + setChartState('error') + processedRef.current = true + return + } + } + } + + // If we get here, either the JSON isn't complete yet, or we failed to parse it + // Check more conditions for streaming data + const isIncomplete + = trimmedContent.length < 5 + || (trimmedContent.startsWith('{') + && (!trimmedContent.endsWith('}') + || trimmedContent.split('{').length !== trimmedContent.split('}').length)) + || (trimmedContent.startsWith('[') + && (!trimmedContent.endsWith(']') + || trimmedContent.split('[').length !== trimmedContent.split('}').length)) + || (trimmedContent.split('"').length % 2 !== 1) + || (trimmedContent.includes('{"') && !trimmedContent.includes('"}')) + + // Only try to parse streaming data if it looks complete and hasn't been processed + if (!isIncomplete && !processedRef.current) { + let isValidOption = false + + try { + const parsed = JSON.parse(trimmedContent) + if (typeof parsed === 'object' && parsed !== null) { + setFinalChartOption(parsed) + isValidOption = true + } + } + catch { + try { + // eslint-disable-next-line no-new-func, sonarjs/code-eval + const result = new Function(`return ${trimmedContent}`)() + if (typeof result === 'object' && result !== null) { + setFinalChartOption(result) + isValidOption = true + } + } + catch { + // Both parsing methods failed, but content looks complete + setChartState('error') + processedRef.current = true + } + } + + if (isValidOption) { + setChartState('success') + processedRef.current = true + } + } + }, [language, children]) + + const renderCodeContent = useMemo(() => { + const content = String(children).replace(/\n$/, '') + switch (language) { + case 'mermaid': + if (isSVG) + return <Flowchart PrimitiveCode={content} /> + break + case 'echarts': { + // Loading state: show loading indicator + if (chartState === 'loading') { + return ( + <div style={{ + minHeight: '350px', + width: '100%', + display: 'flex', + flexDirection: 'column', + alignItems: 'center', + justifyContent: 'center', + borderBottomLeftRadius: '10px', + borderBottomRightRadius: '10px', + backgroundColor: isDarkMode ? 'var(--color-components-input-bg-normal)' : 'transparent', + color: 'var(--color-text-secondary)', + }}> + <div style={{ + marginBottom: '12px', + width: '24px', + height: '24px', + }}> + {/* Rotating spinner that works in both light and dark modes */} + <svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" style={{ animation: 'spin 1.5s linear infinite' }}> + <style> + {` + @keyframes spin { + 0% { transform: rotate(0deg); } + 100% { transform: rotate(360deg); } + } + `} + </style> + <circle opacity="0.2" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="2" /> + <path d="M12 2C6.47715 2 2 6.47715 2 12" stroke="currentColor" strokeWidth="2" strokeLinecap="round" /> + </svg> + </div> + <div style={{ + fontFamily: 'var(--font-family)', + fontSize: '14px', + }}>Chart loading...</div> + </div> + ) + } + + // Success state: show the chart + if (chartState === 'success' && finalChartOption) { + return ( + <div style={{ + minWidth: '300px', + minHeight: '350px', + width: '100%', + overflowX: 'auto', + borderBottomLeftRadius: '10px', + borderBottomRightRadius: '10px', + transition: 'background-color 0.3s ease', + }}> + <ErrorBoundary> + <ReactEcharts + ref={echartsRef} + option={finalChartOption} + style={echartsStyle} + theme={isDarkMode ? 'dark' : undefined} + opts={echartsOpts} + notMerge={true} + onEvents={echartsOnEvents} + /> + </ErrorBoundary> + </div> + ) + } + + // Error state: show error message + const errorOption = { + title: { + text: 'ECharts error - Wrong option.', + }, + } + + return ( + <div style={{ + minWidth: '300px', + minHeight: '350px', + width: '100%', + overflowX: 'auto', + borderBottomLeftRadius: '10px', + borderBottomRightRadius: '10px', + transition: 'background-color 0.3s ease', + }}> + <ErrorBoundary> + <ReactEcharts + ref={echartsRef} + option={errorOption} + style={echartsStyle} + theme={isDarkMode ? 'dark' : undefined} + opts={echartsOpts} + notMerge={true} + /> + </ErrorBoundary> + </div> + ) + } + case 'svg': + if (isSVG) { + return ( + <ErrorBoundary> + <SVGRenderer content={content} /> + </ErrorBoundary> + ) + } + break + case 'abc': + return ( + <ErrorBoundary> + <MarkdownMusic children={content} /> + </ErrorBoundary> + ) + default: + return ( + <SyntaxHighlighter + {...props} + style={theme === Theme.light ? atelierHeathLight : atelierHeathDark} + customStyle={{ + paddingLeft: 12, + borderBottomLeftRadius: '10px', + borderBottomRightRadius: '10px', + backgroundColor: 'var(--color-components-input-bg-normal)', + }} + language={match?.[1]} + showLineNumbers + PreTag="div" + > + {content} + </SyntaxHighlighter> + ) + } + }, [children, language, isSVG, finalChartOption, props, theme, match, chartState, isDarkMode, echartsStyle, echartsOpts, echartsOnEvents]) + + if (inline || !match) + return <code {...props} className={className}>{children}</code> + + return ( + <div className='relative'> + <div className='flex h-8 items-center justify-between rounded-t-[10px] border-b border-divider-subtle bg-components-input-bg-normal p-1 pl-3'> + <div className='system-xs-semibold-uppercase text-text-secondary'>{languageShowName}</div> + <div className='flex items-center gap-1'> + {(['mermaid', 'svg']).includes(language!) && <SVGBtn isSVG={isSVG} setIsSVG={setIsSVG} />} + <ActionButton> + <CopyIcon content={String(children).replace(/\n$/, '')} /> + </ActionButton> + </div> + </div> + {renderCodeContent} + </div> + ) +}) +CodeBlock.displayName = 'CodeBlock' + +export default CodeBlock diff --git a/web/app/components/base/markdown-blocks/img.tsx b/web/app/components/base/markdown-blocks/img.tsx new file mode 100644 index 0000000000..33fce13f0b --- /dev/null +++ b/web/app/components/base/markdown-blocks/img.tsx @@ -0,0 +1,13 @@ +/** + * @fileoverview Img component for rendering <img> tags in Markdown. + * Extracted from the main markdown renderer for modularity. + * Uses the ImageGallery component to display images. + */ +import React from 'react' +import ImageGallery from '@/app/components/base/image-gallery' + +const Img = ({ src }: any) => { + return <div className="markdown-img-wrapper"><ImageGallery srcs={[src]} /></div> +} + +export default Img diff --git a/web/app/components/base/markdown-blocks/index.ts b/web/app/components/base/markdown-blocks/index.ts new file mode 100644 index 0000000000..ba68b4e8b1 --- /dev/null +++ b/web/app/components/base/markdown-blocks/index.ts @@ -0,0 +1,18 @@ +/** + * @fileoverview Barrel file for all markdown block components. + * This allows for cleaner imports in other parts of the application. + */ + +export { default as AudioBlock } from './audio-block' +export { default as CodeBlock } from './code-block' +export { default as Img } from './img' +export { default as Link } from './link' +export { default as Paragraph } from './paragraph' +export { default as PreCode } from './pre-code' +export { default as ScriptBlock } from './script-block' +export { default as VideoBlock } from './video-block' + +// Assuming these are also standalone components in this directory intended for Markdown rendering +export { default as MarkdownButton } from './button' +export { default as MarkdownForm } from './form' +export { default as ThinkBlock } from './think-block' diff --git a/web/app/components/base/markdown-blocks/link.tsx b/web/app/components/base/markdown-blocks/link.tsx new file mode 100644 index 0000000000..c465b3e4f8 --- /dev/null +++ b/web/app/components/base/markdown-blocks/link.tsx @@ -0,0 +1,26 @@ +/** + * @fileoverview Link component for rendering <a> tags in Markdown. + * Extracted from the main markdown renderer for modularity. + * Handles special rendering for "abbr:" type links for interactive chat actions. + */ +import React from 'react' +import { useChatContext } from '@/app/components/base/chat/chat/context' +import { isValidUrl } from './utils' + +const Link = ({ node, children, ...props }: any) => { + const { onSend } = useChatContext() + if (node.properties?.href && node.properties.href?.toString().startsWith('abbr')) { + const hidden_text = decodeURIComponent(node.properties.href.toString().split('abbr:')[1]) + + return <abbr className="cursor-pointer underline !decoration-primary-700 decoration-dashed" onClick={() => onSend?.(hidden_text)} title={node.children[0]?.value || ''}>{node.children[0]?.value || ''}</abbr> + } + else { + const href = props.href || node.properties?.href + if(!isValidUrl(href)) + return <span>{children}</span> + + return <a href={href} target="_blank" className="cursor-pointer underline !decoration-primary-700 decoration-dashed">{children || 'Download'}</a> + } +} + +export default Link diff --git a/web/app/components/base/markdown-blocks/paragraph.tsx b/web/app/components/base/markdown-blocks/paragraph.tsx new file mode 100644 index 0000000000..fb1612477a --- /dev/null +++ b/web/app/components/base/markdown-blocks/paragraph.tsx @@ -0,0 +1,27 @@ +/** + * @fileoverview Paragraph component for rendering <p> tags in Markdown. + * Extracted from the main markdown renderer for modularity. + * Handles special rendering for paragraphs that directly contain an image. + */ +import React from 'react' +import ImageGallery from '@/app/components/base/image-gallery' + +const Paragraph = (paragraph: any) => { + const { node }: any = paragraph + const children_node = node.children + if (children_node && children_node[0] && 'tagName' in children_node[0] && children_node[0].tagName === 'img') { + return ( + <div className="markdown-img-wrapper"> + <ImageGallery srcs={[children_node[0].properties.src]} /> + { + Array.isArray(paragraph.children) && paragraph.children.length > 1 && ( + <div className="mt-2">{paragraph.children.slice(1)}</div> + ) + } + </div> + ) + } + return <p>{paragraph.children}</p> +} + +export default Paragraph diff --git a/web/app/components/base/markdown-blocks/pre-code.tsx b/web/app/components/base/markdown-blocks/pre-code.tsx new file mode 100644 index 0000000000..a9d0cfb9aa --- /dev/null +++ b/web/app/components/base/markdown-blocks/pre-code.tsx @@ -0,0 +1,21 @@ +/** + * @fileoverview PreCode component for rendering <pre> tags in Markdown. + * Extracted from the main markdown renderer for modularity. + * This is a simple wrapper around the HTML <pre> element. + */ +import React, { useRef } from 'react' + +function PreCode(props: { children: any }) { + const ref = useRef<HTMLPreElement>(null) + + return ( + <pre ref={ref}> + <span + className="copy-code-button" + ></span> + {props.children} + </pre> + ) +} + +export default PreCode diff --git a/web/app/components/base/markdown-blocks/script-block.tsx b/web/app/components/base/markdown-blocks/script-block.tsx new file mode 100644 index 0000000000..921e2bf049 --- /dev/null +++ b/web/app/components/base/markdown-blocks/script-block.tsx @@ -0,0 +1,15 @@ +/** + * @fileoverview ScriptBlock component for handling <script> tags in Markdown. + * Extracted from the main markdown renderer for modularity. + * Note: Current implementation returns the script tag as a string, which might not execute as expected in React. + * This behavior is preserved from the original implementation and may need review for security and functionality. + */ +import { memo } from 'react' + +const ScriptBlock = memo(({ node }: any) => { + const scriptContent = node.children[0]?.value || '' + return `<script>${scriptContent}</script>` +}) +ScriptBlock.displayName = 'ScriptBlock' + +export default ScriptBlock diff --git a/web/app/components/base/markdown-blocks/think-block.tsx b/web/app/components/base/markdown-blocks/think-block.tsx index 282282db2b..46f992d758 100644 --- a/web/app/components/base/markdown-blocks/think-block.tsx +++ b/web/app/components/base/markdown-blocks/think-block.tsx @@ -41,9 +41,10 @@ const useThinkTimer = (children: any) => { const timerRef = useRef<NodeJS.Timeout>() useEffect(() => { + if (isComplete) return + timerRef.current = setInterval(() => { - if (!isComplete) - setElapsedTime(Math.floor((Date.now() - startTime) / 100) / 10) + setElapsedTime(Math.floor((Date.now() - startTime) / 100) / 10) }, 100) return () => { @@ -53,11 +54,8 @@ const useThinkTimer = (children: any) => { }, [startTime, isComplete]) useEffect(() => { - if (hasEndThink(children)) { + if (hasEndThink(children)) setIsComplete(true) - if (timerRef.current) - clearInterval(timerRef.current) - } }, [children]) return { elapsedTime, isComplete } @@ -73,7 +71,7 @@ export const ThinkBlock = ({ children, ...props }: any) => { return ( <details {...(!isComplete && { open: true })} className="group"> - <summary className="flex cursor-pointer select-none list-none items-center whitespace-nowrap pl-2 font-bold text-gray-500"> + <summary className="flex cursor-pointer select-none list-none items-center whitespace-nowrap pl-2 font-bold text-text-secondary"> <div className="flex shrink-0 items-center"> <svg className="mr-2 h-3 w-3 transition-transform duration-500 group-open:rotate-90" @@ -91,7 +89,7 @@ export const ThinkBlock = ({ children, ...props }: any) => { {isComplete ? `${t('common.chat.thought')}(${elapsedTime.toFixed(1)}s)` : `${t('common.chat.thinking')}(${elapsedTime.toFixed(1)}s)`} </div> </summary> - <div className="ml-2 border-l border-gray-300 bg-gray-50 p-3 text-gray-500"> + <div className="ml-2 border-l border-components-panel-border bg-components-panel-bg-alt p-3 text-text-secondary"> {displayContent} </div> </details> diff --git a/web/app/components/base/markdown-blocks/utils.ts b/web/app/components/base/markdown-blocks/utils.ts new file mode 100644 index 0000000000..4e9e98dbed --- /dev/null +++ b/web/app/components/base/markdown-blocks/utils.ts @@ -0,0 +1,3 @@ +export const isValidUrl = (url: string): boolean => { + return ['http:', 'https:', '//', 'mailto:'].some(prefix => url.startsWith(prefix)) +} diff --git a/web/app/components/base/markdown-blocks/video-block.tsx b/web/app/components/base/markdown-blocks/video-block.tsx new file mode 100644 index 0000000000..9f1a36f678 --- /dev/null +++ b/web/app/components/base/markdown-blocks/video-block.tsx @@ -0,0 +1,21 @@ +/** + * @fileoverview VideoBlock component for rendering video elements in Markdown. + * Extracted from the main markdown renderer for modularity. + * Uses the VideoGallery component to display videos. + */ +import React, { memo } from 'react' +import VideoGallery from '@/app/components/base/video-gallery' + +const VideoBlock: any = memo(({ node }: any) => { + const srcs = node.children.filter((child: any) => 'properties' in child).map((child: any) => (child as any).properties.src) + if (srcs.length === 0) { + const src = node.properties?.src + if (src) + return <VideoGallery key={src} srcs={[src]} /> + return null + } + return <VideoGallery key={srcs.join()} srcs={srcs} /> +}) +VideoBlock.displayName = 'VideoBlock' + +export default VideoBlock diff --git a/web/app/components/base/markdown.tsx b/web/app/components/base/markdown.tsx deleted file mode 100644 index 6ea84a2842..0000000000 --- a/web/app/components/base/markdown.tsx +++ /dev/null @@ -1,349 +0,0 @@ -import ReactMarkdown from 'react-markdown' -import ReactEcharts from 'echarts-for-react' -import 'katex/dist/katex.min.css' -import RemarkMath from 'remark-math' -import RemarkBreaks from 'remark-breaks' -import RehypeKatex from 'rehype-katex' -import RemarkGfm from 'remark-gfm' -import RehypeRaw from 'rehype-raw' -import SyntaxHighlighter from 'react-syntax-highlighter' -import { - atelierHeathDark, - atelierHeathLight, -} from 'react-syntax-highlighter/dist/esm/styles/hljs' -import { Component, memo, useMemo, useRef, useState } from 'react' -import { flow } from 'lodash-es' -import ActionButton from '@/app/components/base/action-button' -import CopyIcon from '@/app/components/base/copy-icon' -import SVGBtn from '@/app/components/base/svg' -import Flowchart from '@/app/components/base/mermaid' -import ImageGallery from '@/app/components/base/image-gallery' -import { useChatContext } from '@/app/components/base/chat/chat/context' -import VideoGallery from '@/app/components/base/video-gallery' -import AudioGallery from '@/app/components/base/audio-gallery' -import MarkdownButton from '@/app/components/base/markdown-blocks/button' -import MarkdownForm from '@/app/components/base/markdown-blocks/form' -import MarkdownMusic from '@/app/components/base/markdown-blocks/music' -import ThinkBlock from '@/app/components/base/markdown-blocks/think-block' -import { Theme } from '@/types/app' -import useTheme from '@/hooks/use-theme' -import cn from '@/utils/classnames' -import SVGRenderer from './svg-gallery' - -// Available language https://github.com/react-syntax-highlighter/react-syntax-highlighter/blob/master/AVAILABLE_LANGUAGES_HLJS.MD -const capitalizationLanguageNameMap: Record<string, string> = { - sql: 'SQL', - javascript: 'JavaScript', - java: 'Java', - typescript: 'TypeScript', - vbscript: 'VBScript', - css: 'CSS', - html: 'HTML', - xml: 'XML', - php: 'PHP', - python: 'Python', - yaml: 'Yaml', - mermaid: 'Mermaid', - markdown: 'MarkDown', - makefile: 'MakeFile', - echarts: 'ECharts', - shell: 'Shell', - powershell: 'PowerShell', - json: 'JSON', - latex: 'Latex', - svg: 'SVG', - abc: 'ABC', -} -const getCorrectCapitalizationLanguageName = (language: string) => { - if (!language) - return 'Plain' - - if (language in capitalizationLanguageNameMap) - return capitalizationLanguageNameMap[language] - - return language.charAt(0).toUpperCase() + language.substring(1) -} - -const preprocessLaTeX = (content: string) => { - if (typeof content !== 'string') - return content - - const codeBlockRegex = /```[\s\S]*?```/g - const codeBlocks = content.match(codeBlockRegex) || [] - let processedContent = content.replace(codeBlockRegex, 'CODE_BLOCK_PLACEHOLDER') - - processedContent = flow([ - (str: string) => str.replace(/\\\[(.*?)\\\]/g, (_, equation) => `$$${equation}$$`), - (str: string) => str.replace(/\\\[(.*?)\\\]/gs, (_, equation) => `$$${equation}$$`), - (str: string) => str.replace(/\\\((.*?)\\\)/g, (_, equation) => `$$${equation}$$`), - (str: string) => str.replace(/(^|[^\\])\$(.+?)\$/g, (_, prefix, equation) => `${prefix}$${equation}$`), - ])(processedContent) - - codeBlocks.forEach((block) => { - processedContent = processedContent.replace('CODE_BLOCK_PLACEHOLDER', block) - }) - - return processedContent -} - -const preprocessThinkTag = (content: string) => { - const thinkOpenTagRegex = /<think>\n/g - const thinkCloseTagRegex = /\n<\/think>/g - return flow([ - (str: string) => str.replace(thinkOpenTagRegex, '<details data-think=true>\n'), - (str: string) => str.replace(thinkCloseTagRegex, '\n[ENDTHINKFLAG]</details>'), - ])(content) -} - -export function PreCode(props: { children: any }) { - const ref = useRef<HTMLPreElement>(null) - - return ( - <pre ref={ref}> - <span - className="copy-code-button" - ></span> - {props.children} - </pre> - ) -} - -// **Add code block -// Avoid error #185 (Maximum update depth exceeded. -// This can happen when a component repeatedly calls setState inside componentWillUpdate or componentDidUpdate. -// React limits the number of nested updates to prevent infinite loops.) -// Reference A: https://reactjs.org/docs/error-decoder.html?invariant=185 -// Reference B1: https://react.dev/reference/react/memo -// Reference B2: https://react.dev/reference/react/useMemo -// **** -// The original error that occurred in the streaming response during the conversation: -// Error: Minified React error 185; -// visit https://reactjs.org/docs/error-decoder.html?invariant=185 for the full message -// or use the non-minified dev environment for full errors and additional helpful warnings. - -const CodeBlock: any = memo(({ inline, className, children, ...props }: any) => { - const { theme } = useTheme() - const [isSVG, setIsSVG] = useState(true) - const match = /language-(\w+)/.exec(className || '') - const language = match?.[1] - const languageShowName = getCorrectCapitalizationLanguageName(language || '') - const chartData = useMemo(() => { - if (language === 'echarts') { - try { - return JSON.parse(String(children).replace(/\n$/, '')) - } - catch { } - } - return JSON.parse('{"title":{"text":"ECharts error - Wrong JSON format."}}') - }, [language, children]) - - const renderCodeContent = useMemo(() => { - const content = String(children).replace(/\n$/, '') - switch (language) { - case 'mermaid': - if (isSVG) - return <Flowchart PrimitiveCode={content} /> - break - case 'echarts': - return ( - <div style={{ minHeight: '350px', minWidth: '100%', overflowX: 'scroll' }}> - <ErrorBoundary> - <ReactEcharts option={chartData} style={{ minWidth: '700px' }} /> - </ErrorBoundary> - </div> - ) - case 'svg': - if (isSVG) { - return ( - <ErrorBoundary> - <SVGRenderer content={content} /> - </ErrorBoundary> - ) - } - break - case 'abc': - return ( - <ErrorBoundary> - <MarkdownMusic children={content} /> - </ErrorBoundary> - ) - default: - return ( - <SyntaxHighlighter - {...props} - style={theme === Theme.light ? atelierHeathLight : atelierHeathDark} - customStyle={{ - paddingLeft: 12, - borderBottomLeftRadius: '10px', - borderBottomRightRadius: '10px', - backgroundColor: 'var(--color-components-input-bg-normal)', - }} - language={match?.[1]} - showLineNumbers - PreTag="div" - > - {content} - </SyntaxHighlighter> - ) - } - }, [children, language, isSVG, chartData, props, theme, match]) - - if (inline || !match) - return <code {...props} className={className}>{children}</code> - - return ( - <div className='relative'> - <div className='flex h-8 items-center justify-between rounded-t-[10px] border-b border-divider-subtle bg-components-input-bg-normal p-1 pl-3'> - <div className='system-xs-semibold-uppercase text-text-secondary'>{languageShowName}</div> - <div className='flex items-center gap-1'> - {(['mermaid', 'svg']).includes(language!) && <SVGBtn isSVG={isSVG} setIsSVG={setIsSVG} />} - <ActionButton> - <CopyIcon content={String(children).replace(/\n$/, '')} /> - </ActionButton> - </div> - </div> - {renderCodeContent} - </div> - ) -}) -CodeBlock.displayName = 'CodeBlock' - -const VideoBlock: any = memo(({ node }: any) => { - const srcs = node.children.filter((child: any) => 'properties' in child).map((child: any) => (child as any).properties.src) - if (srcs.length === 0) - return null - return <VideoGallery key={srcs.join()} srcs={srcs} /> -}) -VideoBlock.displayName = 'VideoBlock' - -const AudioBlock: any = memo(({ node }: any) => { - const srcs = node.children.filter((child: any) => 'properties' in child).map((child: any) => (child as any).properties.src) - if (srcs.length === 0) - return null - return <AudioGallery key={srcs.join()} srcs={srcs} /> -}) -AudioBlock.displayName = 'AudioBlock' - -const ScriptBlock = memo(({ node }: any) => { - const scriptContent = node.children[0]?.value || '' - return `<script>${scriptContent}</script>` -}) -ScriptBlock.displayName = 'ScriptBlock' - -const Paragraph = (paragraph: any) => { - const { node }: any = paragraph - const children_node = node.children - if (children_node && children_node[0] && 'tagName' in children_node[0] && children_node[0].tagName === 'img') { - return ( - <div className="markdown-img-wrapper"> - <ImageGallery srcs={[children_node[0].properties.src]} /> - { - Array.isArray(paragraph.children) && paragraph.children.length > 1 && ( - <div className="mt-2">{paragraph.children.slice(1)}</div> - ) - } - </div> - ) - } - return <p>{paragraph.children}</p> -} - -const Img = ({ src }: any) => { - return <div className="markdown-img-wrapper"><ImageGallery srcs={[src]} /></div> -} - -const Link = ({ node, children, ...props }: any) => { - if (node.properties?.href && node.properties.href?.toString().startsWith('abbr')) { - // eslint-disable-next-line react-hooks/rules-of-hooks - const { onSend } = useChatContext() - const hidden_text = decodeURIComponent(node.properties.href.toString().split('abbr:')[1]) - - return <abbr className="cursor-pointer underline !decoration-primary-700 decoration-dashed" onClick={() => onSend?.(hidden_text)} title={node.children[0]?.value}>{node.children[0]?.value}</abbr> - } - else { - return <a {...props} target="_blank" className="cursor-pointer underline !decoration-primary-700 decoration-dashed">{children || 'Download'}</a> - } -} - -export function Markdown(props: { content: string; className?: string; customDisallowedElements?: string[] }) { - const latexContent = flow([ - preprocessThinkTag, - preprocessLaTeX, - ])(props.content) - - return ( - <div className={cn('markdown-body', '!text-text-primary', props.className)}> - <ReactMarkdown - remarkPlugins={[ - RemarkGfm, - [RemarkMath, { singleDollarTextMath: false }], - RemarkBreaks, - ]} - rehypePlugins={[ - RehypeKatex, - RehypeRaw as any, - // The Rehype plug-in is used to remove the ref attribute of an element - () => { - return (tree) => { - const iterate = (node: any) => { - if (node.type === 'element' && node.properties?.ref) - delete node.properties.ref - - if (node.type === 'element' && !/^[a-z][a-z0-9]*$/i.test(node.tagName)) { - node.type = 'text' - node.value = `<${node.tagName}` - } - - if (node.children) - node.children.forEach(iterate) - } - tree.children.forEach(iterate) - } - }, - ]} - disallowedElements={['iframe', 'head', 'html', 'meta', 'link', 'style', 'body', ...(props.customDisallowedElements || [])]} - components={{ - code: CodeBlock, - img: Img, - video: VideoBlock, - audio: AudioBlock, - a: Link, - p: Paragraph, - button: MarkdownButton, - form: MarkdownForm, - script: ScriptBlock as any, - details: ThinkBlock, - }} - > - {/* Markdown detect has problem. */} - {latexContent} - </ReactMarkdown> - </div> - ) -} - -// **Add an ECharts runtime error handler -// Avoid error #7832 (Crash when ECharts accesses undefined objects) -// This can happen when a component attempts to access an undefined object that references an unregistered map, causing the program to crash. - -export default class ErrorBoundary extends Component { - constructor(props: any) { - super(props) - this.state = { hasError: false } - } - - componentDidCatch(error: any, errorInfo: any) { - this.setState({ hasError: true }) - console.error(error, errorInfo) - } - - render() { - // eslint-disable-next-line ts/ban-ts-comment - // @ts-expect-error - if (this.state.hasError) - return <div>Oops! An error occurred. This could be due to an ECharts runtime error or invalid SVG content. <br />(see the browser console for more information)</div> - // eslint-disable-next-line ts/ban-ts-comment - // @ts-expect-error - return this.props.children - } -} diff --git a/web/app/components/base/markdown/error-boundary.tsx b/web/app/components/base/markdown/error-boundary.tsx new file mode 100644 index 0000000000..0e6876191a --- /dev/null +++ b/web/app/components/base/markdown/error-boundary.tsx @@ -0,0 +1,33 @@ +/** + * @fileoverview ErrorBoundary component for React. + * This component was extracted from the main markdown renderer. + * It catches JavaScript errors anywhere in its child component tree, + * logs those errors, and displays a fallback UI instead of the crashed component tree. + * Primarily used around complex rendering logic like ECharts or SVG within Markdown. + */ +import React, { Component } from 'react' +// **Add an ECharts runtime error handler +// Avoid error #7832 (Crash when ECharts accesses undefined objects) +// This can happen when a component attempts to access an undefined object that references an unregistered map, causing the program to crash. + +export default class ErrorBoundary extends Component { + constructor(props: any) { + super(props) + this.state = { hasError: false } + } + + componentDidCatch(error: any, errorInfo: any) { + this.setState({ hasError: true }) + console.error(error, errorInfo) + } + + render() { + // eslint-disable-next-line ts/ban-ts-comment + // @ts-expect-error + if (this.state.hasError) + return <div>Oops! An error occurred. This could be due to an ECharts runtime error or invalid SVG content. <br />(see the browser console for more information)</div> + // eslint-disable-next-line ts/ban-ts-comment + // @ts-expect-error + return this.props.children + } +} diff --git a/web/app/components/base/markdown/index.tsx b/web/app/components/base/markdown/index.tsx new file mode 100644 index 0000000000..1e50e6745b --- /dev/null +++ b/web/app/components/base/markdown/index.tsx @@ -0,0 +1,88 @@ +import ReactMarkdown from 'react-markdown' +import 'katex/dist/katex.min.css' +import RemarkMath from 'remark-math' +import RemarkBreaks from 'remark-breaks' +import RehypeKatex from 'rehype-katex' +import RemarkGfm from 'remark-gfm' +import RehypeRaw from 'rehype-raw' +import { flow } from 'lodash-es' +import cn from '@/utils/classnames' +import { customUrlTransform, preprocessLaTeX, preprocessThinkTag } from './markdown-utils' +import { + AudioBlock, + CodeBlock, + Img, + Link, + MarkdownButton, + MarkdownForm, + Paragraph, + ScriptBlock, + ThinkBlock, + VideoBlock, +} from '@/app/components/base/markdown-blocks' + +/** + * @fileoverview Main Markdown rendering component. + * This file was refactored to extract individual block renderers and utility functions + * into separate modules for better organization and maintainability as of [Date of refactor]. + * Further refactoring candidates (custom block components not fitting general categories) + * are noted in their respective files if applicable. + */ + +export function Markdown(props: { content: string; className?: string; customDisallowedElements?: string[] }) { + const latexContent = flow([ + preprocessThinkTag, + preprocessLaTeX, + ])(props.content) + + return ( + <div className={cn('markdown-body', '!text-text-primary', props.className)}> + <ReactMarkdown + remarkPlugins={[ + RemarkGfm, + [RemarkMath, { singleDollarTextMath: false }], + RemarkBreaks, + ]} + rehypePlugins={[ + RehypeKatex, + RehypeRaw as any, + // The Rehype plug-in is used to remove the ref attribute of an element + () => { + return (tree: any) => { + const iterate = (node: any) => { + if (node.type === 'element' && node.properties?.ref) + delete node.properties.ref + + if (node.type === 'element' && !/^[a-z][a-z0-9]*$/i.test(node.tagName)) { + node.type = 'text' + node.value = `<${node.tagName}` + } + + if (node.children) + node.children.forEach(iterate) + } + tree.children.forEach(iterate) + } + }, + ]} + urlTransform={customUrlTransform} + disallowedElements={['iframe', 'head', 'html', 'meta', 'link', 'style', 'body', ...(props.customDisallowedElements || [])]} + components={{ + code: CodeBlock, + img: Img, + video: VideoBlock, + audio: AudioBlock, + a: Link, + p: Paragraph, + button: MarkdownButton, + form: MarkdownForm, + script: ScriptBlock as any, + details: ThinkBlock, + }} + > + {/* Markdown detect has problem. */} + {latexContent} + </ReactMarkdown> + </div> + ) +} diff --git a/web/app/components/base/markdown/markdown-utils.ts b/web/app/components/base/markdown/markdown-utils.ts new file mode 100644 index 0000000000..0aa385a1d1 --- /dev/null +++ b/web/app/components/base/markdown/markdown-utils.ts @@ -0,0 +1,87 @@ +/** + * @fileoverview Utility functions for preprocessing Markdown content. + * These functions were extracted from the main markdown renderer for better separation of concerns. + * Includes preprocessing for LaTeX and custom "think" tags. + */ +import { flow } from 'lodash-es' + +export const preprocessLaTeX = (content: string) => { + if (typeof content !== 'string') + return content + + const codeBlockRegex = /```[\s\S]*?```/g + const codeBlocks = content.match(codeBlockRegex) || [] + let processedContent = content.replace(codeBlockRegex, 'CODE_BLOCK_PLACEHOLDER') + + processedContent = flow([ + (str: string) => str.replace(/\\\[(.*?)\\\]/g, (_, equation) => `$$${equation}$$`), + (str: string) => str.replace(/\\\[([\s\S]*?)\\\]/g, (_, equation) => `$$${equation}$$`), + (str: string) => str.replace(/\\\((.*?)\\\)/g, (_, equation) => `$$${equation}$$`), + (str: string) => str.replace(/(^|[^\\])\$(.+?)\$/g, (_, prefix, equation) => `${prefix}$${equation}$`), + ])(processedContent) + + codeBlocks.forEach((block) => { + processedContent = processedContent.replace('CODE_BLOCK_PLACEHOLDER', block) + }) + + return processedContent +} + +export const preprocessThinkTag = (content: string) => { + const thinkOpenTagRegex = /<think>\n/g + const thinkCloseTagRegex = /\n<\/think>/g + return flow([ + (str: string) => str.replace(thinkOpenTagRegex, '<details data-think=true>\n'), + (str: string) => str.replace(thinkCloseTagRegex, '\n[ENDTHINKFLAG]</details>'), + (str: string) => str.replace(/(<\/details>)(?![^\S\r\n]*[\r\n])(?![^\S\r\n]*$)/g, '$1\n'), + ])(content) +} + +/** + * Transforms a URI for use in react-markdown, ensuring security and compatibility. + * This function is designed to work with react-markdown v9+ which has stricter + * default URL handling. + * + * Behavior: + * 1. Always allows the custom 'abbr:' protocol. + * 2. Always allows page-local fragments (e.g., "#some-id"). + * 3. Always allows protocol-relative URLs (e.g., "//example.com/path"). + * 4. Always allows purely relative paths (e.g., "path/to/file", "/abs/path"). + * 5. Allows absolute URLs if their scheme is in a permitted list (case-insensitive): + * 'http:', 'https:', 'mailto:', 'xmpp:', 'irc:', 'ircs:'. + * 6. Intelligently distinguishes colons used for schemes from colons within + * paths, query parameters, or fragments of relative-like URLs. + * 7. Returns the original URI if allowed, otherwise returns `undefined` to + * signal that the URI should be removed/disallowed by react-markdown. + */ +export const customUrlTransform = (uri: string): string | undefined => { + const PERMITTED_SCHEME_REGEX = /^(https?|ircs?|mailto|xmpp|abbr):$/i + + if (uri.startsWith('#')) + return uri + + if (uri.startsWith('//')) + return uri + + const colonIndex = uri.indexOf(':') + + if (colonIndex === -1) + return uri + + const slashIndex = uri.indexOf('/') + const questionMarkIndex = uri.indexOf('?') + const hashIndex = uri.indexOf('#') + + if ( + (slashIndex !== -1 && colonIndex > slashIndex) + || (questionMarkIndex !== -1 && colonIndex > questionMarkIndex) + || (hashIndex !== -1 && colonIndex > hashIndex) + ) + return uri + + const scheme = uri.substring(0, colonIndex + 1).toLowerCase() + if (PERMITTED_SCHEME_REGEX.test(scheme)) + return uri + + return undefined +} diff --git a/web/app/components/base/mermaid/index.tsx b/web/app/components/base/mermaid/index.tsx index 8fd8ae8b59..31eaffb813 100644 --- a/web/app/components/base/mermaid/index.tsx +++ b/web/app/components/base/mermaid/index.tsx @@ -91,6 +91,11 @@ const initMermaid = () => { numberSectionStyles: 4, axisFormat: '%Y-%m-%d', }, + mindmap: { + useMaxWidth: true, + padding: 10, + diagramPadding: 20, + }, maxTextSize: 50000, }) isMermaidInitialized = true @@ -289,11 +294,12 @@ const Flowchart = React.forwardRef((props: { try { let finalCode: string - // Check if it's a gantt chart + // Check if it's a gantt chart or mindmap const isGanttChart = primitiveCode.trim().startsWith('gantt') + const isMindMap = primitiveCode.trim().startsWith('mindmap') - if (isGanttChart) { - // For gantt charts, ensure each task is on its own line + if (isGanttChart || isMindMap) { + // For gantt charts and mindmaps, ensure each task is on its own line // and preserve exact whitespace/format finalCode = primitiveCode.trim() } @@ -352,6 +358,11 @@ const Flowchart = React.forwardRef((props: { numberSectionStyles: 4, axisFormat: '%Y-%m-%d', }, + mindmap: { + useMaxWidth: true, + padding: 10, + diagramPadding: 20, + }, } if (look === 'classic') { @@ -476,15 +487,15 @@ const Flowchart = React.forwardRef((props: { 'bg-white': currentTheme === Theme.light, 'bg-slate-900': currentTheme === Theme.dark, }), - mermaidDiv: cn('mermaid cursor-pointer h-auto w-full relative', { + mermaidDiv: cn('mermaid relative h-auto w-full cursor-pointer', { 'bg-white': currentTheme === Theme.light, 'bg-slate-900': currentTheme === Theme.dark, }), - errorMessage: cn('py-4 px-[26px]', { + errorMessage: cn('px-[26px] py-4', { 'text-red-500': currentTheme === Theme.light, 'text-red-400': currentTheme === Theme.dark, }), - errorIcon: cn('w-6 h-6', { + errorIcon: cn('h-6 w-6', { 'text-red-500': currentTheme === Theme.light, 'text-red-400': currentTheme === Theme.dark, }), @@ -492,7 +503,7 @@ const Flowchart = React.forwardRef((props: { 'text-gray-700': currentTheme === Theme.light, 'text-gray-300': currentTheme === Theme.dark, }), - themeToggle: cn('flex items-center justify-center w-10 h-10 rounded-full transition-all duration-300 shadow-md backdrop-blur-sm', { + themeToggle: cn('flex h-10 w-10 items-center justify-center rounded-full shadow-md backdrop-blur-sm transition-all duration-300', { 'bg-white/80 hover:bg-white hover:shadow-lg text-gray-700 border border-gray-200': currentTheme === Theme.light, 'bg-slate-800/80 hover:bg-slate-700 hover:shadow-lg text-yellow-300 border border-slate-600': currentTheme === Theme.dark, }), @@ -501,7 +512,7 @@ const Flowchart = React.forwardRef((props: { // Style classes for look options const getLookButtonClass = (lookType: 'classic' | 'handDrawn') => { return cn( - 'flex items-center justify-center mb-4 w-[calc((100%-8px)/2)] h-8 rounded-lg border border-components-option-card-option-border bg-components-option-card-option-bg cursor-pointer system-sm-medium text-text-secondary', + 'system-sm-medium mb-4 flex h-8 w-[calc((100%-8px)/2)] cursor-pointer items-center justify-center rounded-lg border border-components-option-card-option-border bg-components-option-card-option-bg text-text-secondary', look === lookType && 'border-[1.5px] border-components-option-card-option-selected-border bg-components-option-card-option-selected-bg text-text-primary', currentTheme === Theme.dark && 'border-slate-600 bg-slate-800 text-slate-300', look === lookType && currentTheme === Theme.dark && 'border-blue-500 bg-slate-700 text-white', @@ -512,7 +523,7 @@ const Flowchart = React.forwardRef((props: { <div ref={ref as React.RefObject<HTMLDivElement>} className={themeClasses.container}> <div className={themeClasses.segmented}> <div className="msh-segmented-group"> - <label className="msh-segmented-item flex items-center space-x-1 m-2 w-[200px]"> + <label className="msh-segmented-item m-2 flex w-[200px] items-center space-x-1"> <div key='classic' className={getLookButtonClass('classic')} @@ -534,7 +545,7 @@ const Flowchart = React.forwardRef((props: { <div ref={containerRef} style={{ position: 'absolute', visibility: 'hidden', height: 0, overflow: 'hidden' }} /> {isLoading && !svgCode && ( - <div className='py-4 px-[26px]'> + <div className='px-[26px] py-4'> <LoadingAnim type='text'/> {!isCodeComplete && ( <div className="mt-2 text-sm text-gray-500"> @@ -546,7 +557,7 @@ const Flowchart = React.forwardRef((props: { {svgCode && ( <div className={themeClasses.mermaidDiv} style={{ objectFit: 'cover' }} onClick={() => setImagePreviewUrl(svgCode)}> - <div className="absolute left-2 bottom-2 z-[100]"> + <div className="absolute bottom-2 left-2 z-[100]"> <button onClick={(e) => { e.stopPropagation() diff --git a/web/app/components/base/mermaid/utils.ts b/web/app/components/base/mermaid/utils.ts index 9c7ab4a7b5..9936a9fc59 100644 --- a/web/app/components/base/mermaid/utils.ts +++ b/web/app/components/base/mermaid/utils.ts @@ -36,9 +36,9 @@ export function preprocessMermaidCode(code: string): string { export function prepareMermaidCode(code: string, style: 'classic' | 'handDrawn'): string { let finalCode = preprocessMermaidCode(code) - // Special handling for gantt charts - if (finalCode.trim().startsWith('gantt')) { - // For gantt charts, preserve the structure exactly as is + // Special handling for gantt charts and mindmaps + if (finalCode.trim().startsWith('gantt') || finalCode.trim().startsWith('mindmap')) { + // For gantt charts and mindmaps, preserve the structure exactly as is return finalCode } @@ -177,8 +177,15 @@ export function isMermaidCodeComplete(code: string): boolean { return lines.length >= 3 } + // Special handling for mindmaps + if (trimmedCode.startsWith('mindmap')) { + // For mindmaps, check if it has at least a root node + const lines = trimmedCode.split('\n').filter(line => line.trim().length > 0) + return lines.length >= 2 + } + // Check for basic syntax structure - const hasValidStart = /^(graph|flowchart|sequenceDiagram|classDiagram|classDef|class|stateDiagram|gantt|pie|er|journey|requirementDiagram)/.test(trimmedCode) + const hasValidStart = /^(graph|flowchart|sequenceDiagram|classDiagram|classDef|class|stateDiagram|gantt|pie|er|journey|requirementDiagram|mindmap)/.test(trimmedCode) // Check for balanced brackets and parentheses const isBalanced = (() => { diff --git a/web/app/components/base/notion-page-selector/notion-page-selector-modal/index.module.css b/web/app/components/base/notion-page-selector/notion-page-selector-modal/index.module.css index ed9091601f..cd1f9c76ab 100644 --- a/web/app/components/base/notion-page-selector/notion-page-selector-modal/index.module.css +++ b/web/app/components/base/notion-page-selector/notion-page-selector-modal/index.module.css @@ -25,4 +25,4 @@ border-color: #155eef; background-color: #155eef; color: #ffffff; -} \ No newline at end of file +} diff --git a/web/app/components/base/premium-badge/index.css b/web/app/components/base/premium-badge/index.css index 8420415e62..61031cd4d2 100644 --- a/web/app/components/base/premium-badge/index.css +++ b/web/app/components/base/premium-badge/index.css @@ -2,47 +2,55 @@ @layer components { .premium-badge { - @apply inline-flex justify-center items-center rounded-md border box-border border-white/95 text-white + @apply shrink-0 relative inline-flex justify-center items-center rounded-md box-border border border-transparent text-white shadow-xs hover:shadow-lg bg-origin-border overflow-hidden transition-all duration-100 ease-out; + background-clip: padding-box, border-box; + } + .allowHover { + @apply cursor-pointer; } /* m is for the regular button */ .premium-badge-m { - @apply border shadow-lg !p-1 h-6 w-auto + @apply !p-1 h-6 w-auto } .premium-badge-s { - @apply border-[0.5px] shadow-xs !px-1 !py-[3px] h-[18px] w-auto + @apply border-[0.5px] !px-1 !py-[3px] h-[18px] w-auto } .premium-badge-blue { - @apply bg-gradient-to-r from-[#5289ffe6] to-[#155aefe6] bg-util-colors-blue-blue-200 + @apply bg-util-colors-blue-blue-200; + background-image: linear-gradient(90deg, #5289ffe6 0%, #155aefe6 100%), linear-gradient(135deg, var(--color-premium-badge-border-highlight-color) 0%, #155aef 100%); } - - .premium-badge-indigo { - @apply bg-gradient-to-r from-[#8098f9e6] to-[#444ce7e6] bg-util-colors-indigo-indigo-200 - } - - .premium-badge-gray { - @apply bg-gradient-to-r from-[#98a2b2e6] to-[#676f83e6] bg-util-colors-gray-gray-200 - } - - .premium-badge-orange { - @apply bg-gradient-to-r from-[#ff692ee6] to-[#e04f16e6] bg-util-colors-orange-orange-200 - } - .premium-badge-blue.allowHover:hover { - @apply bg-gradient-to-r from-[#296dffe6] to-[#004aebe6] bg-util-colors-blue-blue-300 cursor-pointer + @apply bg-util-colors-blue-blue-300; + background-image: linear-gradient(90deg, #296dffe6 0%, #004aebe6 100%), linear-gradient(135deg, var(--color-premium-badge-border-highlight-color) 0%, #00329e 100%); } + .premium-badge-indigo { + @apply bg-util-colors-indigo-indigo-200; + background-image: linear-gradient(90deg, #8098f9e6 0%, #444ce7e6 100%), linear-gradient(135deg, var(--color-premium-badge-border-highlight-color) 0%, #6172f3 100%); + } .premium-badge-indigo.allowHover:hover { - @apply bg-gradient-to-r from-[#6172f3e6] to-[#2d31a6e6] bg-util-colors-indigo-indigo-300 cursor-pointer + @apply bg-util-colors-indigo-indigo-300; + background-image: linear-gradient(90deg, #6172f3e6 0%, #2d31a6e6 100%), linear-gradient(135deg, var(--color-premium-badge-border-highlight-color) 0%, #2d31a6 100%); } + .premium-badge-gray { + @apply bg-util-colors-gray-gray-200; + background-image: linear-gradient(90deg, #98a2b2e6 0%, #676f83e6 100%), linear-gradient(135deg, var(--color-premium-badge-border-highlight-color) 0%, #676f83 100%); + } .premium-badge-gray.allowHover:hover { - @apply bg-gradient-to-r from-[#676f83e6] to-[#354052e6] bg-util-colors-gray-gray-300 cursor-pointer + @apply bg-util-colors-gray-gray-300; + background-image: linear-gradient(90deg, #676f83e6 0%, #354052e6 100%), linear-gradient(135deg, var(--color-premium-badge-border-highlight-color) 0%, #354052 100%); } + .premium-badge-orange { + @apply bg-util-colors-orange-orange-200; + background-image: linear-gradient(90deg, #ff692ee6 0%, #e04f16e6 100%), linear-gradient(135deg, var(--color-premium-badge-border-highlight-color) 0%, #e62e05 100%); + } .premium-badge-orange.allowHover:hover { - @apply bg-gradient-to-r from-[#ff4405e6] to-[#b93815e6] bg-util-colors-orange-orange-300 cursor-pointer + @apply bg-util-colors-orange-orange-300; + background-image: linear-gradient(90deg, #ff4405e6 0%, #b93815e6 100%), linear-gradient(135deg, var(--color-premium-badge-border-highlight-color) 0%, #e62e05 100%); } -} \ No newline at end of file +} diff --git a/web/app/components/base/premium-badge/index.tsx b/web/app/components/base/premium-badge/index.tsx index b97dd5daf8..ce162d7565 100644 --- a/web/app/components/base/premium-badge/index.tsx +++ b/web/app/components/base/premium-badge/index.tsx @@ -61,13 +61,9 @@ const PremiumBadge: React.FC<PremiumBadgeProps> = ({ {children} <Highlight className={classNames( - 'absolute top-0 opacity-50 hover:opacity-80', + 'absolute top-0 opacity-50 right-1/2 translate-x-[20%] transition-all duration-100 ease-out hover:opacity-80 hover:translate-x-[30%]', size === 's' ? 'h-[18px] w-12' : 'h-6 w-12', )} - style={{ - right: '50%', - transform: 'translateX(10%)', - }} /> </div> ) diff --git a/web/app/components/base/prompt-editor/hooks.ts b/web/app/components/base/prompt-editor/hooks.ts index c9e4cc129c..87119f8b49 100644 --- a/web/app/components/base/prompt-editor/hooks.ts +++ b/web/app/components/base/prompt-editor/hooks.ts @@ -74,9 +74,11 @@ export const useSelectOrDelete: UseSelectOrDeleteHandler = (nodeKey: string, com ) const handleSelect = useCallback((e: MouseEvent) => { - e.stopPropagation() - clearSelection() - setSelected(true) + if (!e.metaKey && !e.ctrlKey) { + e.stopPropagation() + clearSelection() + setSelected(true) + } }, [setSelected, clearSelection]) useEffect(() => { diff --git a/web/app/components/base/prompt-editor/index.tsx b/web/app/components/base/prompt-editor/index.tsx index 8631104b2c..94a65e4b62 100644 --- a/web/app/components/base/prompt-editor/index.tsx +++ b/web/app/components/base/prompt-editor/index.tsx @@ -13,7 +13,7 @@ import { CodeNode } from '@lexical/code' import { LexicalComposer } from '@lexical/react/LexicalComposer' import { RichTextPlugin } from '@lexical/react/LexicalRichTextPlugin' import { ContentEditable } from '@lexical/react/LexicalContentEditable' -import LexicalErrorBoundary from '@lexical/react/LexicalErrorBoundary' +import { LexicalErrorBoundary } from '@lexical/react/LexicalErrorBoundary' import { OnChangePlugin } from '@lexical/react/LexicalOnChangePlugin' import { HistoryPlugin } from '@lexical/react/LexicalHistoryPlugin' // import TreeView from './plugins/tree-view' diff --git a/web/app/components/base/prompt-editor/plugins/component-picker-block/index.tsx b/web/app/components/base/prompt-editor/plugins/component-picker-block/index.tsx index 562bb8c0d9..b43d2c8117 100644 --- a/web/app/components/base/prompt-editor/plugins/component-picker-block/index.tsx +++ b/web/app/components/base/prompt-editor/plugins/component-picker-block/index.tsx @@ -120,10 +120,8 @@ const ComponentPicker = ({ }, [editor, checkForTriggerMatch, triggerString]) const handleClose = useCallback(() => { - ReactDOM.flushSync(() => { - const escapeEvent = new KeyboardEvent('keydown', { key: 'Escape' }) - editor.dispatchCommand(KEY_ESCAPE_COMMAND, escapeEvent) - }) + const escapeEvent = new KeyboardEvent('keydown', { key: 'Escape' }) + editor.dispatchCommand(KEY_ESCAPE_COMMAND, escapeEvent) }, [editor]) const renderMenu = useCallback<MenuRenderFn<PickerBlockMenuOption>>(( @@ -132,7 +130,11 @@ const ComponentPicker = ({ ) => { if (!(anchorElementRef.current && (allFlattenOptions.length || workflowVariableBlock?.show))) return null - refs.setReference(anchorElementRef.current) + + setTimeout(() => { + if (anchorElementRef.current) + refs.setReference(anchorElementRef.current) + }, 0) return ( <> @@ -149,7 +151,6 @@ const ComponentPicker = ({ visibility: isPositioned ? 'visible' : 'hidden', }} ref={refs.setFloating} - data-testid="component-picker-container" > { workflowVariableBlock?.show && ( @@ -173,7 +174,7 @@ const ComponentPicker = ({ <div className='my-1 h-px w-full -translate-x-1 bg-divider-subtle'></div> ) } - <div data-testid="options-list"> + <div> { options.map((option, index) => ( <Fragment key={option.key}> diff --git a/web/app/components/base/prompt-editor/plugins/workflow-variable-block/component.tsx b/web/app/components/base/prompt-editor/plugins/workflow-variable-block/component.tsx index 2f6c3374a7..731841f423 100644 --- a/web/app/components/base/prompt-editor/plugins/workflow-variable-block/component.tsx +++ b/web/app/components/base/prompt-editor/plugins/workflow-variable-block/component.tsx @@ -1,5 +1,6 @@ import { memo, + useCallback, useEffect, useState, } from 'react' @@ -13,6 +14,7 @@ import { RiErrorWarningFill, RiMoreLine, } from '@remixicon/react' +import { useReactFlow, useStoreApi } from 'reactflow' import { useSelectOrDelete } from '../../hooks' import type { WorkflowNodesMap } from './node' import { WorkflowVariableBlockNode } from './node' @@ -66,6 +68,9 @@ const WorkflowVariableBlockComponent = ({ const isChatVar = isConversationVar(variables) const isException = isExceptionVariable(varName, node?.type) + const reactflow = useReactFlow() + const store = useStoreApi() + useEffect(() => { if (!editor.hasNodes([WorkflowVariableBlockNode])) throw new Error('WorkflowVariableBlockPlugin: WorkflowVariableBlock not registered on editor') @@ -83,6 +88,26 @@ const WorkflowVariableBlockComponent = ({ ) }, [editor]) + const handleVariableJump = useCallback(() => { + const workflowContainer = document.getElementById('workflow-container') + const { + clientWidth, + clientHeight, + } = workflowContainer! + + const { + setViewport, + } = reactflow + const { transform } = store.getState() + const zoom = transform[2] + const position = node.position + setViewport({ + x: (clientWidth - 400 - node.width! * zoom) / 2 - position!.x * zoom, + y: (clientHeight - node.height! * zoom) / 2 - position!.y * zoom, + zoom: transform[2], + }) + }, [node, reactflow, store]) + const Item = ( <div className={cn( @@ -90,6 +115,10 @@ const WorkflowVariableBlockComponent = ({ isSelected ? ' border-state-accent-solid bg-state-accent-hover' : ' border-components-panel-border-subtle bg-components-badge-white-to-dark', !node && !isEnv && !isChatVar && '!border-state-destructive-solid !bg-state-destructive-hover', )} + onClick={(e) => { + e.stopPropagation() + handleVariableJump() + }} ref={ref} > {!isEnv && !isChatVar && ( @@ -144,7 +173,7 @@ const WorkflowVariableBlockComponent = ({ } if (!node) - return null + return Item return ( <Tooltip diff --git a/web/app/components/base/prompt-editor/types.ts b/web/app/components/base/prompt-editor/types.ts index 0f09fb2473..e82ec1da16 100644 --- a/web/app/components/base/prompt-editor/types.ts +++ b/web/app/components/base/prompt-editor/types.ts @@ -64,7 +64,7 @@ export type GetVarType = (payload: { export type WorkflowVariableBlockType = { show?: boolean variables?: NodeOutPutVar[] - workflowNodesMap?: Record<string, Pick<Node['data'], 'title' | 'type'>> + workflowNodesMap?: Record<string, Pick<Node['data'], 'title' | 'type' | 'height' | 'width' | 'position'>> onInsert?: () => void onDelete?: () => void getVarType?: GetVarType diff --git a/web/app/components/base/radio-card/simple/style.module.css b/web/app/components/base/radio-card/simple/style.module.css index 58a87086bc..93a0f43c2b 100644 --- a/web/app/components/base/radio-card/simple/style.module.css +++ b/web/app/components/base/radio-card/simple/style.module.css @@ -22,4 +22,4 @@ .item.active .radio { border-width: 5px; border-color: #155EEF; -} \ No newline at end of file +} diff --git a/web/app/components/base/radio/component/group/index.tsx b/web/app/components/base/radio/component/group/index.tsx index 088aec729f..7ead2f9d88 100644 --- a/web/app/components/base/radio/component/group/index.tsx +++ b/web/app/components/base/radio/component/group/index.tsx @@ -15,7 +15,7 @@ export default function Group({ children, value, onChange, className = '' }: TRa onChange?.(value) } return ( - <div className={cn('flex items-center bg-gray-50', s.container, className)}> + <div className={cn('flex items-center bg-workflow-block-parma-bg text-text-secondary', s.container, className)}> <RadioGroupContext.Provider value={{ value, onChange: onRadioChange }}> {children} </RadioGroupContext.Provider> diff --git a/web/app/components/base/radio/component/radio/index.tsx b/web/app/components/base/radio/component/radio/index.tsx index eddc53dd7f..aa4e6d0c7f 100644 --- a/web/app/components/base/radio/component/radio/index.tsx +++ b/web/app/components/base/radio/component/radio/index.tsx @@ -37,14 +37,15 @@ export default function Radio({ const isChecked = groupContext ? groupContext.value === value : checked const divClassName = ` flex items-center py-1 relative - px-7 cursor-pointer hover:bg-gray-200 rounded + px-7 cursor-pointer text-text-secondary rounded + hover:bg-components-option-card-option-bg-hover hover:shadow-xs ` return ( <div className={cn( s.label, disabled ? s.disabled : '', - isChecked ? 'bg-white shadow' : '', + isChecked ? 'bg-components-option-card-option-bg-hover shadow-xs' : '', divClassName, className)} onClick={() => handleChange(value)} diff --git a/web/app/components/base/select/index.tsx b/web/app/components/base/select/index.tsx index a4cfeaef7f..fa8730f698 100644 --- a/web/app/components/base/select/index.tsx +++ b/web/app/components/base/select/index.tsx @@ -324,7 +324,7 @@ const PortalSelect: FC<PortalSelectProps> = ({ : ( <div className={classNames(` - group flex items-center justify-between px-2.5 h-9 rounded-lg border-0 bg-components-input-bg-normal hover:bg-state-base-hover-alt text-sm ${readonly ? 'cursor-not-allowed' : 'cursor-pointer'} + group flex items-center justify-between px-2.5 h-9 rounded-lg border-0 bg-components-input-bg-normal hover:bg-state-base-hover-alt text-sm ${readonly ? 'cursor-not-allowed' : 'cursor-pointer'} `, triggerClassName, triggerClassNameFn?.(open))} title={selectedItem?.name} > diff --git a/web/app/components/base/slider/style.css b/web/app/components/base/slider/style.css index e215a9914e..5d87fb0897 100644 --- a/web/app/components/base/slider/style.css +++ b/web/app/components/base/slider/style.css @@ -8,4 +8,4 @@ .slider-track-1 { background-color: var(--color-components-slider-track); -} \ No newline at end of file +} diff --git a/web/app/components/base/svg-gallery/index.tsx b/web/app/components/base/svg-gallery/index.tsx index 94fc82c740..710a0107fb 100644 --- a/web/app/components/base/svg-gallery/index.tsx +++ b/web/app/components/base/svg-gallery/index.tsx @@ -1,7 +1,7 @@ import { useEffect, useRef, useState } from 'react' import { SVG } from '@svgdotjs/svg.js' -import ImagePreview from '@/app/components/base/image-uploader/image-preview' import DOMPurify from 'dompurify' +import ImagePreview from '@/app/components/base/image-uploader/image-preview' export const SVGRenderer = ({ content }: { content: string }) => { const svgRef = useRef<HTMLDivElement>(null) diff --git a/web/app/components/base/tab-slider/index.tsx b/web/app/components/base/tab-slider/index.tsx index fd6b876d02..56cde52154 100644 --- a/web/app/components/base/tab-slider/index.tsx +++ b/web/app/components/base/tab-slider/index.tsx @@ -40,7 +40,7 @@ const TabSlider: FC<TabSliderProps> = ({ const newIndex = options.findIndex(option => option.value === value) setActiveIndex(newIndex) updateSliderStyle(newIndex) - }, [value, options, pluginList]) + }, [value, options, pluginList?.total]) return ( <div className={cn(className, 'relative inline-flex items-center justify-center rounded-[10px] bg-components-segmented-control-bg-normal p-0.5')}> @@ -69,13 +69,13 @@ const TabSlider: FC<TabSliderProps> = ({ {option.text} {/* if no plugin installed, the badge won't show */} {option.value === 'plugins' - && (pluginList?.plugins.length ?? 0) > 0 + && (pluginList?.total ?? 0) > 0 && <Badge size='s' uppercase={true} state={BadgeState.Default} > - {pluginList?.plugins.length} + {pluginList?.total} </Badge> } </div> diff --git a/web/app/components/base/tag-input/index.tsx b/web/app/components/base/tag-input/index.tsx index 2be9c5ffc7..4824b6f62d 100644 --- a/web/app/components/base/tag-input/index.tsx +++ b/web/app/components/base/tag-input/index.tsx @@ -93,7 +93,7 @@ const TagInput: FC<TagInputProps> = ({ <div className={cn('group/tag-add mt-1 flex items-center gap-x-0.5', !isSpecialMode ? 'rounded-md border border-dashed border-divider-deep px-1.5' : '')}> {!isSpecialMode && !focused && <RiAddLine className='h-3.5 w-3.5 text-text-placeholder group-hover/tag-add:text-text-secondary' />} <AutosizeInput - inputClassName={cn('appearance-none caret-[#295EFF] outline-none placeholder:text-text-placeholder group-hover/tag-add:placeholder:text-text-secondary', isSpecialMode ? 'bg-transparent' : '')} + inputClassName={cn('appearance-none text-text-primary caret-[#295EFF] outline-none placeholder:text-text-placeholder group-hover/tag-add:placeholder:text-text-secondary', isSpecialMode ? 'bg-transparent' : '')} className={cn( !isInWorkflow && 'max-w-[300px]', isInWorkflow && 'max-w-[146px]', diff --git a/web/app/components/base/theme-selector.tsx b/web/app/components/base/theme-selector.tsx new file mode 100644 index 0000000000..8dfe1d2602 --- /dev/null +++ b/web/app/components/base/theme-selector.tsx @@ -0,0 +1,97 @@ +'use client' + +import { useState } from 'react' +import { + RiCheckLine, + RiComputerLine, + RiMoonLine, + RiSunLine, +} from '@remixicon/react' +import { useTranslation } from 'react-i18next' +import { useTheme } from 'next-themes' +import ActionButton from '@/app/components/base/action-button' +import { + PortalToFollowElem, + PortalToFollowElemContent, + PortalToFollowElemTrigger, +} from '@/app/components/base/portal-to-follow-elem' + +export type Theme = 'light' | 'dark' | 'system' + +export default function ThemeSelector() { + const { t } = useTranslation() + const { theme, setTheme } = useTheme() + const [open, setOpen] = useState(false) + + const handleThemeChange = (newTheme: Theme) => { + setTheme(newTheme) + setOpen(false) + } + + const getCurrentIcon = () => { + switch (theme) { + case 'light': return <RiSunLine className='h-4 w-4 text-text-tertiary' /> + case 'dark': return <RiMoonLine className='h-4 w-4 text-text-tertiary' /> + default: return <RiComputerLine className='h-4 w-4 text-text-tertiary' /> + } + } + + return ( + <PortalToFollowElem + open={open} + onOpenChange={setOpen} + placement='bottom-end' + offset={{ mainAxis: 6 }} + > + <PortalToFollowElemTrigger + onClick={() => setOpen(!open)} + > + <ActionButton + className={`h-8 w-8 p-[6px] ${open && 'bg-state-base-hover'}`} + > + {getCurrentIcon()} + </ActionButton> + </PortalToFollowElemTrigger> + <PortalToFollowElemContent className='z-[1000]'> + <div className='flex w-[144px] flex-col items-start rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg-blur p-1 shadow-lg'> + <button + className='flex w-full items-center gap-1 rounded-lg px-2 py-1.5 text-text-secondary hover:bg-state-base-hover' + onClick={() => handleThemeChange('light')} + > + <RiSunLine className='h-4 w-4 text-text-tertiary' /> + <div className='flex grow items-center justify-start px-1'> + <span className='system-md-regular'>{t('common.theme.light')}</span> + </div> + {theme === 'light' && <div className='flex h-4 w-4 shrink-0 items-center justify-center'> + <RiCheckLine className='h-4 w-4 text-text-accent' /> + </div>} + </button> + <button + className='flex w-full items-center gap-1 rounded-lg px-2 py-1.5 text-text-secondary hover:bg-state-base-hover' + onClick={() => handleThemeChange('dark')} + > + <RiMoonLine className='h-4 w-4 text-text-tertiary' /> + <div className='flex grow items-center justify-start px-1'> + <span className='system-md-regular'>{t('common.theme.dark')}</span> + </div> + {theme === 'dark' && <div className='flex h-4 w-4 shrink-0 items-center justify-center'> + <RiCheckLine className='h-4 w-4 text-text-accent' /> + </div>} + </button> + <button + className='flex w-full items-center gap-1 rounded-lg px-2 py-1.5 text-text-secondary hover:bg-state-base-hover' + onClick={() => handleThemeChange('system')} + > + <RiComputerLine className='h-4 w-4 text-text-tertiary' /> + <div className='flex grow items-center justify-start px-1'> + <span className='system-md-regular'>{t('common.theme.auto')}</span> + </div> + {theme === 'system' && <div className='flex h-4 w-4 shrink-0 items-center justify-center'> + <RiCheckLine className='h-4 w-4 text-text-accent' /> + </div>} + </button> + </div> + </PortalToFollowElemContent> + </PortalToFollowElem> + ) +} diff --git a/web/app/components/base/theme-switcher.tsx b/web/app/components/base/theme-switcher.tsx new file mode 100644 index 0000000000..902d064a66 --- /dev/null +++ b/web/app/components/base/theme-switcher.tsx @@ -0,0 +1,58 @@ +'use client' +import { + RiComputerLine, + RiMoonLine, + RiSunLine, +} from '@remixicon/react' +import { useTheme } from 'next-themes' +import cn from '@/utils/classnames' + +export type Theme = 'light' | 'dark' | 'system' + +export default function ThemeSwitcher() { + const { theme, setTheme } = useTheme() + + const handleThemeChange = (newTheme: Theme) => { + setTheme(newTheme) + } + + return ( + <div className='flex items-center rounded-[10px] bg-components-segmented-control-bg-normal p-0.5'> + <div + className={cn( + 'rounded-lg px-2 py-1 text-text-tertiary hover:bg-state-base-hover hover:text-text-secondary', + theme === 'system' && 'bg-components-segmented-control-item-active-bg text-text-accent-light-mode-only shadow-sm hover:bg-components-segmented-control-item-active-bg hover:text-text-accent-light-mode-only', + )} + onClick={() => handleThemeChange('system')} + > + <div className='p-0.5'> + <RiComputerLine className='h-4 w-4' /> + </div> + </div> + <div className={cn('h-[14px] w-px bg-transparent', theme === 'dark' && 'bg-divider-regular')}></div> + <div + className={cn( + 'rounded-lg px-2 py-1 text-text-tertiary hover:bg-state-base-hover hover:text-text-secondary', + theme === 'light' && 'bg-components-segmented-control-item-active-bg text-text-accent-light-mode-only shadow-sm hover:bg-components-segmented-control-item-active-bg hover:text-text-accent-light-mode-only', + )} + onClick={() => handleThemeChange('light')} + > + <div className='p-0.5'> + <RiSunLine className='h-4 w-4' /> + </div> + </div> + <div className={cn('h-[14px] w-px bg-transparent', theme === 'system' && 'bg-divider-regular')}></div> + <div + className={cn( + 'rounded-lg px-2 py-1 text-text-tertiary hover:bg-state-base-hover hover:text-text-secondary', + theme === 'dark' && 'bg-components-segmented-control-item-active-bg text-text-accent-light-mode-only shadow-sm hover:bg-components-segmented-control-item-active-bg hover:text-text-accent-light-mode-only', + )} + onClick={() => handleThemeChange('dark')} + > + <div className='p-0.5'> + <RiMoonLine className='h-4 w-4' /> + </div> + </div> + </div> + ) +} diff --git a/web/app/components/base/toast/index.tsx b/web/app/components/base/toast/index.tsx index b84a321dd4..725c7af8c2 100644 --- a/web/app/components/base/toast/index.tsx +++ b/web/app/components/base/toast/index.tsx @@ -68,7 +68,7 @@ const Toast = ({ </div> <div className={`flex py-1 ${size === 'md' ? 'px-1' : 'px-0.5'} grow flex-col items-start gap-1`}> <div className='flex items-center gap-1'> - <div className='system-sm-semibold text-text-primary'>{message}</div> + <div className='system-sm-semibold text-text-primary [word-break:break-word]'>{message}</div> {customComponent} </div> {children && <div className='system-xs-regular text-text-secondary'> diff --git a/web/app/components/base/toast/style.module.css b/web/app/components/base/toast/style.module.css index a6c9cdba41..216ccf1da8 100644 --- a/web/app/components/base/toast/style.module.css +++ b/web/app/components/base/toast/style.module.css @@ -41,4 +41,4 @@ text-align: center; font-size: .2rem; color: rgba(255, 255, 255, 0.86); -} \ No newline at end of file +} diff --git a/web/app/components/base/tooltip/index.tsx b/web/app/components/base/tooltip/index.tsx index e6c4de31f1..53f36be5fb 100644 --- a/web/app/components/base/tooltip/index.tsx +++ b/web/app/components/base/tooltip/index.tsx @@ -92,6 +92,7 @@ const Tooltip: FC<TooltipProps> = ({ }} onMouseLeave={() => triggerMethod === 'hover' && handleLeave(true)} asChild={asChild} + className={!asChild ? triggerClassName : ''} > {children || <div data-testid={triggerTestId} className={triggerClassName || 'h-3.5 w-3.5 shrink-0 p-[1px]'}><RiQuestionLine className='h-full w-full text-text-quaternary hover:text-text-tertiary' /></div>} </PortalToFollowElemTrigger> diff --git a/web/app/components/base/voice-input/index.module.css b/web/app/components/base/voice-input/index.module.css index 18d51da525..8286f9d9a9 100644 --- a/web/app/components/base/voice-input/index.module.css +++ b/web/app/components/base/voice-input/index.module.css @@ -7,4 +7,4 @@ background: linear-gradient(91.92deg, #104AE1 -1.74%, #0098EE 75.74%); background-clip: text; color: transparent; -} \ No newline at end of file +} diff --git a/web/app/components/billing/annotation-full/style.module.css b/web/app/components/billing/annotation-full/style.module.css index 7ad3180a5a..15bedd84ca 100644 --- a/web/app/components/billing/annotation-full/style.module.css +++ b/web/app/components/billing/annotation-full/style.module.css @@ -4,4 +4,4 @@ -webkit-text-fill-color: transparent; background-clip: text; text-fill-color: transparent; -} \ No newline at end of file +} diff --git a/web/app/components/billing/apps-full-in-dialog/style.module.css b/web/app/components/billing/apps-full-in-dialog/style.module.css index d3150914e8..1f68e665a6 100644 --- a/web/app/components/billing/apps-full-in-dialog/style.module.css +++ b/web/app/components/billing/apps-full-in-dialog/style.module.css @@ -4,4 +4,4 @@ -webkit-text-fill-color: transparent; background-clip: text; text-fill-color: transparent; -} \ No newline at end of file +} diff --git a/web/app/components/billing/config.ts b/web/app/components/billing/config.ts index 52651259ef..1d5fbc7491 100644 --- a/web/app/components/billing/config.ts +++ b/web/app/components/billing/config.ts @@ -1,3 +1,4 @@ +import type { BasicPlan } from '@/app/components/billing/type' import { Plan, type PlanInfo, Priority } from '@/app/components/billing/type' const supportModelProviders = 'OpenAI/Anthropic/Llama2/Azure OpenAI/Hugging Face/Replicate' @@ -10,7 +11,7 @@ export const contactSalesUrl = 'https://vikgc6bnu1s.typeform.com/dify-business' export const getStartedWithCommunityUrl = 'https://github.com/langgenius/dify' export const getWithPremiumUrl = 'https://aws.amazon.com/marketplace/pp/prodview-t22mebxzwjhu6' -export const ALL_PLANS: Record<Plan, PlanInfo> = { +export const ALL_PLANS: Record<BasicPlan, PlanInfo> = { sandbox: { level: 1, price: 0, @@ -22,6 +23,7 @@ export const ALL_PLANS: Record<Plan, PlanInfo> = { vectorSpace: '50MB', documentsUploadQuota: 0, documentsRequestQuota: 10, + apiRateLimit: 5000, documentProcessingPriority: Priority.standard, messageRequest: 200, annotatedResponse: 10, @@ -38,6 +40,7 @@ export const ALL_PLANS: Record<Plan, PlanInfo> = { vectorSpace: '5GB', documentsUploadQuota: 0, documentsRequestQuota: 100, + apiRateLimit: NUM_INFINITE, documentProcessingPriority: Priority.priority, messageRequest: 5000, annotatedResponse: 2000, @@ -54,6 +57,7 @@ export const ALL_PLANS: Record<Plan, PlanInfo> = { vectorSpace: '20GB', documentsUploadQuota: 0, documentsRequestQuota: 1000, + apiRateLimit: NUM_INFINITE, documentProcessingPriority: Priority.topPriority, messageRequest: 10000, annotatedResponse: 5000, @@ -62,7 +66,7 @@ export const ALL_PLANS: Record<Plan, PlanInfo> = { } export const defaultPlan = { - type: Plan.sandbox, + type: Plan.sandbox as BasicPlan, usage: { documents: 50, vectorSpace: 1, diff --git a/web/app/components/billing/pricing/index.tsx b/web/app/components/billing/pricing/index.tsx index 0d4011895b..0516794414 100644 --- a/web/app/components/billing/pricing/index.tsx +++ b/web/app/components/billing/pricing/index.tsx @@ -15,6 +15,7 @@ import { useProviderContext } from '@/context/provider-context' import GridMask from '@/app/components/base/grid-mask' import { useAppContext } from '@/context/app-context' import classNames from '@/utils/classnames' +import { useGetPricingPageLanguage } from '@/context/i18n' type Props = { onCancel: () => void @@ -33,6 +34,11 @@ const Pricing: FC<Props> = ({ useKeyPress(['esc'], onCancel) + const pricingPageLanguage = useGetPricingPageLanguage() + const pricingPageURL = pricingPageLanguage + ? `https://dify.ai/${pricingPageLanguage}/pricing#plans-and-features` + : 'https://dify.ai/pricing#plans-and-features' + return createPortal( <div className='fixed inset-0 bottom-0 left-0 right-0 top-0 z-[1000] bg-background-overlay-backdrop p-4 backdrop-blur-[6px]' @@ -127,7 +133,7 @@ const Pricing: FC<Props> = ({ </div> <div className='flex items-center justify-center py-4'> <div className='flex items-center justify-center gap-x-0.5 rounded-lg px-3 py-2 text-components-button-secondary-accent-text hover:cursor-pointer hover:bg-state-accent-hover'> - <Link href='https://dify.ai/pricing#plans-and-features' className='system-sm-medium'>{t('billing.plansCommon.comparePlanAndFeatures')}</Link> + <Link href={pricingPageURL} className='system-sm-medium'>{t('billing.plansCommon.comparePlanAndFeatures')}</Link> <RiArrowRightUpLine className='size-4' /> </div> </div> diff --git a/web/app/components/billing/pricing/plan-item.tsx b/web/app/components/billing/pricing/plan-item.tsx index a0b8685989..07af0ffec8 100644 --- a/web/app/components/billing/pricing/plan-item.tsx +++ b/web/app/components/billing/pricing/plan-item.tsx @@ -2,7 +2,8 @@ import type { FC, ReactNode } from 'react' import React from 'react' import { useTranslation } from 'react-i18next' -import { RiApps2Line, RiBook2Line, RiBrain2Line, RiChatAiLine, RiFileEditLine, RiFolder6Line, RiGroupLine, RiHardDrive3Line, RiHistoryLine, RiProgress3Line, RiQuestionLine, RiSeoLine } from '@remixicon/react' +import { RiApps2Line, RiBook2Line, RiBrain2Line, RiChatAiLine, RiFileEditLine, RiFolder6Line, RiGroupLine, RiHardDrive3Line, RiHistoryLine, RiProgress3Line, RiQuestionLine, RiSeoLine, RiTerminalBoxLine } from '@remixicon/react' +import type { BasicPlan } from '../type' import { Plan } from '../type' import { ALL_PLANS, NUM_INFINITE } from '../config' import Toast from '../../base/toast' @@ -15,8 +16,8 @@ import { useAppContext } from '@/context/app-context' import { fetchSubscriptionUrls } from '@/service/billing' type Props = { - currentPlan: Plan - plan: Plan + currentPlan: BasicPlan + plan: BasicPlan planRange: PlanRange canPay: boolean } @@ -127,8 +128,8 @@ const PlanItem: FC<Props> = ({ <div className='flex flex-col gap-y-1'> {style[plan].icon} <div className='flex items-center'> - <div className='text-lg font-semibold uppercase leading-[125%] text-text-primary'>{t(`${i18nPrefix}.name`)}</div> - {isMostPopularPlan && <div className='ml-1 flex items-center justify-center rounded-full border-[0.5px] bg-price-premium-badge-background px-1 py-[3px] text-components-premium-badge-grey-text-stop-0 shadow-xs'> + <div className='grow text-lg font-semibold uppercase leading-[125%] text-text-primary'>{t(`${i18nPrefix}.name`)}</div> + {isMostPopularPlan && <div className='ml-1 flex shrink-0 items-center justify-center rounded-full border-[0.5px] bg-price-premium-badge-background px-1 py-[3px] text-components-premium-badge-grey-text-stop-0 shadow-xs'> <div className='pl-0.5'> <SparklesSoft className='size-3' /> </div> @@ -205,6 +206,14 @@ const PlanItem: FC<Props> = ({ label={t('billing.plansCommon.documentsRequestQuota', { count: planInfo.documentsRequestQuota })} tooltip={t('billing.plansCommon.documentsRequestQuotaTooltip')} /> + <KeyValue + icon={<RiTerminalBoxLine />} + label={ + planInfo.apiRateLimit === NUM_INFINITE ? `${t('billing.plansCommon.unlimitedApiRate')}` + : `${t('billing.plansCommon.apiRateLimitUnit', { count: planInfo.apiRateLimit })} ${t('billing.plansCommon.apiRateLimit')}` + } + tooltip={planInfo.apiRateLimit === NUM_INFINITE ? null : t('billing.plansCommon.apiRateLimitTooltip') as string} + /> <KeyValue icon={<RiProgress3Line />} label={[t(`billing.plansCommon.priority.${planInfo.documentProcessingPriority}`), t('billing.plansCommon.documentProcessingPriority')].join('')} diff --git a/web/app/components/billing/type.ts b/web/app/components/billing/type.ts index 28bce37098..506ef46799 100644 --- a/web/app/components/billing/type.ts +++ b/web/app/components/billing/type.ts @@ -9,6 +9,9 @@ export enum Priority { priority = 'priority', topPriority = 'top-priority', } + +export type BasicPlan = Plan.sandbox | Plan.professional | Plan.team + export type PlanInfo = { level: number price: number @@ -20,6 +23,7 @@ export type PlanInfo = { vectorSpace: string documentsUploadQuota: number documentsRequestQuota: number + apiRateLimit: number documentProcessingPriority: Priority logHistory: number messageRequest: number @@ -60,7 +64,7 @@ export type CurrentPlanInfoBackend = { billing: { enabled: boolean subscription: { - plan: Plan + plan: BasicPlan } } members: { @@ -90,6 +94,11 @@ export type CurrentPlanInfoBackend = { education: { enabled: boolean activated: boolean + }, + webapp_copyright_enabled: boolean + workspace_members: { + size: number + limit: number } } diff --git a/web/app/components/billing/upgrade-btn/style.module.css b/web/app/components/billing/upgrade-btn/style.module.css index a1e2687679..ab8c30ebd5 100644 --- a/web/app/components/billing/upgrade-btn/style.module.css +++ b/web/app/components/billing/upgrade-btn/style.module.css @@ -6,4 +6,4 @@ .upgradeBtn:hover { background: linear-gradient(99deg, rgba(255, 255, 255, 0.12) 7.16%, rgba(255, 255, 255, 0.00) 85.47%), linear-gradient(280deg, #02C2FF 12.96%, #001AFF 90.95%); box-shadow: 0px 4px 6px -2px rgba(16, 18, 40, 0.08), 0px 12px 16px -4px rgba(0, 209, 255, 0.08); -} \ No newline at end of file +} diff --git a/web/app/components/billing/vector-space-full/style.module.css b/web/app/components/billing/vector-space-full/style.module.css index 7ad3180a5a..15bedd84ca 100644 --- a/web/app/components/billing/vector-space-full/style.module.css +++ b/web/app/components/billing/vector-space-full/style.module.css @@ -4,4 +4,4 @@ -webkit-text-fill-color: transparent; background-clip: text; text-fill-color: transparent; -} \ No newline at end of file +} diff --git a/web/app/components/custom/custom-web-app-brand/index.tsx b/web/app/components/custom/custom-web-app-brand/index.tsx index b95016fec5..ea2f44caea 100644 --- a/web/app/components/custom/custom-web-app-brand/index.tsx +++ b/web/app/components/custom/custom-web-app-brand/index.tsx @@ -10,7 +10,7 @@ import { RiLoader2Line, RiPlayLargeLine, } from '@remixicon/react' -import LogoSite from '@/app/components/base/logo/logo-site' +import DifyLogo from '@/app/components/base/logo/dify-logo' import Switch from '@/app/components/base/switch' import Button from '@/app/components/base/button' import Divider from '@/app/components/base/divider' @@ -24,6 +24,7 @@ import { } from '@/service/common' import { useAppContext } from '@/context/app-context' import cn from '@/utils/classnames' +import { useGlobalPublicStore } from '@/context/global-public-context' const ALLOW_FILE_EXTENSIONS = ['svg', 'png'] @@ -39,6 +40,7 @@ const CustomWebAppBrand = () => { const [fileId, setFileId] = useState('') const [imgKey, setImgKey] = useState(Date.now()) const [uploadProgress, setUploadProgress] = useState(0) + const systemFeatures = useGlobalPublicStore(s => s.systemFeatures) const isSandbox = enableBilling && plan.type === Plan.sandbox const uploading = uploadProgress > 0 && uploadProgress < 100 const webappLogo = currentWorkspace.custom_config?.replace_webapp_logo || '' @@ -128,7 +130,7 @@ const CustomWebAppBrand = () => { <div className='system-xs-regular text-text-tertiary'>{t('custom.webapp.changeLogoTip')}</div> </div> <div className='flex items-center'> - {(uploadDisabled || (!webappLogo && !webappBrandRemoved)) && ( + {(!uploadDisabled && webappLogo && !webappBrandRemoved) && ( <> <Button variant='ghost' @@ -244,9 +246,12 @@ const CustomWebAppBrand = () => { {!webappBrandRemoved && ( <> <div className='system-2xs-medium-uppercase text-text-tertiary'>POWERED BY</div> - {webappLogo - ? <img src={`${webappLogo}?hash=${imgKey}`} alt='logo' className='block h-5 w-auto' /> - : <LogoSite className='!h-5' /> + { + systemFeatures.branding.enabled && systemFeatures.branding.workspace_logo + ? <img src={systemFeatures.branding.workspace_logo} alt='logo' className='block h-5 w-auto' /> + : webappLogo + ? <img src={`${webappLogo}?hash=${imgKey}`} alt='logo' className='block h-5 w-auto' /> + : <DifyLogo size='small' /> } </> )} @@ -303,9 +308,12 @@ const CustomWebAppBrand = () => { {!webappBrandRemoved && ( <> <div className='system-2xs-medium-uppercase text-text-tertiary'>POWERED BY</div> - {webappLogo - ? <img src={`${webappLogo}?hash=${imgKey}`} alt='logo' className='block h-5 w-auto' /> - : <LogoSite className='!h-5' /> + { + systemFeatures.branding.enabled && systemFeatures.branding.workspace_logo + ? <img src={systemFeatures.branding.workspace_logo} alt='logo' className='block h-5 w-auto' /> + : webappLogo + ? <img src={`${webappLogo}?hash=${imgKey}`} alt='logo' className='block h-5 w-auto' /> + : <DifyLogo size='small' /> } </> )} diff --git a/web/app/components/custom/custom-web-app-brand/style.module.css b/web/app/components/custom/custom-web-app-brand/style.module.css index 6fe7d84944..bdc7d7cfbf 100644 --- a/web/app/components/custom/custom-web-app-brand/style.module.css +++ b/web/app/components/custom/custom-web-app-brand/style.module.css @@ -1,3 +1,3 @@ .mask { background: linear-gradient(273deg, rgba(255, 255, 255, 0.00) 51.75%, rgba(255, 255, 255, 0.80) 115.32%); -} \ No newline at end of file +} diff --git a/web/app/components/custom/style.module.css b/web/app/components/custom/style.module.css index 088fd3a55d..0a839f6387 100644 --- a/web/app/components/custom/style.module.css +++ b/web/app/components/custom/style.module.css @@ -3,4 +3,4 @@ -webkit-background-clip: text; -webkit-text-fill-color: transparent; background-clip: text; -} \ No newline at end of file +} diff --git a/web/app/components/datasets/create/file-preview/index.module.css b/web/app/components/datasets/create/file-preview/index.module.css index d4109e41fc..da139fa9ae 100644 --- a/web/app/components/datasets/create/file-preview/index.module.css +++ b/web/app/components/datasets/create/file-preview/index.module.css @@ -2,39 +2,38 @@ @apply flex flex-col border-l border-components-panel-border shrink-0 bg-background-default-lighter; width: 100%; } - + .previewHeader { @apply border-b border-divider-subtle shrink-0; margin: 42px 32px 0; padding-bottom: 16px; } - + .previewHeader .title { @apply flex justify-between items-center text-text-primary; } - + .previewHeader .fileName { @apply text-text-tertiary; } - + .previewHeader .filetype { @apply text-text-tertiary; } - + .previewContent { @apply overflow-y-auto grow text-text-secondary; padding: 20px 32px; } - + .previewContent .loading { width: 100%; height: 180px; background: transparent center no-repeat url(../assets/Loading.svg); background-size: contain; } - + .fileContent { white-space: pre-line; word-break: break-all; } - \ No newline at end of file diff --git a/web/app/components/datasets/create/file-uploader/index.tsx b/web/app/components/datasets/create/file-uploader/index.tsx index 84ef226d34..5ed4b7dd05 100644 --- a/web/app/components/datasets/create/file-uploader/index.tsx +++ b/web/app/components/datasets/create/file-uploader/index.tsx @@ -196,22 +196,68 @@ const FileUploader = ({ e.stopPropagation() e.target === dragRef.current && setDragging(false) } + type FileWithPath = { + relativePath?: string + } & File + const traverseFileEntry = useCallback( + (entry: any, prefix = ''): Promise<FileWithPath[]> => { + return new Promise((resolve) => { + if (entry.isFile) { + entry.file((file: FileWithPath) => { + file.relativePath = `${prefix}${file.name}` + resolve([file]) + }) + } + else if (entry.isDirectory) { + const reader = entry.createReader() + const entries: any[] = [] + const read = () => { + reader.readEntries(async (results: FileSystemEntry[]) => { + if (!results.length) { + const files = await Promise.all( + entries.map(ent => + traverseFileEntry(ent, `${prefix}${entry.name}/`), + ), + ) + resolve(files.flat()) + } + else { + entries.push(...results) + read() + } + }) + } + read() + } + else { + resolve([]) + } + }) + }, + [], + ) - const handleDrop = useCallback((e: DragEvent) => { - e.preventDefault() - e.stopPropagation() - setDragging(false) - if (!e.dataTransfer) - return - - let files = [...e.dataTransfer.files] as File[] - if (notSupportBatchUpload) - files = files.slice(0, 1) - - const validFiles = files.filter(isValid) - initialUpload(validFiles) - }, [initialUpload, isValid, notSupportBatchUpload]) - + const handleDrop = useCallback( + async (e: DragEvent) => { + e.preventDefault() + e.stopPropagation() + setDragging(false) + if (!e.dataTransfer) return + const nested = await Promise.all( + Array.from(e.dataTransfer.items).map((it) => { + const entry = (it as any).webkitGetAsEntry?.() + if (entry) return traverseFileEntry(entry) + const f = it.getAsFile?.() + return f ? Promise.resolve([f]) : Promise.resolve([]) + }), + ) + let files = nested.flat() + if (notSupportBatchUpload) files = files.slice(0, 1) + const valid = files.filter(isValid) + initialUpload(valid) + }, + [initialUpload, isValid, notSupportBatchUpload, traverseFileEntry], + ) const selectHandle = () => { if (fileUploader.current) fileUploader.current.click() diff --git a/web/app/components/datasets/create/notion-page-preview/index.module.css b/web/app/components/datasets/create/notion-page-preview/index.module.css index 6f2e5d9fa5..b3bc589480 100644 --- a/web/app/components/datasets/create/notion-page-preview/index.module.css +++ b/web/app/components/datasets/create/notion-page-preview/index.module.css @@ -31,4 +31,3 @@ .fileContent { white-space: pre-line; } - \ No newline at end of file diff --git a/web/app/components/datasets/create/step-one/index.module.css b/web/app/components/datasets/create/step-one/index.module.css index bbc5f5a634..581409bbff 100644 --- a/web/app/components/datasets/create/step-one/index.module.css +++ b/web/app/components/datasets/create/step-one/index.module.css @@ -63,4 +63,4 @@ .notionIcon { background: var(--color-components-card-bg) center no-repeat url(../assets/notion.svg); background-size: 24px; -} \ No newline at end of file +} diff --git a/web/app/components/datasets/create/step-one/index.tsx b/web/app/components/datasets/create/step-one/index.tsx index 38c885ebe2..11664c7ada 100644 --- a/web/app/components/datasets/create/step-one/index.tsx +++ b/web/app/components/datasets/create/step-one/index.tsx @@ -21,6 +21,7 @@ import VectorSpaceFull from '@/app/components/billing/vector-space-full' import classNames from '@/utils/classnames' import { Icon3Dots } from '@/app/components/base/icons/src/vender/line/others' import { ENABLE_WEBSITE_FIRECRAWL, ENABLE_WEBSITE_JINAREADER, ENABLE_WEBSITE_WATERCRAWL } from '@/config' + type IStepOneProps = { datasetId?: string dataSourceType?: DataSourceType @@ -45,7 +46,8 @@ type IStepOneProps = { type NotionConnectorProps = { onSetting: () => void } -export const NotionConnector = ({ onSetting }: NotionConnectorProps) => { +export const NotionConnector = (props: NotionConnectorProps) => { + const { onSetting } = props const { t } = useTranslation() return ( @@ -162,7 +164,7 @@ const StepOne = ({ > <span className={cn(s.datasetIcon)} /> <span - title={t('datasetCreation.stepOne.dataSourceType.file')} + title={t('datasetCreation.stepOne.dataSourceType.file')!} className='truncate' > {t('datasetCreation.stepOne.dataSourceType.file')} @@ -185,7 +187,7 @@ const StepOne = ({ > <span className={cn(s.datasetIcon, s.notion)} /> <span - title={t('datasetCreation.stepOne.dataSourceType.notion')} + title={t('datasetCreation.stepOne.dataSourceType.notion')!} className='truncate' > {t('datasetCreation.stepOne.dataSourceType.notion')} @@ -193,21 +195,21 @@ const StepOne = ({ </div> {(ENABLE_WEBSITE_FIRECRAWL || ENABLE_WEBSITE_JINAREADER || ENABLE_WEBSITE_WATERCRAWL) && ( <div - className={cn( - s.dataSourceItem, - 'system-sm-medium', - dataSourceType === DataSourceType.WEB && s.active, - dataSourceTypeDisable && dataSourceType !== DataSourceType.WEB && s.disabled, - )} - onClick={() => changeType(DataSourceType.WEB)} + className={cn( + s.dataSourceItem, + 'system-sm-medium', + dataSourceType === DataSourceType.WEB && s.active, + dataSourceTypeDisable && dataSourceType !== DataSourceType.WEB && s.disabled, + )} + onClick={() => changeType(DataSourceType.WEB)} > - <span className={cn(s.datasetIcon, s.web)} /> - <span - title={t('datasetCreation.stepOne.dataSourceType.web')} - className='truncate' - > - {t('datasetCreation.stepOne.dataSourceType.web')} - </span> + <span className={cn(s.datasetIcon, s.web)} /> + <span + title={t('datasetCreation.stepOne.dataSourceType.web')!} + className='truncate' + > + {t('datasetCreation.stepOne.dataSourceType.web')} + </span> </div> )} </div> diff --git a/web/app/components/datasets/create/step-two/index.module.css b/web/app/components/datasets/create/step-two/index.module.css index 178cbeba85..e79ab2df6f 100644 --- a/web/app/components/datasets/create/step-two/index.module.css +++ b/web/app/components/datasets/create/step-two/index.module.css @@ -385,7 +385,7 @@ max-width: 524px; } -/* +/* * `fixed` must under `previewHeader` because of style override would not work */ .fixed { diff --git a/web/app/components/datasets/create/step-two/index.tsx b/web/app/components/datasets/create/step-two/index.tsx index 6b6580ae7e..c931addd1a 100644 --- a/web/app/components/datasets/create/step-two/index.tsx +++ b/web/app/components/datasets/create/step-two/index.tsx @@ -63,6 +63,7 @@ import CustomDialog from '@/app/components/base/dialog' import { PortalToFollowElem, PortalToFollowElemContent, PortalToFollowElemTrigger } from '@/app/components/base/portal-to-follow-elem' import { AlertTriangle } from '@/app/components/base/icons/src/vender/solid/alertsAndFeedback' import { noop } from 'lodash-es' +import { useDocLink } from '@/context/i18n' const TextLabel: FC<PropsWithChildren> = (props) => { return <label className='system-sm-semibold text-text-secondary'>{props.children}</label> @@ -146,6 +147,7 @@ const StepTwo = ({ updateRetrievalMethodCache, }: StepTwoProps) => { const { t } = useTranslation() + const docLink = useDocLink() const { locale } = useContext(I18n) const media = useBreakpoints() const isMobile = media === MediaType.mobile @@ -865,10 +867,10 @@ const StepTwo = ({ <> <CustomDialog show={isQAConfirmDialogOpen} onClose={() => setIsQAConfirmDialogOpen(false)} className='w-[432px]'> <header className='mb-4 pt-6'> - <h2 className='text-lg font-semibold'> + <h2 className='text-lg font-semibold text-text-primary'> {t('datasetCreation.stepTwo.qaSwitchHighQualityTipTitle')} </h2> - <p className='mt-2 text-sm font-normal'> + <p className='mt-2 text-sm font-normal text-text-secondary'> {t('datasetCreation.stepTwo.qaSwitchHighQualityTipContent')} </p> </header> @@ -928,7 +930,7 @@ const StepTwo = ({ </div> )} {hasSetIndexType && indexType === IndexingType.ECONOMICAL && ( - <div className='system-xs-medium mt-2'> + <div className='system-xs-medium mt-2 text-text-tertiary'> {t('datasetCreation.stepTwo.indexSettingTip')} <Link className='text-text-accent' href={`/datasets/${datasetId}/settings`}>{t('datasetCreation.stepTwo.datasetSettingLink')}</Link> </div> @@ -962,7 +964,9 @@ const StepTwo = ({ <div className={'mb-1'}> <div className='system-md-semibold mb-0.5 text-text-secondary'>{t('datasetSettings.form.retrievalSetting.title')}</div> <div className='body-xs-regular text-text-tertiary'> - <a target='_blank' rel='noopener noreferrer' href='https://docs.dify.ai/guides/knowledge-base/create-knowledge-and-upload-documents#id-4-retrieval-settings' className='text-text-accent'>{t('datasetSettings.form.retrievalSetting.learnMore')}</a> + <a target='_blank' rel='noopener noreferrer' + href={docLink('/guides/knowledge-base/create-knowledge-and-upload-documents')} + className='text-text-accent'>{t('datasetSettings.form.retrievalSetting.learnMore')}</a> {t('datasetSettings.form.retrievalSetting.longDescription')} </div> </div> diff --git a/web/app/components/datasets/create/website/base/url-input.tsx b/web/app/components/datasets/create/website/base/url-input.tsx index b7dc9bfca5..ab965bebc3 100644 --- a/web/app/components/datasets/create/website/base/url-input.tsx +++ b/web/app/components/datasets/create/website/base/url-input.tsx @@ -4,6 +4,7 @@ import React, { useCallback, useState } from 'react' import { useTranslation } from 'react-i18next' import Input from './input' import Button from '@/app/components/base/button' +import { useDocLink } from '@/context/i18n' const I18N_PREFIX = 'datasetCreation.stepOne.website' @@ -17,6 +18,7 @@ const UrlInput: FC<Props> = ({ onRun, }) => { const { t } = useTranslation() + const docLink = useDocLink() const [url, setUrl] = useState('') const handleUrlChange = useCallback((url: string | number) => { setUrl(url as string) @@ -32,7 +34,7 @@ const UrlInput: FC<Props> = ({ <Input value={url} onChange={handleUrlChange} - placeholder='https://docs.dify.ai' + placeholder={docLink()} /> <Button variant='primary' diff --git a/web/app/components/datasets/create/website/jina-reader/base/url-input.tsx b/web/app/components/datasets/create/website/jina-reader/base/url-input.tsx index e6b0475874..ee300cb567 100644 --- a/web/app/components/datasets/create/website/jina-reader/base/url-input.tsx +++ b/web/app/components/datasets/create/website/jina-reader/base/url-input.tsx @@ -4,6 +4,7 @@ import React, { useCallback, useState } from 'react' import { useTranslation } from 'react-i18next' import Input from './input' import Button from '@/app/components/base/button' +import { useDocLink } from '@/context/i18n' const I18N_PREFIX = 'datasetCreation.stepOne.website' @@ -17,6 +18,7 @@ const UrlInput: FC<Props> = ({ onRun, }) => { const { t } = useTranslation() + const docLink = useDocLink() const [url, setUrl] = useState('') const handleUrlChange = useCallback((url: string | number) => { setUrl(url as string) @@ -32,7 +34,7 @@ const UrlInput: FC<Props> = ({ <Input value={url} onChange={handleUrlChange} - placeholder='https://docs.dify.ai' + placeholder={docLink()} /> <Button variant='primary' diff --git a/web/app/components/datasets/create/website/no-data.tsx b/web/app/components/datasets/create/website/no-data.tsx index 65a314f516..1db83a7417 100644 --- a/web/app/components/datasets/create/website/no-data.tsx +++ b/web/app/components/datasets/create/website/no-data.tsx @@ -17,6 +17,7 @@ type Props = { const NoData: FC<Props> = ({ onConfig, + provider, }) => { const { t } = useTranslation() @@ -38,7 +39,7 @@ const NoData: FC<Props> = ({ } : null, } - const currentProvider = Object.values(providerConfig).find(provider => provider !== null) || providerConfig[DataSourceProvider.jinaReader] + const currentProvider = providerConfig[provider] || providerConfig[DataSourceProvider.jinaReader] if (!currentProvider) return null diff --git a/web/app/components/datasets/documents/detail/batch-modal/csv-downloader.tsx b/web/app/components/datasets/documents/detail/batch-modal/csv-downloader.tsx index 90bdb707df..7f3be965b3 100644 --- a/web/app/components/datasets/documents/detail/batch-modal/csv-downloader.tsx +++ b/web/app/components/datasets/documents/detail/batch-modal/csv-downloader.tsx @@ -50,20 +50,20 @@ const CSVDownload: FC<{ docForm: ChunkingMode }> = ({ docForm }) => { return ( <div className='mt-6'> - <div className='text-sm font-medium text-gray-900'>{t('share.generation.csvStructureTitle')}</div> + <div className='text-sm font-medium text-text-primary'>{t('share.generation.csvStructureTitle')}</div> <div className='mt-2 max-h-[500px] overflow-auto'> {docForm === ChunkingMode.qa && ( - <table className='w-full table-fixed border-separate border-spacing-0 rounded-lg border border-gray-200 text-xs'> - <thead className='text-gray-500'> + <table className='w-full table-fixed border-separate border-spacing-0 rounded-lg border border-divider-subtle text-xs'> + <thead className='text-text-secondary'> <tr> - <td className='h-9 border-b border-gray-200 pl-3 pr-2'>{t('datasetDocuments.list.batchModal.question')}</td> - <td className='h-9 border-b border-gray-200 pl-3 pr-2'>{t('datasetDocuments.list.batchModal.answer')}</td> + <td className='h-9 border-b border-divider-subtle pl-3 pr-2'>{t('datasetDocuments.list.batchModal.question')}</td> + <td className='h-9 border-b border-divider-subtle pl-3 pr-2'>{t('datasetDocuments.list.batchModal.answer')}</td> </tr> </thead> - <tbody className='text-gray-700'> + <tbody className='text-text-tertiary'> <tr> - <td className='h-9 border-b border-gray-100 pl-3 pr-2 text-[13px]'>{t('datasetDocuments.list.batchModal.question')} 1</td> - <td className='h-9 border-b border-gray-100 pl-3 pr-2 text-[13px]'>{t('datasetDocuments.list.batchModal.answer')} 1</td> + <td className='h-9 border-b border-divider-subtle pl-3 pr-2 text-[13px]'>{t('datasetDocuments.list.batchModal.question')} 1</td> + <td className='h-9 border-b border-divider-subtle pl-3 pr-2 text-[13px]'>{t('datasetDocuments.list.batchModal.answer')} 1</td> </tr> <tr> <td className='h-9 pl-3 pr-2 text-[13px]'>{t('datasetDocuments.list.batchModal.question')} 2</td> @@ -73,15 +73,15 @@ const CSVDownload: FC<{ docForm: ChunkingMode }> = ({ docForm }) => { </table> )} {docForm === ChunkingMode.text && ( - <table className='w-full table-fixed border-separate border-spacing-0 rounded-lg border border-gray-200 text-xs'> - <thead className='text-gray-500'> + <table className='w-full table-fixed border-separate border-spacing-0 rounded-lg border border-divider-subtle text-xs'> + <thead className='text-text-secondary'> <tr> - <td className='h-9 border-b border-gray-200 pl-3 pr-2'>{t('datasetDocuments.list.batchModal.contentTitle')}</td> + <td className='h-9 border-b border-divider-subtle pl-3 pr-2'>{t('datasetDocuments.list.batchModal.contentTitle')}</td> </tr> </thead> - <tbody className='text-gray-700'> + <tbody className='text-text-tertiary'> <tr> - <td className='h-9 border-b border-gray-100 pl-3 pr-2 text-[13px]'>{t('datasetDocuments.list.batchModal.content')} 1</td> + <td className='h-9 border-b border-divider-subtle pl-3 pr-2 text-[13px]'>{t('datasetDocuments.list.batchModal.content')} 1</td> </tr> <tr> <td className='h-9 pl-3 pr-2 text-[13px]'>{t('datasetDocuments.list.batchModal.content')} 2</td> diff --git a/web/app/components/datasets/documents/detail/batch-modal/csv-uploader.tsx b/web/app/components/datasets/documents/detail/batch-modal/csv-uploader.tsx index 471bf7be2f..c2224296d6 100644 --- a/web/app/components/datasets/documents/detail/batch-modal/csv-uploader.tsx +++ b/web/app/components/datasets/documents/detail/batch-modal/csv-uploader.tsx @@ -93,29 +93,29 @@ const CSVUploader: FC<Props> = ({ /> <div ref={dropRef}> {!file && ( - <div className={cn('flex h-20 items-center rounded-xl border border-dashed border-gray-200 bg-gray-50 text-sm font-normal', dragging && 'border border-[#B2CCFF] bg-[#F5F8FF]')}> + <div className={cn('flex h-20 items-center rounded-xl border border-dashed border-components-panel-border bg-components-panel-bg-blur text-sm font-normal', dragging && 'border border-divider-subtle bg-components-panel-on-panel-item-bg-hover')}> <div className='flex w-full items-center justify-center space-x-2'> <CSVIcon className="shrink-0" /> - <div className='text-gray-500'> + <div className='text-text-secondary'> {t('datasetDocuments.list.batchModal.csvUploadTitle')} - <span className='cursor-pointer text-primary-400' onClick={selectHandle}>{t('datasetDocuments.list.batchModal.browse')}</span> + <span className='cursor-pointer text-text-accent' onClick={selectHandle}>{t('datasetDocuments.list.batchModal.browse')}</span> </div> </div> {dragging && <div ref={dragRef} className='absolute left-0 top-0 h-full w-full' />} </div> )} {file && ( - <div className={cn('group flex h-20 items-center rounded-xl border border-gray-200 bg-gray-50 px-6 text-sm font-normal', 'hover:border-[#B2CCFF] hover:bg-[#F5F8FF]')}> + <div className={cn('group flex h-20 items-center rounded-xl border border-components-panel-border bg-components-panel-bg-blur px-6 text-sm font-normal', 'hover:border-divider-subtle hover:bg-components-panel-on-panel-item-bg-hover')}> <CSVIcon className="shrink-0" /> <div className='ml-2 flex w-0 grow'> - <span className='max-w-[calc(100%_-_30px)] overflow-hidden text-ellipsis whitespace-nowrap text-gray-800'>{file.name.replace(/.csv$/, '')}</span> - <span className='shrink-0 text-gray-500'>.csv</span> + <span className='max-w-[calc(100%_-_30px)] overflow-hidden text-ellipsis whitespace-nowrap text-text-primary'>{file.name.replace(/.csv$/, '')}</span> + <span className='shrink-0 text-text-secondary'>.csv</span> </div> <div className='hidden items-center group-hover:flex'> <Button onClick={selectHandle}>{t('datasetCreation.stepOne.uploader.change')}</Button> - <div className='mx-2 h-4 w-px bg-gray-200' /> + <div className='mx-2 h-4 w-px bg-text-secondary' /> <div className='cursor-pointer p-2' onClick={removeFile}> - <RiDeleteBinLine className='h-4 w-4 text-gray-500' /> + <RiDeleteBinLine className='h-4 w-4 text-text-secondary' /> </div> </div> </div> diff --git a/web/app/components/datasets/documents/detail/batch-modal/index.tsx b/web/app/components/datasets/documents/detail/batch-modal/index.tsx index 775d755106..614471c565 100644 --- a/web/app/components/datasets/documents/detail/batch-modal/index.tsx +++ b/web/app/components/datasets/documents/detail/batch-modal/index.tsx @@ -41,9 +41,9 @@ const BatchModal: FC<IBatchModalProps> = ({ return ( <Modal isShow={isShow} onClose={noop} className='!max-w-[520px] !rounded-xl px-8 py-6'> - <div className='relative pb-1 text-xl font-medium leading-[30px] text-gray-900'>{t('datasetDocuments.list.batchModal.title')}</div> + <div className='relative pb-1 text-xl font-medium leading-[30px] text-text-primary'>{t('datasetDocuments.list.batchModal.title')}</div> <div className='absolute right-4 top-4 cursor-pointer p-2' onClick={onCancel}> - <RiCloseLine className='h-4 w-4 text-gray-500' /> + <RiCloseLine className='h-4 w-4 text-text-secondary' /> </div> <CSVUploader file={currentCSV} diff --git a/web/app/components/datasets/documents/detail/completed/display-toggle.tsx b/web/app/components/datasets/documents/detail/completed/display-toggle.tsx index b5da72c603..24e0be15a1 100644 --- a/web/app/components/datasets/documents/detail/completed/display-toggle.tsx +++ b/web/app/components/datasets/documents/detail/completed/display-toggle.tsx @@ -2,7 +2,7 @@ import React, { type FC } from 'react' import { useTranslation } from 'react-i18next' import { RiLineHeight } from '@remixicon/react' import Tooltip from '@/app/components/base/tooltip' -import { Collapse } from '@/app/components/base/icons/src/public/knowledge' +import { Collapse } from '@/app/components/base/icons/src/vender/line/editor' type DisplayToggleProps = { isCollapsed: boolean diff --git a/web/app/components/datasets/documents/detail/completed/new-child-segment.tsx b/web/app/components/datasets/documents/detail/completed/new-child-segment.tsx index e1d5701f71..0fc9580fdd 100644 --- a/web/app/components/datasets/documents/detail/completed/new-child-segment.tsx +++ b/web/app/components/datasets/documents/detail/completed/new-child-segment.tsx @@ -91,6 +91,7 @@ const NewChildSegmentModal: FC<NewChildSegmentModalProps> = ({ customComponent: isFullDocMode && CustomButton, }) handleCancel('add') + setContent('') if (isFullDocMode) { refreshTimer.current = setTimeout(() => { onSave() diff --git a/web/app/components/datasets/documents/detail/new-segment.tsx b/web/app/components/datasets/documents/detail/new-segment.tsx index 7a08027add..80f0f1dbb9 100644 --- a/web/app/components/datasets/documents/detail/new-segment.tsx +++ b/web/app/components/datasets/documents/detail/new-segment.tsx @@ -118,6 +118,9 @@ const NewSegmentModal: FC<NewSegmentModalProps> = ({ customComponent: CustomButton, }) handleCancel('add') + setQuestion('') + setAnswer('') + setKeywords([]) refreshTimer.current = setTimeout(() => { onSave() }, 3000) diff --git a/web/app/components/datasets/documents/index.tsx b/web/app/components/datasets/documents/index.tsx index 854c984559..acc5dd7606 100644 --- a/web/app/components/datasets/documents/index.tsx +++ b/web/app/components/datasets/documents/index.tsx @@ -29,8 +29,7 @@ import { useChildSegmentListKey, useSegmentListKey } from '@/service/knowledge/u import useEditDocumentMetadata from '../metadata/hooks/use-edit-dataset-metadata' import DatasetMetadataDrawer from '../metadata/metadata-dataset/dataset-metadata-drawer' import StatusWithAction from '../common/document-status-with-action/status-with-action' -import { LanguagesSupported } from '@/i18n/language' -import { getLocaleOnClient } from '@/i18n' +import { useDocLink } from '@/context/i18n' const FolderPlusIcon = ({ className }: React.SVGProps<SVGElement>) => { return <svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg" className={className ?? ''}> @@ -70,7 +69,7 @@ const EmptyElement: FC<{ canAdd: boolean; onClick: () => void; type?: 'upload' | <div className={s.emptyTip}> {t(`datasetDocuments.list.empty.${type}.tip`)} </div> - {type === 'upload' && canAdd && <Button onClick={onClick} className={s.addFileBtn}> + {type === 'upload' && canAdd && <Button onClick={onClick} className={s.addFileBtn} variant='secondary-accent'> <PlusIcon className={s.plusIcon} />{t('datasetDocuments.list.addFile')} </Button>} </div> @@ -86,6 +85,7 @@ const DEFAULT_LIMIT = 10 const Documents: FC<IDocumentsProps> = ({ datasetId }) => { const { t } = useTranslation() + const docLink = useDocLink() const { plan } = useProviderContext() const isFreePlan = plan.type === 'sandbox' const [inputValue, setInputValue] = useState<string>('') // the input value @@ -100,7 +100,6 @@ const Documents: FC<IDocumentsProps> = ({ datasetId }) => { const isDataSourceWeb = dataset?.data_source_type === DataSourceType.WEB const isDataSourceFile = dataset?.data_source_type === DataSourceType.FILE const embeddingAvailable = !!dataset?.embedding_available - const locale = getLocaleOnClient() const debouncedSearchValue = useDebounce(searchValue, { wait: 500 }) const { data: documentsRes, isFetching: isListLoading } = useDocumentList({ @@ -262,12 +261,8 @@ const Documents: FC<IDocumentsProps> = ({ datasetId }) => { <a className='flex items-center text-text-accent' target='_blank' - href={ - locale === LanguagesSupported[1] - ? 'https://docs.dify.ai/v/zh-hans/guides/knowledge-base/integrate-knowledge-within-application' - : 'https://docs.dify.ai/guides/knowledge-base/integrate-knowledge-within-application' - } - > + href={docLink('/guides/knowledge-base/integrate-knowledge-within-application')} + > <span>{t('datasetDocuments.list.learnMore')}</span> <RiExternalLinkLine className='h-3 w-3' /> </a> diff --git a/web/app/components/datasets/documents/style.module.css b/web/app/components/datasets/documents/style.module.css index ececd3ad90..dd1ced5ad5 100644 --- a/web/app/components/datasets/documents/style.module.css +++ b/web/app/components/datasets/documents/style.module.css @@ -26,7 +26,7 @@ @apply text-text-secondary text-sm; } .addFileBtn { - @apply mt-4 w-fit !text-[13px] text-primary-600 font-medium bg-white border-[0.5px]; + @apply mt-4 w-fit !text-[13px] font-medium border-[0.5px]; } .plusIcon { @apply w-4 h-4 mr-2 stroke-current stroke-[1.5px]; @@ -35,16 +35,16 @@ @apply flex items-center justify-center h-full; } .emptyElement { - @apply bg-gray-50 w-[560px] h-fit box-border px-5 py-4 rounded-2xl; + @apply bg-components-panel-on-panel-item-bg border-divider-subtle w-[560px] h-fit box-border px-5 py-4 rounded-2xl; } .emptyTitle { - @apply text-gray-700 font-semibold; + @apply text-text-secondary font-semibold; } .emptyTip { - @apply mt-2 text-gray-500 text-sm font-normal; + @apply mt-2 text-text-primary text-sm font-normal; } .emptySymbolIconWrapper { - @apply w-[44px] h-[44px] border border-solid border-gray-100 rounded-lg flex items-center justify-center mb-2; + @apply w-[44px] h-[44px] border border-solid border-components-button-secondary-border rounded-lg flex items-center justify-center mb-2; } .commonIcon { @apply w-4 h-4 inline-block align-middle; diff --git a/web/app/components/datasets/external-api/external-api-modal/Form.tsx b/web/app/components/datasets/external-api/external-api-modal/Form.tsx index 2746a32a5b..8884cb787f 100644 --- a/web/app/components/datasets/external-api/external-api-modal/Form.tsx +++ b/web/app/components/datasets/external-api/external-api-modal/Form.tsx @@ -5,6 +5,7 @@ import { RiBookOpenLine } from '@remixicon/react' import type { CreateExternalAPIReq, FormSchema } from '../declarations' import Input from '@/app/components/base/input' import cn from '@/utils/classnames' +import { useDocLink } from '@/context/i18n' type FormProps = { className?: string @@ -26,6 +27,7 @@ const Form: FC<FormProps> = React.memo(({ inputClassName, }) => { const { t, i18n } = useTranslation() + const docLink = useDocLink() const [changeKey, setChangeKey] = useState('') const handleFormChange = (key: string, val: string) => { @@ -57,7 +59,7 @@ const Form: FC<FormProps> = React.memo(({ </label> {variable === 'endpoint' && ( <a - href={'https://docs.dify.ai/guides/knowledge-base/external-knowledge-api-documentation' || '/'} + href={docLink('/guides/knowledge-base/connect-external-knowledge-base') || '/'} target='_blank' rel='noopener noreferrer' className='body-xs-regular flex items-center text-text-accent' diff --git a/web/app/components/datasets/external-api/external-api-panel/index.tsx b/web/app/components/datasets/external-api/external-api-panel/index.tsx index d4af34feaa..990fa5fcbc 100644 --- a/web/app/components/datasets/external-api/external-api-panel/index.tsx +++ b/web/app/components/datasets/external-api/external-api-panel/index.tsx @@ -12,6 +12,7 @@ import ActionButton from '@/app/components/base/action-button' import Button from '@/app/components/base/button' import Loading from '@/app/components/base/loading' import { useModalContext } from '@/context/modal-context' +import { useDocLink } from '@/context/i18n' type ExternalAPIPanelProps = { onClose: () => void @@ -19,6 +20,7 @@ type ExternalAPIPanelProps = { const ExternalAPIPanel: React.FC<ExternalAPIPanelProps> = ({ onClose }) => { const { t } = useTranslation() + const docLink = useDocLink() const { setShowExternalKnowledgeAPIModal } = useModalContext() const { externalKnowledgeApiList, mutateExternalKnowledgeApis, isLoading } = useExternalKnowledgeApi() @@ -50,7 +52,8 @@ const ExternalAPIPanel: React.FC<ExternalAPIPanelProps> = ({ onClose }) => { <div className='flex grow flex-col items-start gap-1'> <div className='system-xl-semibold self-stretch text-text-primary'>{t('dataset.externalAPIPanelTitle')}</div> <div className='body-xs-regular self-stretch text-text-tertiary'>{t('dataset.externalAPIPanelDescription')}</div> - <a className='flex cursor-pointer items-center justify-center gap-1 self-stretch' href='https://docs.dify.ai/guides/knowledge-base/external-knowledge-api-documentation' target='_blank'> + <a className='flex cursor-pointer items-center justify-center gap-1 self-stretch' + href={docLink('/guides/knowledge-base/connect-external-knowledge-base')} target='_blank'> <RiBookOpenLine className='h-3 w-3 text-text-accent' /> <div className='body-xs-regular grow text-text-accent'>{t('dataset.externalAPIPanelDocumentation')}</div> </a> diff --git a/web/app/components/datasets/external-api/external-knowledge-api-card/index.tsx b/web/app/components/datasets/external-api/external-knowledge-api-card/index.tsx index 30a3c261b7..bb506316c6 100644 --- a/web/app/components/datasets/external-api/external-knowledge-api-card/index.tsx +++ b/web/app/components/datasets/external-api/external-knowledge-api-card/index.tsx @@ -105,8 +105,8 @@ const ExternalKnowledgeAPICard: React.FC<ExternalKnowledgeAPICardProps> = ({ api return ( <> - <div className={`shadows-shadow-xs flex items-start self-stretch rounded-lg border-[0.5px] border-components-panel-border-subtle - bg-components-panel-on-panel-item-bg p-2 + <div className={`shadows-shadow-xs flex items-start self-stretch rounded-lg border-[0.5px] border-components-panel-border-subtle + bg-components-panel-on-panel-item-bg p-2 pl-3 ${isHovered ? 'border-state-destructive-border bg-state-destructive-hover' : ''}`} > <div className='flex grow flex-col items-start justify-center gap-1.5 py-1'> diff --git a/web/app/components/datasets/external-knowledge-base/create/ExternalApiSelect.tsx b/web/app/components/datasets/external-knowledge-base/create/ExternalApiSelect.tsx index 7acd9f33ee..d4590616d9 100644 --- a/web/app/components/datasets/external-knowledge-base/create/ExternalApiSelect.tsx +++ b/web/app/components/datasets/external-knowledge-base/create/ExternalApiSelect.tsx @@ -59,7 +59,7 @@ const ExternalApiSelect: React.FC<ExternalApiSelectProps> = ({ items, value, onS return ( <div className="relative w-full"> <div - className={`flex cursor-pointer items-center justify-between gap-0.5 self-stretch rounded-lg bg-components-input-bg-normal px-2 + className={`flex cursor-pointer items-center justify-between gap-0.5 self-stretch rounded-lg bg-components-input-bg-normal px-2 py-1 hover:bg-state-base-hover-alt ${isOpen && 'bg-state-base-hover-alt'}`} onClick={() => setIsOpen(!isOpen)} > diff --git a/web/app/components/datasets/external-knowledge-base/create/InfoPanel.tsx b/web/app/components/datasets/external-knowledge-base/create/InfoPanel.tsx index 33386940c4..336e5dbb42 100644 --- a/web/app/components/datasets/external-knowledge-base/create/InfoPanel.tsx +++ b/web/app/components/datasets/external-knowledge-base/create/InfoPanel.tsx @@ -1,8 +1,10 @@ import { RiBookOpenLine } from '@remixicon/react' import { useTranslation } from 'react-i18next' +import { useDocLink } from '@/context/i18n' const InfoPanel = () => { const { t } = useTranslation() + const docLink = useDocLink() return ( <div className='flex w-[360px] flex-col items-start pb-2 pr-8 pt-[108px]'> @@ -16,12 +18,15 @@ const InfoPanel = () => { </span> <span className='system-sm-regular text-text-tertiary'> {t('dataset.connectDatasetIntro.content.front')} - <a className='system-sm-regular ml-1 text-text-accent' href='https://docs.dify.ai/guides/knowledge-base/external-knowledge-api-documentation' target='_blank' rel="noopener noreferrer"> + <a className='system-sm-regular ml-1 text-text-accent' href={docLink('/guides/knowledge-base/external-knowledge-api')} target='_blank' rel="noopener noreferrer"> {t('dataset.connectDatasetIntro.content.link')} </a> {t('dataset.connectDatasetIntro.content.end')} </span> - <a className='system-sm-regular self-stretch text-text-accent' href='https://docs.dify.ai/guides/knowledge-base/connect-external-knowledge' target='_blank' rel="noopener noreferrer"> + <a className='system-sm-regular self-stretch text-text-accent' + href={docLink('/guides/knowledge-base/connect-external-knowledge-base')} + target='_blank' + rel="noopener noreferrer"> {t('dataset.connectDatasetIntro.learnMore')} </a> </p> diff --git a/web/app/components/datasets/external-knowledge-base/create/index.tsx b/web/app/components/datasets/external-knowledge-base/create/index.tsx index 6cbfc05ca7..b8f754e9c4 100644 --- a/web/app/components/datasets/external-knowledge-base/create/index.tsx +++ b/web/app/components/datasets/external-knowledge-base/create/index.tsx @@ -11,6 +11,7 @@ import InfoPanel from './InfoPanel' import type { CreateKnowledgeBaseReq } from './declarations' import Divider from '@/app/components/base/divider' import Button from '@/app/components/base/button' +import { useDocLink } from '@/context/i18n' type ExternalKnowledgeBaseCreateProps = { onConnect: (formValue: CreateKnowledgeBaseReq) => void @@ -19,6 +20,7 @@ type ExternalKnowledgeBaseCreateProps = { const ExternalKnowledgeBaseCreate: React.FC<ExternalKnowledgeBaseCreateProps> = ({ onConnect, loading }) => { const { t } = useTranslation() + const docLink = useDocLink() const router = useRouter() const [formData, setFormData] = useState<CreateKnowledgeBaseReq>({ name: '', @@ -59,7 +61,7 @@ const ExternalKnowledgeBaseCreate: React.FC<ExternalKnowledgeBaseCreateProps> = <span>{t('dataset.connectHelper.helper1')}</span> <span className='system-sm-medium text-text-secondary'>{t('dataset.connectHelper.helper2')}</span> <span>{t('dataset.connectHelper.helper3')}</span> - <a className='system-sm-regular self-stretch text-text-accent' href='https://docs.dify.ai/guides/knowledge-base/connect-external-knowledge' target='_blank' rel="noopener noreferrer"> + <a className='system-sm-regular self-stretch text-text-accent' href={docLink('/guides/knowledge-base/connect-external-knowledge-base')} target='_blank' rel="noopener noreferrer"> {t('dataset.connectHelper.helper4')} </a> <span>{t('dataset.connectHelper.helper5')} </span> diff --git a/web/app/components/datasets/hit-testing/modify-retrieval-modal.tsx b/web/app/components/datasets/hit-testing/modify-retrieval-modal.tsx index e66448b979..f65f395e30 100644 --- a/web/app/components/datasets/hit-testing/modify-retrieval-modal.tsx +++ b/web/app/components/datasets/hit-testing/modify-retrieval-modal.tsx @@ -11,6 +11,7 @@ import EconomicalRetrievalMethodConfig from '@/app/components/datasets/common/ec import Button from '@/app/components/base/button' import { isReRankModelSelected } from '@/app/components/datasets/common/check-rerank-model' import { useModelListAndDefaultModelAndCurrentProviderAndModel } from '@/app/components/header/account-setting/model-provider-page/hooks' +import { useDocLink } from '@/context/i18n' type Props = { indexMethod: string @@ -29,6 +30,7 @@ const ModifyRetrievalModal: FC<Props> = ({ }) => { const ref = useRef(null) const { t } = useTranslation() + const docLink = useDocLink() const [retrievalConfig, setRetrievalConfig] = useState(value) // useClickAway(() => { @@ -72,7 +74,10 @@ const ModifyRetrievalModal: FC<Props> = ({ <a target='_blank' rel='noopener noreferrer' - href='https://docs.dify.ai/guides/knowledge-base/create-knowledge-and-upload-documents#id-4-retrieval-settings' + href={docLink('/guides/knowledge-base/retrieval-test-and-citation#modify-text-retrieval-setting', { + 'zh-Hans': '/guides/knowledge-base/retrieval-test-and-citation#修改文本检索方式', + 'ja-JP': '/guides/knowledge-base/retrieval-test-and-citation', + })} className='text-text-accent' > {t('datasetSettings.form.retrievalSetting.learnMore')} diff --git a/web/app/components/datasets/hit-testing/style.module.css b/web/app/components/datasets/hit-testing/style.module.css index cf8536f998..a421962f48 100644 --- a/web/app/components/datasets/hit-testing/style.module.css +++ b/web/app/components/datasets/hit-testing/style.module.css @@ -40,4 +40,4 @@ .clockIcon { mask-image: url(./assets/clock.svg); @apply bg-gray-500; -} \ No newline at end of file +} diff --git a/web/app/components/datasets/metadata/metadata-dataset/create-metadata-modal.tsx b/web/app/components/datasets/metadata/metadata-dataset/create-metadata-modal.tsx index 179ed9607a..6e00aa4b2c 100644 --- a/web/app/components/datasets/metadata/metadata-dataset/create-metadata-modal.tsx +++ b/web/app/components/datasets/metadata/metadata-dataset/create-metadata-modal.tsx @@ -36,7 +36,7 @@ const CreateMetadataModal: FC<Props> = ({ {trigger} </PortalToFollowElemTrigger> <PortalToFollowElemContent className='z-[1000]'> - <CreateContent {...createContentProps} onClose={() => setOpen(false)} /> + <CreateContent {...createContentProps} onClose={() => setOpen(false)} onBack={() => setOpen(false)} /> </PortalToFollowElemContent> </PortalToFollowElem > diff --git a/web/app/components/datasets/metadata/metadata-dataset/select-metadata-modal.tsx b/web/app/components/datasets/metadata/metadata-dataset/select-metadata-modal.tsx index ccd0284210..91c97156f4 100644 --- a/web/app/components/datasets/metadata/metadata-dataset/select-metadata-modal.tsx +++ b/web/app/components/datasets/metadata/metadata-dataset/select-metadata-modal.tsx @@ -71,6 +71,7 @@ const SelectMetadataModal: FC<Props> = ({ onSave={handleSave} hasBack onBack={() => setStep(Step.select)} + onClose={() => setStep(Step.select)} /> )} </PortalToFollowElemContent> diff --git a/web/app/components/datasets/settings/form/index.tsx b/web/app/components/datasets/settings/form/index.tsx index c36c16c815..b90e65a85b 100644 --- a/web/app/components/datasets/settings/form/index.tsx +++ b/web/app/components/datasets/settings/form/index.tsx @@ -32,6 +32,7 @@ import { ModelTypeEnum } from '@/app/components/header/account-setting/model-pro import { fetchMembers } from '@/service/common' import type { Member } from '@/models/common' import AlertTriangle from '@/app/components/base/icons/src/vender/solid/alertsAndFeedback/AlertTriangle' +import { useDocLink } from '@/context/i18n' const rowClass = 'flex' const labelClass = ` @@ -46,6 +47,7 @@ const getKey = (pageIndex: number, previousPageData: DataSetListResponse) => { const Form = () => { const { t } = useTranslation() + const docLink = useDocLink() const { notify } = useContext(ToastContext) const { mutate } = useSWRConfig() const { isCurrentWorkspaceDatasetOperator } = useAppContext() @@ -232,7 +234,7 @@ const Form = () => { <div className='p-1'> <AlertTriangle className='size-4 text-text-warning-secondary' /> </div> - <span className='system-xs-medium'>{t('datasetSettings.form.upgradeHighQualityTip')}</span> + <span className='system-xs-medium text-text-warning-secondary'>{t('datasetSettings.form.upgradeHighQualityTip')}</span> </div>} </div> </div> @@ -308,7 +310,16 @@ const Form = () => { <div> <div className='system-sm-semibold text-text-secondary'>{t('datasetSettings.form.retrievalSetting.title')}</div> <div className='body-xs-regular text-text-tertiary'> - <a target='_blank' rel='noopener noreferrer' href='https://docs.dify.ai/guides/knowledge-base/create-knowledge-and-upload-documents#id-4-retrieval-settings' className='text-text-accent'>{t('datasetSettings.form.retrievalSetting.learnMore')}</a> + <a + target='_blank' + rel='noopener noreferrer' + href={docLink('/guides/knowledge-base/create-knowledge-and-upload-documents/setting-indexing-methods#setting-the-retrieval-setting', { + 'zh-Hans': '/guides/knowledge-base/create-knowledge-and-upload-documents/setting-indexing-methods#指定检索方式', + 'ja-JP': '/guides/knowledge-base/create-knowledge-and-upload-documents/setting-indexing-methods#検索方法の指定', + })} + className='text-text-accent'> + {t('datasetSettings.form.retrievalSetting.learnMore')} + </a> {t('datasetSettings.form.retrievalSetting.description')} </div> </div> diff --git a/web/app/components/datasets/settings/permission-selector/index.tsx b/web/app/components/datasets/settings/permission-selector/index.tsx index 9bb6f812d4..18b0908956 100644 --- a/web/app/components/datasets/settings/permission-selector/index.tsx +++ b/web/app/components/datasets/settings/permission-selector/index.tsx @@ -151,7 +151,7 @@ const PermissionSelector = ({ disabled, permission, value, memberList, onChange, </div> {isPartialMembers && ( <div className='max-h-[360px] overflow-y-auto border-t-[1px] border-divider-regular pb-1 pl-1 pr-1'> - <div className='sticky left-0 top-0 z-10 bg-white p-2 pb-1'> + <div className='sticky left-0 top-0 z-10 bg-components-panel-on-panel-item-bg p-2 pb-1'> <Input showLeftIcon showClearIcon diff --git a/web/app/components/develop/doc.tsx b/web/app/components/develop/doc.tsx index 4a964df840..562c09a405 100644 --- a/web/app/components/develop/doc.tsx +++ b/web/app/components/develop/doc.tsx @@ -264,8 +264,55 @@ const Doc = ({ appDetail }: IDocProps) => { </button> )} </div> - <article className={cn('prose-xl prose mx-1 overflow-auto rounded-t-xl bg-background-default px-4 pt-16 sm:mx-12', theme === Theme.dark && 'dark:prose-invert')}> - {Template} + <article className={cn('prose-xl prose', theme === Theme.dark && 'prose-invert')} > + {(appDetail?.mode === 'chat' || appDetail?.mode === 'agent-chat') && ( + (() => { + switch (locale) { + case LanguagesSupported[1]: + return <TemplateChatZh appDetail={appDetail} variables={variables} inputs={inputs} /> + case LanguagesSupported[7]: + return <TemplateChatJa appDetail={appDetail} variables={variables} inputs={inputs} /> + default: + return <TemplateChatEn appDetail={appDetail} variables={variables} inputs={inputs} /> + } + })() + )} + {appDetail?.mode === 'advanced-chat' && ( + (() => { + switch (locale) { + case LanguagesSupported[1]: + return <TemplateAdvancedChatZh appDetail={appDetail} variables={variables} inputs={inputs} /> + case LanguagesSupported[7]: + return <TemplateAdvancedChatJa appDetail={appDetail} variables={variables} inputs={inputs} /> + default: + return <TemplateAdvancedChatEn appDetail={appDetail} variables={variables} inputs={inputs} /> + } + })() + )} + {appDetail?.mode === 'workflow' && ( + (() => { + switch (locale) { + case LanguagesSupported[1]: + return <TemplateWorkflowZh appDetail={appDetail} variables={variables} inputs={inputs} /> + case LanguagesSupported[7]: + return <TemplateWorkflowJa appDetail={appDetail} variables={variables} inputs={inputs} /> + default: + return <TemplateWorkflowEn appDetail={appDetail} variables={variables} inputs={inputs} /> + } + })() + )} + {appDetail?.mode === 'completion' && ( + (() => { + switch (locale) { + case LanguagesSupported[1]: + return <TemplateZh appDetail={appDetail} variables={variables} inputs={inputs} /> + case LanguagesSupported[7]: + return <TemplateJa appDetail={appDetail} variables={variables} inputs={inputs} /> + default: + return <TemplateEn appDetail={appDetail} variables={variables} inputs={inputs} /> + } + })() + )} </article> </div> ) diff --git a/web/app/components/develop/secret-key/input-copy.tsx b/web/app/components/develop/secret-key/input-copy.tsx index bb6b4653b5..982c63f620 100644 --- a/web/app/components/develop/secret-key/input-copy.tsx +++ b/web/app/components/develop/secret-key/input-copy.tsx @@ -2,20 +2,18 @@ import React, { useEffect, useState } from 'react' import copy from 'copy-to-clipboard' import { t } from 'i18next' -import s from './style.module.css' import Tooltip from '@/app/components/base/tooltip' +import CopyFeedback from '@/app/components/base/copy-feedback' type IInputCopyProps = { value?: string className?: string - readOnly?: boolean children?: React.ReactNode } const InputCopy = ({ value = '', className, - readOnly = true, children, }: IInputCopyProps) => { const [isCopied, setIsCopied] = useState(false) @@ -45,23 +43,12 @@ const InputCopy = ({ popupContent={isCopied ? `${t('appApi.copied')}` : `${t('appApi.copy')}`} position='bottom' > - {value} + <span className='text-text-secondary'>{value}</span> </Tooltip> </div> </div> - <div className="h-4 shrink-0 border bg-divider-regular" /> - <Tooltip - popupContent={isCopied ? `${t('appApi.copied')}` : `${t('appApi.copy')}`} - position='bottom' - > - <div className="shrink-0 px-0.5"> - <div className={`box-border flex h-[30px] w-[30px] cursor-pointer items-center justify-center rounded-lg hover:bg-state-base-hover ${s.copyIcon} ${isCopied ? s.copied : ''}`} onClick={() => { - copy(value) - setIsCopied(true) - }}> - </div> - </div> - </Tooltip> + <div className="h-4 w-px shrink-0 bg-divider-regular" /> + <div className='mx-1'><CopyFeedback content={value} /></div> </div> </div> ) diff --git a/web/app/components/develop/secret-key/style.module.css b/web/app/components/develop/secret-key/style.module.css index 663d8e817d..f13161c888 100644 --- a/web/app/components/develop/secret-key/style.module.css +++ b/web/app/components/develop/secret-key/style.module.css @@ -54,4 +54,4 @@ .copyIcon.copied { background-image: url(./assets/copied.svg); -} \ No newline at end of file +} diff --git a/web/app/components/develop/template/template.en.mdx b/web/app/components/develop/template/template.en.mdx index 75f044f780..f178645a8e 100755 --- a/web/app/components/develop/template/template.en.mdx +++ b/web/app/components/develop/template/template.en.mdx @@ -57,8 +57,8 @@ The text generation application offers non-session support and is ideal for tran <i>Due to Cloudflare restrictions, the request will be interrupted without a return after 100 seconds.</i> </Property> <Property name='user' type='string' key='user'> - User identifier, used to define the identity of the end-user for retrieval and statistics. - Should be uniquely defined by the developer within the application. + User identifier, used to define the identity of the end-user, convenient for retrieval and statistics. + The rules are defined by the developer and need to ensure that the user identifier is unique within the application. The Service API does not share conversations created by the WebApp. </Property> <Property name='files' type='array[object]' key='files'> File list, suitable for inputting files (images) combined with text understanding and answering questions, available only when the model supports Vision capability. @@ -220,7 +220,7 @@ The text generation application offers non-session support and is ideal for tran - `file` (File) Required The file to be uploaded. - `user` (string) Required - User identifier, defined by the developer's rules, must be unique within the application. + User identifier, defined by the developer's rules, must be unique within the application. The Service API does not share conversations created by the WebApp. ### Response After a successful upload, the server will return the file's ID and related information. @@ -248,7 +248,7 @@ The text generation application offers non-session support and is ideal for tran </Col> <Col sticky> ### Request Example - <CodeGroup title="Request" tag="POST" label="/files/upload" targetCode={`curl -X POST '${props.appDetail.api_base_url}/files/upload' \\\n--header 'Authorization: Bearer {api_key}' \\\n--form 'file=@localfile;type=image/[png|jpeg|jpg|webp|gif] \\\n--form 'user=abc-123'`}> + <CodeGroup title="Request" tag="POST" label="/files/upload" targetCode={`curl -X POST '${props.appDetail.api_base_url}/files/upload' \\\n--header 'Authorization: Bearer {api_key}' \\\n--form 'file=@localfile;type=image/[png|jpeg|jpg|webp|gif]' \\\n--form 'user=abc-123'`}> ```bash {{ title: 'cURL' }} curl -X POST '${props.appDetail.api_base_url}/files/upload' \ @@ -290,7 +290,7 @@ The text generation application offers non-session support and is ideal for tran - `task_id` (string) Task ID, can be obtained from the streaming chunk return Request Body - `user` (string) Required - User identifier, used to define the identity of the end-user, must be consistent with the user passed in the send message interface. + User identifier, used to define the identity of the end-user, must be consistent with the user passed in the send message interface. The Service API does not share conversations created by the WebApp. ### Response - `result` (string) Always returns "success" </Col> @@ -383,6 +383,69 @@ The text generation application offers non-session support and is ideal for tran --- +<Heading + url='/app/feedbacks' + method='GET' + title='Get feedbacks of application' + name='#app-feedbacks' +/> +<Row> + <Col> + Get application's feedbacks. + + ### Query + <Properties> + <Property name='page' type='string' key='page'> + (optional)pagination,default:1 + </Property> + </Properties> + + <Properties> + <Property name='limit' type='string' key='limit'> + (optional) records per page default:20 + </Property> + </Properties> + + ### Response + - `data` (List) return apps feedback list. + </Col> + <Col sticky> + + <CodeGroup title="Request" tag="GET" label="/app/feedbacks" targetCode={`curl -X GET '${props.appDetail.api_base_url}/app/feedbacks?page=1&limit=20'`}> + + ```bash {{ title: 'cURL' }} + curl -X GET '${props.appDetail.api_base_url}/app/feedbacks?page=1&limit=20' \ + --header 'Authorization: Bearer {api_key}' \ + --header 'Content-Type: application/json' + ``` + + </CodeGroup> + + <CodeGroup title="Response"> + ```json {{ title: 'Response' }} + { + "data": [ + { + "id": "8c0fbed8-e2f9-49ff-9f0e-15a35bdd0e25", + "app_id": "f252d396-fe48-450e-94ec-e184218e7346", + "conversation_id": "2397604b-9deb-430e-b285-4726e51fd62d", + "message_id": "709c0b0f-0a96-4a4e-91a4-ec0889937b11", + "rating": "like", + "content": "message feedback information-3", + "from_source": "user", + "from_end_user_id": "74286412-9a1a-42c1-929c-01edb1d381d5", + "from_account_id": null, + "created_at": "2025-04-24T09:24:38", + "updated_at": "2025-04-24T09:24:38" + } + ] + } + ``` + </CodeGroup> + </Col> +</Row> +--- + <Heading url='/text-to-audio' method='POST' @@ -449,6 +512,8 @@ The text generation application offers non-session support and is ideal for tran - `name` (string) application name - `description` (string) application description - `tags` (array[string]) application tags + - `mode` (string) application mode + - `author_name` (string) author name </Col> <Col> <CodeGroup title="Request" tag="GET" label="/info" targetCode={`curl -X GET '${props.appDetail.api_base_url}/info' \\\n-H 'Authorization: Bearer {api_key}'`}> @@ -465,7 +530,9 @@ The text generation application offers non-session support and is ideal for tran "tags": [ "tag1", "tag2" - ] + ], + "mode": "chat", + "author_name": "Dify" } ``` </CodeGroup> @@ -584,3 +651,62 @@ The text generation application offers non-session support and is ideal for tran </CodeGroup> </Col> </Row> +--- + +<Heading + url='/site' + method='GET' + title='Get Application WebApp Settings' + name='#site' +/> +<Row> + <Col> + Used to get the WebApp settings of the application. + ### Response + - `title` (string) WebApp name + - `chat_color_theme` (string) Chat color theme, in hex format + - `chat_color_theme_inverted` (bool) Whether the chat color theme is inverted + - `icon_type` (string) Icon type, `emoji` - emoji, `image` - picture + - `icon` (string) Icon. If it's `emoji` type, it's an emoji symbol; if it's `image` type, it's an image URL. + - `icon_background` (string) Background color in hex format + - `icon_url` (string) Icon URL + - `description` (string) Description + - `copyright` (string) Copyright information + - `privacy_policy` (string) Privacy policy link + - `custom_disclaimer` (string) Custom disclaimer + - `default_language` (string) Default language + - `show_workflow_steps` (bool) Whether to show workflow details + - `use_icon_as_answer_icon` (bool) Whether to replace 🤖 in chat with the WebApp icon + </Col> + <Col> + <CodeGroup title="Request" tag="POST" label="/meta" targetCode={`curl -X GET '${props.appDetail.api_base_url}/site' \\\n-H 'Authorization: Bearer {api_key}'`}> + ```bash {{ title: 'cURL' }} + curl -X GET '${props.appDetail.api_base_url}/site' \ + -H 'Authorization: Bearer {api_key}' + ``` + + </CodeGroup> + + <CodeGroup title="Response"> + ```json {{ title: 'Response' }} + { + "title": "My App", + "chat_color_theme": "#ff4a4a", + "chat_color_theme_inverted": false, + "icon_type": "emoji", + "icon": "😄", + "icon_background": "#FFEAD5", + "icon_url": null, + "description": "This is my app.", + "copyright": "all rights reserved", + "privacy_policy": "", + "custom_disclaimer": "All generated by AI", + "default_language": "en-US", + "show_workflow_steps": false, + "use_icon_as_answer_icon": false, + } + ``` + </CodeGroup> + </Col> +</Row> +___ diff --git a/web/app/components/develop/template/template.ja.mdx b/web/app/components/develop/template/template.ja.mdx index 50bf1e234b..4dbefca8f8 100755 --- a/web/app/components/develop/template/template.ja.mdx +++ b/web/app/components/develop/template/template.ja.mdx @@ -3,10 +3,10 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from # Completion アプリ API -テキスト生成アプリケーションはセッションレスをサポートし、翻訳、記事作成、要約AI等に最適です。 +テキスト生成アプリケーションはセッションレスをサポートし、翻訳、記事作成、要約 AI 等に最適です。 <div> - ### ベースURL + ### ベース URL <CodeGroup title="Code" targetCode={props.appDetail.api_base_url}> ```javascript ``` @@ -14,10 +14,10 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from ### 認証 - サービスAPIは`API-Key`認証を使用します。 - <i>**APIキーの漏洩による重大な結果を避けるため、APIキーはサーバーサイドに保存し、クライアントサイドでは共有や保存しないことを強く推奨します。**</i> + サービス API は `API-Key` 認証を使用します。 + <i>**API キーの漏洩による重大な結果を避けるため、API キーはサーバーサイドに保存し、クライアントサイドでは共有や保存しないことを強く推奨します。**</i> - すべてのAPIリクエストで、以下のように`Authorization` HTTPヘッダーにAPIキーを含めてください: + すべての API リクエストで、以下のように `Authorization` HTTP ヘッダーに API キーを含めてください: <CodeGroup title="Code"> ```javascript @@ -212,7 +212,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from <Row> <Col> メッセージ送信時に使用するファイル(現在は画像のみ対応)をアップロードし、画像とテキストのマルチモーダルな理解を可能にします。 - png、jpg、jpeg、webp、gif形式に対応しています。 + png、jpg、jpeg、webp、gif 形式に対応しています。 <i>アップロードされたファイルは、現在のエンドユーザーのみが使用できます。</i> ### リクエストボディ @@ -220,34 +220,34 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from - `file` (File) 必須 アップロードするファイル。 - `user` (string) 必須 - 開発者のルールで定義されたユーザー識別子。アプリケーション内で一意である必要があります。 + 開発者のルールで定義されたユーザー識別子。アプリケーション内で一意である必要があります。サービス API は WebApp によって作成された会話を共有しません。 ### レスポンス - アップロードが成功すると、サーバーはファイルのIDと関連情報を返します。 + アップロードが成功すると、サーバーはファイルの ID と関連情報を返します。 - `id` (uuid) ID - `name` (string) ファイル名 - `size` (int) ファイルサイズ(バイト) - `extension` (string) ファイル拡張子 - - `mime_type` (string) ファイルのMIMEタイプ + - `mime_type` (string) ファイルの MIME タイプ - `created_by` (uuid) エンドユーザーID - `created_at` (timestamp) 作成タイムスタンプ、例:1705395332 ### エラー - 400, `no_file_uploaded`, ファイルを提供する必要があります - - 400, `too_many_files`, 現在は1つのファイルのみ受け付けています + - 400, `too_many_files`, 現在は 1 つのファイルのみ受け付けています - 400, `unsupported_preview`, ファイルがプレビューに対応していません - 400, `unsupported_estimate`, ファイルが推定に対応していません - 413, `file_too_large`, ファイルが大きすぎます - 415, `unsupported_file_type`, サポートされていない拡張子です。現在はドキュメントファイルのみ受け付けています - - 503, `s3_connection_failed`, S3サービスに接続できません - - 503, `s3_permission_denied`, S3へのファイルアップロード権限がありません - - 503, `s3_file_too_large`, ファイルがS3のサイズ制限を超えています + - 503, `s3_connection_failed`, S3 サービスに接続できません + - 503, `s3_permission_denied`, S3 へのファイルアップロード権限がありません + - 503, `s3_file_too_large`, ファイルが S3 のサイズ制限を超えています - 500, 内部サーバーエラー </Col> <Col sticky> ### リクエスト例 - <CodeGroup title="Request" tag="POST" label="/files/upload" targetCode={`curl -X POST '${props.appDetail.api_base_url}/files/upload' \\\n--header 'Authorization: Bearer {api_key}' \\\n--form 'file=@localfile;type=image/[png|jpeg|jpg|webp|gif] \\\n--form 'user=abc-123'`}> + <CodeGroup title="Request" tag="POST" label="/files/upload" targetCode={`curl -X POST '${props.appDetail.api_base_url}/files/upload' \\\n--header 'Authorization: Bearer {api_key}' \\\n--form 'file=@localfile;type=image/[png|jpeg|jpg|webp|gif]' \\\n--form 'user=abc-123'`}> ```bash {{ title: 'cURL' }} curl -X POST '${props.appDetail.api_base_url}/files/upload' \ @@ -286,10 +286,10 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from <Col> ストリーミングモードでのみサポートされています。 ### パス - - `task_id` (string) タスクID、ストリーミングチャンクの返信から取得可能 + - `task_id` (string) タスク ID、ストリーミングチャンクの返信から取得可能 リクエストボディ - `user` (string) 必須 - ユーザー識別子。エンドユーザーの身元を定義するために使用され、メッセージ送信インターフェースで渡されたユーザーと一致する必要があります。 + ユーザー識別子。エンドユーザーの身元を定義するために使用され、メッセージ送信インターフェースで渡されたユーザーと一致する必要があります。サービス API は WebApp によって作成された会話を共有しません。 ### レスポンス - `result` (string) 常に"success"を返します </Col> @@ -381,6 +381,69 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from </Row> --- +<Heading + url='/app/feedbacks' + method='GET' + title='アプリのメッセージの「いいね」とフィードバックを取得' + name='#app-feedbacks' +/> +<Row> + <Col> + アプリのエンドユーザーからのフィードバックや「いいね」を取得します。 + + ### クエリ + <Properties> + <Property name='page' type='string' key='page'> + (任意)ページ番号。デフォルト値:1 + </Property> + </Properties> + + <Properties> + <Property name='limit' type='string' key='limit'> + (任意)1ページあたりの件数。デフォルト値:20 + </Property> + </Properties> + + ### レスポンス + - `data` (リスト) このアプリの「いいね」とフィードバックの一覧を返します。 + </Col> + <Col sticky> + + <CodeGroup title="Request" tag="GET" label="/app/feedbacks" targetCode={`curl -X GET '${props.appDetail.api_base_url}/app/feedbacks?page=1&limit=20'`}> + + ```bash {{ title: 'cURL' }} + curl -X GET '${props.appDetail.api_base_url}/app/feedbacks?page=1&limit=20' \ + --header 'Authorization: Bearer {api_key}' \ + --header 'Content-Type: application/json' + ``` + + </CodeGroup> + + <CodeGroup title="Response"> + ```json {{ title: 'Response' }} + { + "data": [ + { + "id": "8c0fbed8-e2f9-49ff-9f0e-15a35bdd0e25", + "app_id": "f252d396-fe48-450e-94ec-e184218e7346", + "conversation_id": "2397604b-9deb-430e-b285-4726e51fd62d", + "message_id": "709c0b0f-0a96-4a4e-91a4-ec0889937b11", + "rating": "like", + "content": "message feedback information-3", + "from_source": "user", + "from_end_user_id": "74286412-9a1a-42c1-929c-01edb1d381d5", + "from_account_id": null, + "created_at": "2025-04-24T09:24:38", + "updated_at": "2025-04-24T09:24:38" + } + ] + } + ``` + </CodeGroup> + </Col> +</Row> +--- + <Heading url='/text-to-audio' method='POST' @@ -447,6 +510,8 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from - `name` (string) アプリケーションの名前 - `description` (string) アプリケーションの説明 - `tags` (array[string]) アプリケーションのタグ + - `mode` (string) アプリケーションのモード + - `author_name` (string) 作者の名前 </Col> <Col> <CodeGroup title="Request" tag="GET" label="/info" targetCode={`curl -X GET '${props.appDetail.api_base_url}/info' \\\n-H 'Authorization: Bearer {api_key}'`}> @@ -463,7 +528,9 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from "tags": [ "tag1", "tag2" - ] + ], + "mode": "chat", + "author_name": "Dify" } ``` </CodeGroup> @@ -582,3 +649,62 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from </CodeGroup> </Col> </Row> +--- + +<Heading + url='/site' + method='GET' + title='アプリのWebApp設定を取得' + name='#site' +/> +<Row> + <Col> + アプリの WebApp 設定を取得するために使用します。 + ### レスポンス + - `title` (string) WebApp 名 + - `chat_color_theme` (string) チャットの色テーマ、16 進数形式 + - `chat_color_theme_inverted` (bool) チャットの色テーマを反転するかどうか + - `icon_type` (string) アイコンタイプ、`emoji`-絵文字、`image`-画像 + - `icon` (string) アイコン。`emoji`タイプの場合は絵文字、`image`タイプの場合は画像 URL + - `icon_background` (string) 16 進数形式の背景色 + - `icon_url` (string) アイコンの URL + - `description` (string) 説明 + - `copyright` (string) 著作権情報 + - `privacy_policy` (string) プライバシーポリシーのリンク + - `custom_disclaimer` (string) カスタム免責事項 + - `default_language` (string) デフォルト言語 + - `show_workflow_steps` (bool) ワークフローの詳細を表示するかどうか + - `use_icon_as_answer_icon` (bool) WebApp のアイコンをチャット内の🤖に置き換えるかどうか + </Col> + <Col> + <CodeGroup title="Request" tag="POST" label="/meta" targetCode={`curl -X GET '${props.appDetail.api_base_url}/site' \\\n-H 'Authorization: Bearer {api_key}'`}> + ```bash {{ title: 'cURL' }} + curl -X GET '${props.appDetail.api_base_url}/site' \ + -H 'Authorization: Bearer {api_key}' + ``` + + </CodeGroup> + + <CodeGroup title="Response"> + ```json {{ title: 'Response' }} + { + "title": "My App", + "chat_color_theme": "#ff4a4a", + "chat_color_theme_inverted": false, + "icon_type": "emoji", + "icon": "😄", + "icon_background": "#FFEAD5", + "icon_url": null, + "description": "This is my app.", + "copyright": "all rights reserved", + "privacy_policy": "", + "custom_disclaimer": "All generated by AI", + "default_language": "en-US", + "show_workflow_steps": false, + "use_icon_as_answer_icon": false, + } + ``` + </CodeGroup> + </Col> +</Row> +___ diff --git a/web/app/components/develop/template/template.zh.mdx b/web/app/components/develop/template/template.zh.mdx index 24abb481e3..4af5a28050 100755 --- a/web/app/components/develop/template/template.zh.mdx +++ b/web/app/components/develop/template/template.zh.mdx @@ -15,7 +15,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty } from '../md.tsx' ### 鉴权 - Dify Service API 使用 `API-Key` 进行鉴权。 + Service API 使用 `API-Key` 进行鉴权。 <i>**强烈建议开发者把 `API-Key` 放在后端存储,而非分享或者放在客户端存储,以免 `API-Key` 泄露,导致财产损失。**</i> 所有 API 请求都应在 **`Authorization`** HTTP Header 中包含您的 `API-Key`,如下所示: @@ -60,7 +60,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty } from '../md.tsx' <Property name='files' type='array[object]' key='files'> 上传的文件。 - `type` (string) 支持类型:图片 `image`(目前仅支持图片格式) 。 - - `transfer_method` (string) 传递方式: + - `transfer_method` (string) 传递方式: - `remote_url`: 图片地址。 - `local_file`: 上传文件。 - `url` 图片地址。(仅当传递方式为 `remote_url` 时)。 @@ -226,7 +226,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty } from '../md.tsx' </Col> <Col sticky> - <CodeGroup title="Request" tag="POST" label="/files/upload" targetCode={`curl -X POST '${props.appDetail.api_base_url}/files/upload' \\\n--header 'Authorization: Bearer {api_key}' \\\n--form 'file=@localfile;type=image/[png|jpeg|jpg|webp|gif] \\\n--form 'user=abc-123'`}> + <CodeGroup title="Request" tag="POST" label="/files/upload" targetCode={`curl -X POST '${props.appDetail.api_base_url}/files/upload' \\\n--header 'Authorization: Bearer {api_key}' \\\n--form 'file=@localfile;type=image/[png|jpeg|jpg|webp|gif]' \\\n--form 'user=abc-123'`}> ```bash {{ title: 'cURL' }} curl -X POST '${props.appDetail.api_base_url}/files/upload' \ @@ -266,7 +266,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty } from '../md.tsx' ### Request Body - `user` (string) Required - 用户标识,用于定义终端用户的身份,必须和发送消息接口传入 user 保持一致。 + 用户标识,用于定义终端用户的身份,必须和发送消息接口传入 user 保持一致。API 无法访问 WebApp 创建的会话。 ### Response - `result` (string) 固定返回 success </Col> @@ -355,6 +355,68 @@ import { Row, Col, Properties, Property, Heading, SubProperty } from '../md.tsx' </Col> </Row> +--- +<Heading + url='/app/feedbacks' + method='GET' + title='Get feedbacks of application' + name='#app-feedbacks' +/> +<Row> + <Col> + Get application's feedbacks. + + ### Query + <Properties> + <Property name='page' type='string' key='page'> + (optional)pagination,default:1 + </Property> + </Properties> + + <Properties> + <Property name='limit' type='string' key='limit'> + (optional) records per page default:20 + </Property> + </Properties> + + ### Response + - `data` (List) return apps feedback list. + </Col> + <Col sticky> + + <CodeGroup title="Request" tag="GET" label="/app/feedbacks" targetCode={`curl -X GET '${props.appDetail.api_base_url}/app/feedbacks?page=1&limit=20'`}> + + ```bash {{ title: 'cURL' }} + curl -X GET '${props.appDetail.api_base_url}/app/feedbacks?page=1&limit=20' \ + --header 'Authorization: Bearer {api_key}' \ + --header 'Content-Type: application/json' + ``` + + </CodeGroup> + + <CodeGroup title="Response"> + ```json {{ title: 'Response' }} + { + "data": [ + { + "id": "8c0fbed8-e2f9-49ff-9f0e-15a35bdd0e25", + "app_id": "f252d396-fe48-450e-94ec-e184218e7346", + "conversation_id": "2397604b-9deb-430e-b285-4726e51fd62d", + "message_id": "709c0b0f-0a96-4a4e-91a4-ec0889937b11", + "rating": "like", + "content": "message feedback information-3", + "from_source": "user", + "from_end_user_id": "74286412-9a1a-42c1-929c-01edb1d381d5", + "from_account_id": null, + "created_at": "2025-04-24T09:24:38", + "updated_at": "2025-04-24T09:24:38" + } + ] + } + ``` + </CodeGroup> + </Col> +</Row> --- <Heading @@ -423,6 +485,8 @@ import { Row, Col, Properties, Property, Heading, SubProperty } from '../md.tsx' - `name` (string) 应用名称 - `description` (string) 应用描述 - `tags` (array[string]) 应用标签 + - `mode` (string) 应用模式 + - 'author_name' (string) 作者名称 </Col> <Col> <CodeGroup title="Request" tag="GET" label="/info" targetCode={`curl -X GET '${props.appDetail.api_base_url}/info' \\\n-H 'Authorization: Bearer {api_key}'`}> @@ -439,13 +503,14 @@ import { Row, Col, Properties, Property, Heading, SubProperty } from '../md.tsx' "tags": [ "tag1", "tag2" - ] + ], + "mode": "chat", + "author_name": "Dify" } ``` </CodeGroup> </Col> </Row> - --- <Heading @@ -550,6 +615,64 @@ import { Row, Col, Properties, Property, Heading, SubProperty } from '../md.tsx' </Row> --- +<Heading + url='/site' + method='GET' + title='获取应用 WebApp 设置' + name='#site' +/> +<Row> + <Col> + 用于获取应用的 WebApp 设置 + ### Response + - `title` (string) WebApp 名称 + - `chat_color_theme` (string) 聊天颜色主题,hex 格式 + - `chat_color_theme_inverted` (bool) 聊天颜色主题是否反转 + - `icon_type` (string) 图标类型,`emoji`-表情,`image`-图片 + - `icon` (string) 图标,如果是 `emoji` 类型,则是 emoji 表情符号,如果是 `image` 类型,则是图片 URL + - `icon_background` (string) hex 格式的背景色 + - `icon_url` (string) 图标 URL + - `description` (string) 描述 + - `copyright` (string) 版权信息 + - `privacy_policy` (string) 隐私政策链接 + - `custom_disclaimer` (string) 自定义免责声明 + - `default_language` (string) 默认语言 + - `show_workflow_steps` (bool) 是否显示工作流详情 + - `use_icon_as_answer_icon` (bool) 是否使用 WebApp 图标替换聊天中的 🤖 + </Col> + <Col> + <CodeGroup title="Request" tag="POST" label="/meta" targetCode={`curl -X GET '${props.appDetail.api_base_url}/site' \\\n-H 'Authorization: Bearer {api_key}'`}> + ```bash {{ title: 'cURL' }} + curl -X GET '${props.appDetail.api_base_url}/site' \ + -H 'Authorization: Bearer {api_key}' + ``` + + </CodeGroup> + + <CodeGroup title="Response"> + ```json {{ title: 'Response' }} + { + "title": "My App", + "chat_color_theme": "#ff4a4a", + "chat_color_theme_inverted": false, + "icon_type": "emoji", + "icon": "😄", + "icon_background": "#FFEAD5", + "icon_url": null, + "description": "This is my app.", + "copyright": "all rights reserved", + "privacy_policy": "", + "custom_disclaimer": "All generated by AI", + "default_language": "en-US", + "show_workflow_steps": false, + "use_icon_as_answer_icon": false, + } + ``` + </CodeGroup> + </Col> +</Row> +___ + <Heading url='/apps/annotations' method='GET' @@ -643,13 +766,11 @@ import { Row, Col, Properties, Property, Heading, SubProperty } from '../md.tsx' <CodeGroup title="Response"> ```json {{ title: 'Response' }} { - { - "id": "69d48372-ad81-4c75-9c46-2ce197b4d402", - "question": "What is your name?", - "answer": "I am Dify.", - "hit_count": 0, - "created_at": 1735625869 - } + "id": "69d48372-ad81-4c75-9c46-2ce197b4d402", + "question": "What is your name?", + "answer": "I am Dify.", + "hit_count": 0, + "created_at": 1735625869 } ``` </CodeGroup> @@ -683,10 +804,10 @@ import { Row, Col, Properties, Property, Heading, SubProperty } from '../md.tsx' title="Request" tag="PUT" label="/apps/annotations/{annotation_id}" - targetCode={`curl --location --request POST '${props.apiBaseUrl}/apps/annotations/{annotation_id}' \\\n--header 'Authorization: Bearer {api_key}' \\\n--header 'Content-Type: application/json' \\\n--data-raw '{"question": "What is your name?","answer": "I am Dify."}'`} + targetCode={`curl --location --request PUT '${props.apiBaseUrl}/apps/annotations/{annotation_id}' \\\n--header 'Authorization: Bearer {api_key}' \\\n--header 'Content-Type: application/json' \\\n--data-raw '{"question": "What is your name?","answer": "I am Dify."}'`} > ```bash {{ title: 'cURL' }} - curl --location --request POST '${props.apiBaseUrl}/apps/annotations/{annotation_id}' \ + curl --location --request PUT '${props.apiBaseUrl}/apps/annotations/{annotation_id}' \ --header 'Authorization: Bearer {api_key}' \ --header 'Content-Type: application/json' \ --data-raw '{ @@ -699,13 +820,11 @@ import { Row, Col, Properties, Property, Heading, SubProperty } from '../md.tsx' <CodeGroup title="Response"> ```json {{ title: 'Response' }} { - { - "id": "69d48372-ad81-4c75-9c46-2ce197b4d402", - "question": "What is your name?", - "answer": "I am Dify.", - "hit_count": 0, - "created_at": 1735625869 - } + "id": "69d48372-ad81-4c75-9c46-2ce197b4d402", + "question": "What is your name?", + "answer": "I am Dify.", + "hit_count": 0, + "created_at": 1735625869 } ``` </CodeGroup> @@ -742,8 +861,8 @@ import { Row, Col, Properties, Property, Heading, SubProperty } from '../md.tsx' </CodeGroup> <CodeGroup title="Response"> - ```json {{ title: 'Response' }} - {"result": "success"} + ```text {{ title: 'Response' }} + 204 No Content ``` </CodeGroup> </Col> @@ -763,11 +882,11 @@ import { Row, Col, Properties, Property, Heading, SubProperty } from '../md.tsx' <Property name='action' type='string' key='action'> 动作,只能是 'enable' 或 'disable' </Property> - <Property name='embedding_model_provider' type='string' key='embedding_model_provider'> - 指定的嵌入模型提供商, 必须先在系统内设定好接入的模型,对应的是provider字段 + <Property name='embedding_provider_name' type='string' key='embedding_provider_name'> + 指定的嵌入模型提供商,必须先在系统内设定好接入的模型,对应的是 provider 字段 </Property> - <Property name='embedding_model' type='string' key='embedding_model'> - 指定的嵌入模型,对应的是model字段 + <Property name='embedding_model_name' type='string' key='embedding_model_name'> + 指定的嵌入模型,对应的是 model 字段 </Property> <Property name='score_threshold' type='number' key='score_threshold'> 相似度阈值,当相似度大于该阈值时,系统会自动回复,否则不回复 @@ -775,8 +894,8 @@ import { Row, Col, Properties, Property, Heading, SubProperty } from '../md.tsx' </Properties> </Col> <Col sticky> - 嵌入模型的提供商和模型名称可以通过以下接口获取:v1/workspaces/current/models/model-types/text-embedding, 具体见:通过 API 维护知识库。 使用的Authorization是Dataset的API Token。 - 该接口是异步执行,所以会返回一个job_id,通过查询job状态接口可以获取到最终的执行结果。 + 嵌入模型的提供商和模型名称可以通过以下接口获取:v1/workspaces/current/models/model-types/text-embedding,具体见:通过 API 维护知识库。使用的 Authorization 是 Dataset 的 API Token。 + 该接口是异步执行,所以会返回一个 job_id,通过查询 job 状态接口可以获取到最终的执行结果。 <CodeGroup title="Request" tag="POST" diff --git a/web/app/components/develop/template/template_advanced_chat.en.mdx b/web/app/components/develop/template/template_advanced_chat.en.mdx index 9502f20124..2e0242427b 100644 --- a/web/app/components/develop/template/template_advanced_chat.en.mdx +++ b/web/app/components/develop/template/template_advanced_chat.en.mdx @@ -59,7 +59,7 @@ Chat applications support session persistence, allowing previous chat history to </Property> <Property name='user' type='string' key='user'> User identifier, used to define the identity of the end-user for retrieval and statistics. - Should be uniquely defined by the developer within the application. + Should be uniquely defined by the developer within the application. The Service API does not share conversations created by the WebApp. </Property> <Property name='conversation_id' type='string' key='conversation_id'> Conversation ID, to continue the conversation based on previous chat records, it is necessary to pass the previous message's conversation_id. @@ -324,7 +324,7 @@ Chat applications support session persistence, allowing previous chat history to - `file` (File) Required The file to be uploaded. - `user` (string) Required - User identifier, defined by the developer's rules, must be unique within the application. + User identifier, defined by the developer's rules, must be unique within the application. The Service API does not share conversations created by the WebApp. ### Response After a successful upload, the server will return the file's ID and related information. @@ -352,7 +352,7 @@ Chat applications support session persistence, allowing previous chat history to </Col> <Col sticky> ### Request Example - <CodeGroup title="Request" tag="POST" label="/files/upload" targetCode={`curl -X POST '${props.appDetail.api_base_url}/files/upload' \\\n--header 'Authorization: Bearer {api_key}' \\\n--form 'file=@localfile;type=image/[png|jpeg|jpg|webp|gif] \\\n--form 'user=abc-123'`}> + <CodeGroup title="Request" tag="POST" label="/files/upload" targetCode={`curl -X POST '${props.appDetail.api_base_url}/files/upload' \\\n--header 'Authorization: Bearer {api_key}' \\\n--form 'file=@localfile;type=image/[png|jpeg|jpg|webp|gif]' \\\n--form 'user=abc-123'`}> ```bash {{ title: 'cURL' }} curl -X POST '${props.appDetail.api_base_url}/files/upload' \ @@ -394,7 +394,7 @@ Chat applications support session persistence, allowing previous chat history to - `task_id` (string) Task ID, can be obtained from the streaming chunk return ### Request Body - `user` (string) Required - User identifier, used to define the identity of the end-user, must be consistent with the user passed in the send message interface. + User identifier, used to define the identity of the end-user, must be consistent with the user passed in the message sending interface. The Service API does not share conversations created by the WebApp. ### Response - `result` (string) Always returns "success" </Col> @@ -448,7 +448,7 @@ Chat applications support session persistence, allowing previous chat history to Upvote as `like`, downvote as `dislike`, revoke upvote as `null` </Property> <Property name='user' type='string' key='user'> - User identifier, defined by the developer's rules, must be unique within the application. + User identifier, defined by the developer's rules, must be unique within the application. The Service API does not share conversations created by the WebApp. </Property> <Property name='content' type='string' key='content'> The specific content of message feedback. @@ -487,6 +487,69 @@ Chat applications support session persistence, allowing previous chat history to --- +<Heading + url='/app/feedbacks' + method='GET' + title='Get feedbacks of application' + name='#app-feedbacks' +/> +<Row> + <Col> + Get application's feedbacks. + + ### Query + <Properties> + <Property name='page' type='string' key='page'> + (optional)pagination,default:1 + </Property> + </Properties> + + <Properties> + <Property name='limit' type='string' key='limit'> + (optional) records per page default:20 + </Property> + </Properties> + + ### Response + - `data` (List) return apps feedback list. + </Col> + <Col sticky> + + <CodeGroup title="Request" tag="GET" label="/app/feedbacks" targetCode={`curl -X GET '${props.appDetail.api_base_url}/app/feedbacks?page=1&limit=20'`}> + + ```bash {{ title: 'cURL' }} + curl -X GET '${props.appDetail.api_base_url}/app/feedbacks?page=1&limit=20' \ + --header 'Authorization: Bearer {api_key}' \ + --header 'Content-Type: application/json' + ``` + + </CodeGroup> + + <CodeGroup title="Response"> + ```json {{ title: 'Response' }} + { + "data": [ + { + "id": "8c0fbed8-e2f9-49ff-9f0e-15a35bdd0e25", + "app_id": "f252d396-fe48-450e-94ec-e184218e7346", + "conversation_id": "2397604b-9deb-430e-b285-4726e51fd62d", + "message_id": "709c0b0f-0a96-4a4e-91a4-ec0889937b11", + "rating": "like", + "content": "message feedback information-3", + "from_source": "user", + "from_end_user_id": "74286412-9a1a-42c1-929c-01edb1d381d5", + "from_account_id": null, + "created_at": "2025-04-24T09:24:38", + "updated_at": "2025-04-24T09:24:38" + } + ] + } + ``` + </CodeGroup> + </Col> +</Row> +--- + <Heading url='/messages/{message_id}/suggested' method='GET' @@ -572,22 +635,22 @@ Chat applications support session persistence, allowing previous chat history to ### Response - `data` (array[object]) Message list - - `id` (string) Message ID - - `conversation_id` (string) Conversation ID - - `inputs` (object) User input parameters. - - `query` (string) User input / question content. - - `message_files` (array[object]) Message files - - `id` (string) ID - - `type` (string) File type, image for images - - `url` (string) Preview image URL - - `belongs_to` (string) belongs to,user orassistant - - `answer` (string) Response message content - - `created_at` (timestamp) Creation timestamp, e.g., 1705395332 - - `feedback` (object) Feedback information - - `rating` (string) Upvote as `like` / Downvote as `dislike` - - `retriever_resources` (array[RetrieverResource]) Citation and Attribution List - - `has_more` (bool) Whether there is a next page - - `limit` (int) Number of returned items, if input exceeds system limit, returns system limit amount + - `id` (string) Message ID + - `conversation_id` (string) Conversation ID + - `inputs` (object) User input parameters. + - `query` (string) User input / question content. + - `message_files` (array[object]) Message files + - `id` (string) ID + - `type` (string) File type, image for images + - `url` (string) Preview image URL + - `belongs_to` (string) belongs to,user orassistant + - `answer` (string) Response message content + - `created_at` (timestamp) Creation timestamp, e.g., 1705395332 + - `feedback` (object) Feedback information + - `rating` (string) Upvote as `like` / Downvote as `dislike` + - `retriever_resources` (array[RetrieverResource]) Citation and Attribution List + - `has_more` (bool) Whether there is a next page + - `limit` (int) Number of returned items, if input exceeds system limit, returns system limit amount </Col> <Col sticky> @@ -765,10 +828,8 @@ Chat applications support session persistence, allowing previous chat history to </CodeGroup> <CodeGroup title="Response"> - ```json {{ title: 'Response' }} - { - "result": "success" - } + ```text {{ title: 'Response' }} + 204 No Content ``` </CodeGroup> </Col> @@ -845,6 +906,106 @@ Chat applications support session persistence, allowing previous chat history to --- +<Heading + url='/conversations/:conversation_id/variables' + method='GET' + title='Get Conversation Variables' + name='#conversation-variables' +/> +<Row> + <Col> + Retrieve variables from a specific conversation. This endpoint is useful for extracting structured data that was captured during the conversation. + + ### Path Parameters + + <Properties> + <Property name='conversation_id' type='string' key='conversation_id'> + The ID of the conversation to retrieve variables from. + </Property> + </Properties> + + ### Query Parameters + + <Properties> + <Property name='user' type='string' key='user'> + The user identifier, defined by the developer, must ensure uniqueness within the application + </Property> + <Property name='last_id' type='string' key='last_id'> + (Optional) The ID of the last record on the current page, default is null. + </Property> + <Property name='limit' type='int' key='limit'> + (Optional) How many records to return in one request, default is the most recent 20 entries. Maximum 100, minimum 1. + </Property> + </Properties> + + ### Response + + - `limit` (int) Number of items per page + - `has_more` (bool) Whether there is a next page + - `data` (array[object]) List of variables + - `id` (string) Variable ID + - `name` (string) Variable name + - `value_type` (string) Variable type (string, number, object, etc.) + - `value` (string) Variable value + - `description` (string) Variable description + - `created_at` (int) Creation timestamp + - `updated_at` (int) Last update timestamp + + ### Errors + - 404, `conversation_not_exists`, Conversation not found + + </Col> + <Col sticky> + + <CodeGroup title="Request" tag="GET" label="/conversations/:conversation_id/variables" targetCode={`curl -X GET '${props.appDetail.api_base_url}/conversations/{conversation_id}/variables?user=abc-123' \\\n--header 'Authorization: Bearer {api_key}'`}> + + ```bash {{ title: 'cURL' }} + curl -X GET '${props.appDetail.api_base_url}/conversations/{conversation_id}/variables?user=abc-123' \ + --header 'Authorization: Bearer {api_key}' + ``` + + </CodeGroup> + + <CodeGroup title="Request with variable name filter"> + ```bash {{ title: 'cURL' }} + curl -X GET '${props.appDetail.api_base_url}/conversations/{conversation_id}/variables?user=abc-123&variable_name=customer_name' \ + --header 'Authorization: Bearer {api_key}' + ``` + </CodeGroup> + + <CodeGroup title="Response"> + ```json {{ title: 'Response' }} + { + "limit": 100, + "has_more": false, + "data": [ + { + "id": "variable-uuid-1", + "name": "customer_name", + "value_type": "string", + "value": "John Doe", + "description": "Customer name extracted from the conversation", + "created_at": 1650000000000, + "updated_at": 1650000000000 + }, + { + "id": "variable-uuid-2", + "name": "order_details", + "value_type": "json", + "value": "{\"product\":\"Widget\",\"quantity\":5,\"price\":19.99}", + "description": "Order details from the customer", + "created_at": 1650000000000, + "updated_at": 1650000000000 + } + ] + } + ``` + </CodeGroup> + </Col> +</Row> + +--- + <Heading url='/audio-to-text' method='POST' @@ -962,6 +1123,8 @@ Chat applications support session persistence, allowing previous chat history to - `name` (string) application name - `description` (string) application description - `tags` (array[string]) application tags + - `mode` (string) application mode + - `author_name` (string) application author name </Col> <Col> <CodeGroup title="Request" tag="GET" label="/info" targetCode={`curl -X GET '${props.appDetail.api_base_url}/info' \\\n-H 'Authorization: Bearer {api_key}'`}> @@ -978,7 +1141,9 @@ Chat applications support session persistence, allowing previous chat history to "tags": [ "tag1", "tag2" - ] + ], + "mode": "advanced-chat", + "author_name": "Dify" } ``` </CodeGroup> @@ -1004,6 +1169,13 @@ Chat applications support session persistence, allowing previous chat history to - `enabled` (bool) Whether it is enabled - `speech_to_text` (object) Speech to text - `enabled` (bool) Whether it is enabled + - `text_to_speech` (object) Text to speech + - `enabled` (bool) Whether it is enabled + - `voice` (string) Voice type + - `language` (string) Language + - `autoPlay` (string) Auto play + - `enabled` Enabled + - `disabled` Disabled - `retriever_resource` (object) Citation and Attribution - `enabled` (bool) Whether it is enabled - `annotation_reply` (object) Annotation reply @@ -1059,6 +1231,12 @@ Chat applications support session persistence, allowing previous chat history to "speech_to_text": { "enabled": true }, + "text_to_speech": { + "enabled": true, + "voice": "sambert-zhinan-v1", + "language": "zh-Hans", + "autoPlay": "disabled" + }, "retriever_resource": { "enabled": true }, @@ -1144,6 +1322,64 @@ Chat applications support session persistence, allowing previous chat history to </Row> --- +<Heading + url='/site' + method='GET' + title='Get Application WebApp Settings' + name='#site' +/> +<Row> + <Col> + Used to get the WebApp settings of the application. + ### Response + - `title` (string) WebApp name + - `chat_color_theme` (string) Chat color theme, in hex format + - `chat_color_theme_inverted` (bool) Whether the chat color theme is inverted + - `icon_type` (string) Icon type, `emoji` - emoji, `image` - picture + - `icon` (string) Icon. If it's `emoji` type, it's an emoji symbol; if it's `image` type, it's an image URL + - `icon_background` (string) Background color in hex format + - `icon_url` (string) Icon URL + - `description` (string) Description + - `copyright` (string) Copyright information + - `privacy_policy` (string) Privacy policy link + - `custom_disclaimer` (string) Custom disclaimer + - `default_language` (string) Default language + - `show_workflow_steps` (bool) Whether to show workflow details + - `use_icon_as_answer_icon` (bool) Whether to replace 🤖 in chat with the WebApp icon + </Col> + <Col> + <CodeGroup title="Request" tag="POST" label="/meta" targetCode={`curl -X GET '${props.appDetail.api_base_url}/site' \\\n-H 'Authorization: Bearer {api_key}'`}> + ```bash {{ title: 'cURL' }} + curl -X GET '${props.appDetail.api_base_url}/site' \ + -H 'Authorization: Bearer {api_key}' + ``` + + </CodeGroup> + + <CodeGroup title="Response"> + ```json {{ title: 'Response' }} + { + "title": "My App", + "chat_color_theme": "#ff4a4a", + "chat_color_theme_inverted": false, + "icon_type": "emoji", + "icon": "😄", + "icon_background": "#FFEAD5", + "icon_url": null, + "description": "This is my app.", + "copyright": "all rights reserved", + "privacy_policy": "", + "custom_disclaimer": "All generated by AI", + "default_language": "en-US", + "show_workflow_steps": false, + "use_icon_as_answer_icon": false, + } + ``` + </CodeGroup> + </Col> +</Row> +___ + <Heading url='/apps/annotations' method='GET' @@ -1237,13 +1473,11 @@ Chat applications support session persistence, allowing previous chat history to <CodeGroup title="Response"> ```json {{ title: 'Response' }} { - { - "id": "69d48372-ad81-4c75-9c46-2ce197b4d402", - "question": "What is your name?", - "answer": "I am Dify.", - "hit_count": 0, - "created_at": 1735625869 - } + "id": "69d48372-ad81-4c75-9c46-2ce197b4d402", + "question": "What is your name?", + "answer": "I am Dify.", + "hit_count": 0, + "created_at": 1735625869 } ``` </CodeGroup> @@ -1277,10 +1511,10 @@ Chat applications support session persistence, allowing previous chat history to title="Request" tag="PUT" label="/apps/annotations/{annotation_id}" - targetCode={`curl --location --request POST '${props.apiBaseUrl}/apps/annotations/{annotation_id}' \\\n--header 'Authorization: Bearer {api_key}' \\\n--header 'Content-Type: application/json' \\\n--data-raw '{"question": "What is your name?","answer": "I am Dify."}'`} + targetCode={`curl --location --request PUT '${props.apiBaseUrl}/apps/annotations/{annotation_id}' \\\n--header 'Authorization: Bearer {api_key}' \\\n--header 'Content-Type: application/json' \\\n--data-raw '{"question": "What is your name?","answer": "I am Dify."}'`} > ```bash {{ title: 'cURL' }} - curl --location --request POST '${props.apiBaseUrl}/apps/annotations/{annotation_id}' \ + curl --location --request PUT '${props.apiBaseUrl}/apps/annotations/{annotation_id}' \ --header 'Authorization: Bearer {api_key}' \ --header 'Content-Type: application/json' \ --data-raw '{ @@ -1293,13 +1527,11 @@ Chat applications support session persistence, allowing previous chat history to <CodeGroup title="Response"> ```json {{ title: 'Response' }} { - { - "id": "69d48372-ad81-4c75-9c46-2ce197b4d402", - "question": "What is your name?", - "answer": "I am Dify.", - "hit_count": 0, - "created_at": 1735625869 - } + "id": "69d48372-ad81-4c75-9c46-2ce197b4d402", + "question": "What is your name?", + "answer": "I am Dify.", + "hit_count": 0, + "created_at": 1735625869 } ``` </CodeGroup> @@ -1336,8 +1568,8 @@ Chat applications support session persistence, allowing previous chat history to </CodeGroup> <CodeGroup title="Response"> - ```json {{ title: 'Response' }} - {"result": "success"} + ```text {{ title: 'Response' }} + 204 No Content ``` </CodeGroup> </Col> @@ -1357,10 +1589,10 @@ Chat applications support session persistence, allowing previous chat history to <Property name='action' type='string' key='action'> Action, can only be 'enable' or 'disable' </Property> - <Property name='embedding_model_provider' type='string' key='embedding_model_provider'> + <Property name='embedding_provider_name' type='string' key='embedding_provider_name'> Specified embedding model provider, must be set up in the system first, corresponding to the provider field(Optional) </Property> - <Property name='embedding_model' type='string' key='embedding_model'> + <Property name='embedding_model_name' type='string' key='embedding_model_name'> Specified embedding model, corresponding to the model field(Optional) </Property> <Property name='score_threshold' type='number' key='score_threshold'> diff --git a/web/app/components/develop/template/template_advanced_chat.ja.mdx b/web/app/components/develop/template/template_advanced_chat.ja.mdx index 0554f5caf3..535de3aa0f 100644 --- a/web/app/components/develop/template/template_advanced_chat.ja.mdx +++ b/web/app/components/develop/template/template_advanced_chat.ja.mdx @@ -1,12 +1,12 @@ import { CodeGroup } from '../code.tsx' import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from '../md.tsx' -# 高度なチャットアプリAPI +# 高度なチャットアプリ API -チャットアプリケーションはセッションの持続性をサポートしており、以前のチャット履歴を応答のコンテキストとして使用できます。これは、チャットボットやカスタマーサービスAIなどに適用できます。 +チャットアプリケーションはセッションの持続性をサポートしており、以前のチャット履歴を応答のコンテキストとして使用できます。これは、チャットボットやカスタマーサービス AI などに適用できます。 <div> - ### ベースURL + ### ベース URL <CodeGroup title="コード" targetCode={props.appDetail.api_base_url}> ```javascript ``` @@ -14,10 +14,10 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from ### 認証 - サービスAPIは`API-Key`認証を使用します。 - <i>**APIキーはサーバー側に保存し、クライアント側で共有または保存しないことを強くお勧めします。APIキーの漏洩は深刻な結果を招く可能性があります。**</i> + サービス API は `API-Key` 認証を使用します。 + <i>**API キーはサーバー側に保存し、クライアント側で共有または保存しないことを強くお勧めします。API キーの漏洩は深刻な結果を招く可能性があります。**</i> - すべてのAPIリクエストには、以下のように`Authorization`HTTPヘッダーにAPIキーを含めてください: + すべての API リクエストには、以下のように `Authorization`HTTP ヘッダーに API キーを含めてください: <CodeGroup title="コード"> ```javascript @@ -59,7 +59,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from </Property> <Property name='user' type='string' key='user'> ユーザー識別子、エンドユーザーの身元を定義するために使用され、統計のために使用されます。 - アプリケーション内で開発者によって一意に定義されるべきです。 + アプリケーション内で開発者によって一意に定義されるべきです。サービス API は WebApp によって作成された会話を共有しません。 </Property> <Property name='conversation_id' type='string' key='conversation_id'> 会話ID、以前のチャット記録に基づいて会話を続けるには、以前のメッセージのconversation_idを渡す必要があります。 @@ -324,35 +324,35 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from - `file` (File) 必須 アップロードするファイル。 - `user` (string) 必須 - ユーザー識別子、開発者のルールによって定義され、アプリケーション内で一意でなければなりません。 + ユーザー識別子、開発者のルールによって定義され、アプリケーション内で一意でなければなりません。サービス API は WebApp によって作成された会話を共有しません。 ### 応答 - アップロードが成功すると、サーバーはファイルのIDと関連情報を返します。 + アップロードが成功すると、サーバーはファイルの ID と関連情報を返します。 - `id` (uuid) ID - `name` (string) ファイル名 - `size` (int) ファイルサイズ(バイト) - `extension` (string) ファイル拡張子 - - `mime_type` (string) ファイルのMIMEタイプ + - `mime_type` (string) ファイルの MIME タイプ - `created_by` (uuid) エンドユーザーID - `created_at` (timestamp) 作成タイムスタンプ、例:1705395332 ### エラー - 400, `no_file_uploaded`, ファイルが提供されなければなりません - - 400, `too_many_files`, 現在は1つのファイルのみ受け付けます + - 400, `too_many_files`, 現在は 1 つのファイルのみ受け付けます - 400, `unsupported_preview`, ファイルはプレビューをサポートしていません - 400, `unsupported_estimate`, ファイルは推定をサポートしていません - 413, `file_too_large`, ファイルが大きすぎます - 415, `unsupported_file_type`, サポートされていない拡張子、現在はドキュメントファイルのみ受け付けます - - 503, `s3_connection_failed`, S3サービスに接続できません - - 503, `s3_permission_denied`, S3にファイルをアップロードする権限がありません - - 503, `s3_file_too_large`, ファイルがS3のサイズ制限を超えています + - 503, `s3_connection_failed`, S3 サービスに接続できません + - 503, `s3_permission_denied`, S3 にファイルをアップロードする権限がありません + - 503, `s3_file_too_large`, ファイルが S3 のサイズ制限を超えています - 500, 内部サーバーエラー </Col> <Col sticky> ### リクエスト例 - <CodeGroup title="リクエスト" tag="POST" label="/files/upload" targetCode={`curl -X POST '${props.appDetail.api_base_url}/files/upload' \\\n--header 'Authorization: Bearer {api_key}' \\\n--form 'file=@localfile;type=image/[png|jpeg|jpg|webp|gif] \\\n--form 'user=abc-123'`}> + <CodeGroup title="リクエスト" tag="POST" label="/files/upload" targetCode={`curl -X POST '${props.appDetail.api_base_url}/files/upload' \\\n--header 'Authorization: Bearer {api_key}' \\\n--form 'file=@localfile;type=image/[png|jpeg|jpg|webp|gif]' \\\n--form 'user=abc-123'`}> ```bash {{ title: 'cURL' }} curl -X POST '${props.appDetail.api_base_url}/files/upload' \ @@ -391,10 +391,10 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from <Col> ストリーミングモードでのみサポートされています。 ### パス - - `task_id` (string) タスクID、ストリーミングチャンクの返り値から取得できます + - `task_id` (string) タスク ID、ストリーミングチャンクの返り値から取得できます ### リクエストボディ - `user` (string) 必須 - ユーザー識別子、エンドユーザーの身元を定義するために使用され、送信メッセージインターフェースで渡されたユーザーと一致している必要があります。 + ユーザー識別子、エンドユーザーの身元を定義するために使用され、送信メッセージインターフェースで渡されたユーザーと一致している必要があります。サービス API は WebApp によって作成された会話を共有しません。 ### 応答 - `result` (string) 常に"success"を返します </Col> @@ -487,6 +487,70 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from --- +<Heading + url='/app/feedbacks' + method='GET' + title='アプリのメッセージの「いいね」とフィードバックを取得' + name='#app-feedbacks' +/> +<Row> + <Col> + アプリのエンドユーザーからのフィードバックや「いいね」を取得します。 + + ### クエリ + <Properties> + <Property name='page' type='string' key='page'> + (任意)ページ番号。デフォルト値:1 + </Property> + </Properties> + + <Properties> + <Property name='limit' type='string' key='limit'> + (任意)1ページあたりの件数。デフォルト値:20 + </Property> + </Properties> + + ### レスポンス + - `data` (リスト) このアプリの「いいね」とフィードバックの一覧を返します。 + </Col> + <Col sticky> + + <CodeGroup title="Request" tag="GET" label="/app/feedbacks" targetCode={`curl -X GET '${props.appDetail.api_base_url}/app/feedbacks?page=1&limit=20'`}> + + ```bash {{ title: 'cURL' }} + curl -X GET '${props.appDetail.api_base_url}/app/feedbacks?page=1&limit=20' \ + --header 'Authorization: Bearer {api_key}' \ + --header 'Content-Type: application/json' + ``` + + </CodeGroup> + + <CodeGroup title="Response"> + ```json {{ title: 'Response' }} + { + "data": [ + { + "id": "8c0fbed8-e2f9-49ff-9f0e-15a35bdd0e25", + "app_id": "f252d396-fe48-450e-94ec-e184218e7346", + "conversation_id": "2397604b-9deb-430e-b285-4726e51fd62d", + "message_id": "709c0b0f-0a96-4a4e-91a4-ec0889937b11", + "rating": "like", + "content": "message feedback information-3", + "from_source": "user", + "from_end_user_id": "74286412-9a1a-42c1-929c-01edb1d381d5", + "from_account_id": null, + "created_at": "2025-04-24T09:24:38", + "updated_at": "2025-04-24T09:24:38" + } + ] + } + ``` + </CodeGroup> + </Col> +</Row> +--- + + <Heading url='/messages/{message_id}/suggested' method='GET' @@ -572,22 +636,22 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from ### 応答 - `data` (array[object]) メッセージリスト - - `id` (string) メッセージID - - `conversation_id` (string) 会話ID - - `inputs` (object) ユーザー入力パラメータ。 - - `query` (string) ユーザー入力/質問内容。 - - `message_files` (array[object]) メッセージファイル - - `id` (string) ID - - `type` (string) ファイルタイプ、画像の場合はimage - - `url` (string) プレビュー画像URL - - `belongs_to` (string) 所属、userまたはassistant - - `answer` (string) 応答メッセージ内容 - - `created_at` (timestamp) 作成タイムスタンプ、例:1705395332 - - `feedback` (object) フィードバック情報 - - `rating` (string) アップボートは`like` / ダウンボートは`dislike` - - `retriever_resources` (array[RetrieverResource]) 引用と帰属リスト - - `has_more` (bool) 次のページがあるかどうか - - `limit` (int) 返された項目数、入力がシステム制限を超える場合、システム制限数を返します + - `id` (string) メッセージID + - `conversation_id` (string) 会話ID + - `inputs` (object) ユーザー入力パラメータ。 + - `query` (string) ユーザー入力/質問内容。 + - `message_files` (array[object]) メッセージファイル + - `id` (string) ID + - `type` (string) ファイルタイプ、画像の場合はimage + - `url` (string) プレビュー画像URL + - `belongs_to` (string) 所属、userまたはassistant + - `answer` (string) 応答メッセージ内容 + - `created_at` (timestamp) 作成タイムスタンプ、例:1705395332 + - `feedback` (object) フィードバック情報 + - `rating` (string) アップボートは`like` / ダウンボートは`dislike` + - `retriever_resources` (array[RetrieverResource]) 引用と帰属リスト + - `has_more` (bool) 次のページがあるかどうか + - `limit` (int) 返された項目数、入力がシステム制限を超える場合、システム制限数を返します </Col> <Col sticky> @@ -648,7 +712,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from /> <Row> <Col> - 現在のユーザーの会話リストを取得し、デフォルトで最新の20件を返します。 + 現在のユーザーの会話リストを取得し、デフォルトで最新の 20 件を返します。 ### クエリ @@ -764,10 +828,8 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from </CodeGroup> <CodeGroup title="応答"> - ```json {{ title: '応答' }} - { - "result": "success" - } + ```text {{ title: '応答' }} + 204 No Content ``` </CodeGroup> </Col> @@ -844,6 +906,106 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from --- +<Heading + url='/conversations/:conversation_id/variables' + method='GET' + title='会話変数の取得' + name='#conversation-variables' +/> +<Row> + <Col> + 特定の会話から変数を取得します。このエンドポイントは、会話中に取得された構造化データを抽出するのに役立ちます。 + + ### パスパラメータ + + <Properties> + <Property name='conversation_id' type='string' key='conversation_id'> + 変数を取得する会話のID。 + </Property> + </Properties> + + ### クエリパラメータ + + <Properties> + <Property name='user' type='string' key='user'> + ユーザー識別子。開発者によって定義されたルールに従い、アプリケーション内で一意である必要があります。 + </Property> + <Property name='last_id' type='string' key='last_id'> + (Optional)現在のページの最後の記録のID、デフォルトはnullです。 + </Property> + <Property name='limit' type='int' key='limit'> + (Optional)1回のリクエストで返す記録の数、デフォルトは最新の20件です。最大100、最小1。 + </Property> + </Properties> + + ### レスポンス + + - `limit` (int) ページごとのアイテム数 + - `has_more` (bool) さらにアイテムがあるかどうか + - `data` (array[object]) 変数のリスト + - `id` (string) 変数 ID + - `name` (string) 変数名 + - `value_type` (string) 変数タイプ(文字列、数値、真偽値など) + - `value` (string) 変数値 + - `description` (string) 変数の説明 + - `created_at` (int) 作成タイムスタンプ + - `updated_at` (int) 最終更新タイムスタンプ + + ### エラー + - 404, `conversation_not_exists`, 会話が見つかりません + + </Col> + <Col sticky> + + <CodeGroup title="Request" tag="GET" label="/conversations/:conversation_id/variables" targetCode={`curl -X GET '${props.appDetail.api_base_url}/conversations/{conversation_id}/variables?user=abc-123' \\\n--header 'Authorization: Bearer {api_key}'`}> + + ```bash {{ title: 'cURL' }} + curl -X GET '${props.appDetail.api_base_url}/conversations/{conversation_id}/variables?user=abc-123' \ + --header 'Authorization: Bearer {api_key}' + ``` + + </CodeGroup> + + <CodeGroup title="Request with variable name filter"> + ```bash {{ title: 'cURL' }} + curl -X GET '${props.appDetail.api_base_url}/conversations/{conversation_id}/variables?user=abc-123&variable_name=customer_name' \ + --header 'Authorization: Bearer {api_key}' + ``` + </CodeGroup> + + <CodeGroup title="Response"> + ```json {{ title: 'Response' }} + { + "limit": 100, + "has_more": false, + "data": [ + { + "id": "variable-uuid-1", + "name": "customer_name", + "value_type": "string", + "value": "John Doe", + "description": "会話から抽出された顧客名", + "created_at": 1650000000000, + "updated_at": 1650000000000 + }, + { + "id": "variable-uuid-2", + "name": "order_details", + "value_type": "json", + "value": "{\"product\":\"Widget\",\"quantity\":5,\"price\":19.99}", + "description": "顧客の注文詳細", + "created_at": 1650000000000, + "updated_at": 1650000000000 + } + ] + } + ``` + </CodeGroup> + </Col> +</Row> + +--- + <Heading url='/audio-to-text' method='POST' @@ -852,7 +1014,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from /> <Row> <Col> - このエンドポイントはmultipart/form-dataリクエストを必要とします。 + このエンドポイントは multipart/form-data リクエストを必要とします。 ### リクエストボディ @@ -961,6 +1123,8 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from - `name` (string) アプリケーションの名前 - `description` (string) アプリケーションの説明 - `tags` (array[string]) アプリケーションのタグ + - `mode` (string) アプリケーションのモード + - `author_name` (string) 作者の名前 </Col> <Col> <CodeGroup title="Request" tag="GET" label="/info" targetCode={`curl -X GET '${props.appDetail.api_base_url}/info' \\\n-H 'Authorization: Bearer {api_key}'`}> @@ -977,7 +1141,9 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from "tags": [ "tag1", "tag2" - ] + ], + "mode": "advanced-chat", + "author_name": "Dify" } ``` </CodeGroup> @@ -1003,6 +1169,13 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from - `enabled` (bool) 有効かどうか - `speech_to_text` (object) 音声からテキストへ - `enabled` (bool) 有効かどうか + - `text_to_speech` (object) テキストから音声へ + - `enabled` (bool) 有効かどうか + - `voice` (string) 音声タイプ + - `language` (string) 言語 + - `autoPlay` (string) 自動再生 + - `enabled` 有効 + - `disabled` 無効 - `retriever_resource` (object) 引用と帰属 - `enabled` (bool) 有効かどうか - `annotation_reply` (object) 注釈返信 @@ -1058,6 +1231,12 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from "speech_to_text": { "enabled": true }, + "text_to_speech": { + "enabled": true, + "voice": "sambert-zhinan-v1", + "language": "zh-Hans", + "autoPlay": "disabled" + }, "retriever_resource": { "enabled": true }, @@ -1113,9 +1292,9 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from - `tool_name` (string) - `icon` (object|string) - (object) アイコンオブジェクト - - `background` (string) 背景色(16進数形式) + - `background` (string) 背景色(16 進数形式) - `content`(string) 絵文字 - - (string) アイコンのURL + - (string) アイコンの URL </Col> <Col> <CodeGroup title="リクエスト" tag="GET" label="/meta" targetCode={`curl -X GET '${props.appDetail.api_base_url}/meta' \\\n-H 'Authorization: Bearer {api_key}'`}> @@ -1141,3 +1320,63 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from </CodeGroup> </Col> </Row> + +--- + +<Heading + url='/site' + method='GET' + title='アプリのWebApp設定を取得' + name='#site' +/> +<Row> + <Col> + アプリの WebApp 設定を取得するために使用します。 + ### 応答 + - `title` (string) WebApp 名 + - `chat_color_theme` (string) チャットの色テーマ、16 進数形式 + - `chat_color_theme_inverted` (bool) チャットの色テーマを反転するかどうか + - `icon_type` (string) アイコンタイプ、`emoji`-絵文字、`image`-画像 + - `icon` (string) アイコン。`emoji`タイプの場合は絵文字、`image`タイプの場合は画像 URL + - `icon_background` (string) 16 進数形式の背景色 + - `icon_url` (string) アイコンの URL + - `description` (string) 説明 + - `copyright` (string) 著作権情報 + - `privacy_policy` (string) プライバシーポリシーのリンク + - `custom_disclaimer` (string) カスタム免責事項 + - `default_language` (string) デフォルト言語 + - `show_workflow_steps` (bool) ワークフローの詳細を表示するかどうか + - `use_icon_as_answer_icon` (bool) WebApp のアイコンをチャット内の🤖に置き換えるかどうか + </Col> + <Col> + <CodeGroup title="Request" tag="POST" label="/meta" targetCode={`curl -X GET '${props.appDetail.api_base_url}/site' \\\n-H 'Authorization: Bearer {api_key}'`}> + ```bash {{ title: 'cURL' }} + curl -X GET '${props.appDetail.api_base_url}/site' \ + -H 'Authorization: Bearer {api_key}' + ``` + + </CodeGroup> + + <CodeGroup title="Response"> + ```json {{ title: 'Response' }} + { + "title": "My App", + "chat_color_theme": "#ff4a4a", + "chat_color_theme_inverted": false, + "icon_type": "emoji", + "icon": "😄", + "icon_background": "#FFEAD5", + "icon_url": null, + "description": "This is my app.", + "copyright": "all rights reserved", + "privacy_policy": "", + "custom_disclaimer": "All generated by AI", + "default_language": "en-US", + "show_workflow_steps": false, + "use_icon_as_answer_icon": false, + } + ``` + </CodeGroup> + </Col> +</Row> +___ diff --git a/web/app/components/develop/template/template_advanced_chat.zh.mdx b/web/app/components/develop/template/template_advanced_chat.zh.mdx index 7135cf6188..fa3ad3f37f 100755 --- a/web/app/components/develop/template/template_advanced_chat.zh.mdx +++ b/web/app/components/develop/template/template_advanced_chat.zh.mdx @@ -56,7 +56,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty } from '../md.tsx' </Property> <Property name='user' type='string' key='user'> 用户标识,用于定义终端用户的身份,方便检索、统计。 - 由开发者定义规则,需保证用户标识在应用内唯一。 + 由开发者定义规则,需保证用户标识在应用内唯一。服务 API 不会共享 WebApp 创建的对话。 </Property> <Property name='conversation_id' type='string' key='conversation_id'> (选填)会话 ID,需要基于之前的聊天记录继续对话,必须传之前消息的 conversation_id。 @@ -362,7 +362,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty } from '../md.tsx' </Col> <Col sticky> - <CodeGroup title="Request" tag="POST" label="/files/upload" targetCode={`curl -X POST '${props.appDetail.api_base_url}/files/upload' \\\n--header 'Authorization: Bearer {api_key}' \\\n--form 'file=@localfile;type=image/[png|jpeg|jpg|webp|gif] \\\n--form 'user=abc-123'`}> + <CodeGroup title="Request" tag="POST" label="/files/upload" targetCode={`curl -X POST '${props.appDetail.api_base_url}/files/upload' \\\n--header 'Authorization: Bearer {api_key}' \\\n--form 'file=@localfile;type=image/[png|jpeg|jpg|webp|gif]' \\\n--form 'user=abc-123'`}> ```bash {{ title: 'cURL' }} curl -X POST '${props.appDetail.api_base_url}/files/upload' \ @@ -402,7 +402,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty } from '../md.tsx' ### Request Body - `user` (string) Required - 用户标识,用于定义终端用户的身份,必须和发送消息接口传入 user 保持一致。 + 用户标识,用于定义终端用户的身份,必须和发送消息接口传入 user 保持一致。API 无法访问 WebApp 创建的会话。 ### Response - `result` (string) 固定返回 success </Col> @@ -493,6 +493,71 @@ import { Row, Col, Properties, Property, Heading, SubProperty } from '../md.tsx' --- +<Heading + url='/app/feedbacks' + method='GET' + title='获取APP的消息点赞和反馈' + name='#app-feedbacks' +/> +<Row> + <Col> + 获取应用的终端用户反馈、点赞。 + + ### Query + <Properties> + <Property name='page' type='string' key='page'> + (选填)分页,默认值:1 + </Property> + </Properties> + + <Properties> + <Property name='limit' type='string' key='limit'> + (选填)每页数量,默认值:20 + </Property> + </Properties> + + ### Response + - `data` (List) 返回该APP的点赞、反馈列表。 + </Col> + <Col sticky> + + <CodeGroup title="Request" tag="GET" label="/app/feedbacks" targetCode={`curl -X GET '${props.appDetail.api_base_url}/app/feedbacks?page=1&limit=20'`}> + + ```bash {{ title: 'cURL' }} + curl -X GET '${props.appDetail.api_base_url}/app/feedbacks?page=1&limit=20' \ + --header 'Authorization: Bearer {api_key}' \ + --header 'Content-Type: application/json' + ``` + + </CodeGroup> + + <CodeGroup title="Response"> + ```json {{ title: 'Response' }} + { + "data": [ + { + "id": "8c0fbed8-e2f9-49ff-9f0e-15a35bdd0e25", + "app_id": "f252d396-fe48-450e-94ec-e184218e7346", + "conversation_id": "2397604b-9deb-430e-b285-4726e51fd62d", + "message_id": "709c0b0f-0a96-4a4e-91a4-ec0889937b11", + "rating": "like", + "content": "message feedback information-3", + "from_source": "user", + "from_end_user_id": "74286412-9a1a-42c1-929c-01edb1d381d5", + "from_account_id": null, + "created_at": "2025-04-24T09:24:38", + "updated_at": "2025-04-24T09:24:38" + } + ] + } + + ``` + </CodeGroup> + </Col> +</Row> + +--- + <Heading url='/messages/{message_id}/suggested' method='GET' @@ -578,22 +643,22 @@ import { Row, Col, Properties, Property, Heading, SubProperty } from '../md.tsx' ### Response - `data` (array[object]) 消息列表 - - `id` (string) 消息 ID - - `conversation_id` (string) 会话 ID - - `inputs` (object) 用户输入参数。 - - `query` (string) 用户输入 / 提问内容。 - - `message_files` (array[object]) 消息文件 - - `id` (string) ID - - `type` (string) 文件类型,image 图片 - - `url` (string) 预览图片地址 - - `belongs_to` (string) 文件归属方,user 或 assistant - - `answer` (string) 回答消息内容 - - `created_at` (timestamp) 创建时间 - - `feedback` (object) 反馈信息 - - `rating` (string) 点赞 like / 点踩 dislike - - `retriever_resources` (array[RetrieverResource]) 引用和归属分段列表 - - `has_more` (bool) 是否存在下一页 - - `limit` (int) 返回条数,若传入超过系统限制,返回系统限制数量 + - `id` (string) 消息 ID + - `conversation_id` (string) 会话 ID + - `inputs` (object) 用户输入参数。 + - `query` (string) 用户输入 / 提问内容。 + - `message_files` (array[object]) 消息文件 + - `id` (string) ID + - `type` (string) 文件类型,image 图片 + - `url` (string) 预览图片地址 + - `belongs_to` (string) 文件归属方,user 或 assistant + - `answer` (string) 回答消息内容 + - `created_at` (timestamp) 创建时间 + - `feedback` (object) 反馈信息 + - `rating` (string) 点赞 like / 点踩 dislike + - `retriever_resources` (array[RetrieverResource]) 引用和归属分段列表 + - `has_more` (bool) 是否存在下一页 + - `limit` (int) 返回条数,若传入超过系统限制,返回系统限制数量 </Col> <Col sticky> ### Request Example @@ -799,10 +864,8 @@ import { Row, Col, Properties, Property, Heading, SubProperty } from '../md.tsx' </CodeGroup> <CodeGroup title="Response"> - ```json {{ title: 'Response' }} - { - "result": "success" - } + ```text {{ title: 'Response' }} + 204 No Content ``` </CodeGroup> </Col> @@ -881,6 +944,106 @@ import { Row, Col, Properties, Property, Heading, SubProperty } from '../md.tsx' --- +<Heading + url='/conversations/:conversation_id/variables' + method='GET' + title='获取对话变量' + name='#conversation-variables' +/> +<Row> + <Col> + 从特定对话中检索变量。此端点对于提取对话过程中捕获的结构化数据非常有用。 + + ### 路径参数 + + <Properties> + <Property name='conversation_id' type='string' key='conversation_id'> + 要从中检索变量的对话ID。 + </Property> + </Properties> + + ### 查询参数 + + <Properties> + <Property name='user' type='string' key='user'> + 用户标识符,由开发人员定义的规则,在应用程序内必须唯一。 + </Property> + <Property name='last_id' type='string' key='last_id'> + (选填)当前页最后面一条记录的 ID,默认 null + </Property> + <Property name='limit' type='int' key='limit'> + (选填)一次请求返回多少条记录,默认 20 条,最大 100 条,最小 1 条。 + </Property> + </Properties> + + ### 响应 + + - `limit` (int) 每页项目数 + - `has_more` (bool) 是否有更多项目 + - `data` (array[object]) 变量列表 + - `id` (string) 变量 ID + - `name` (string) 变量名称 + - `value_type` (string) 变量类型(字符串、数字、布尔等) + - `value` (string) 变量值 + - `description` (string) 变量描述 + - `created_at` (int) 创建时间戳 + - `updated_at` (int) 最后更新时间戳 + + ### 错误 + - 404, `conversation_not_exists`, 对话不存在 + + </Col> + <Col sticky> + + <CodeGroup title="Request" tag="GET" label="/conversations/:conversation_id/variables" targetCode={`curl -X GET '${props.appDetail.api_base_url}/conversations/{conversation_id}/variables?user=abc-123' \\\n--header 'Authorization: Bearer {api_key}'`}> + + ```bash {{ title: 'cURL' }} + curl -X GET '${props.appDetail.api_base_url}/conversations/{conversation_id}/variables?user=abc-123' \ + --header 'Authorization: Bearer {api_key}' + ``` + + </CodeGroup> + + <CodeGroup title="Request with variable name filter"> + ```bash {{ title: 'cURL' }} + curl -X GET '${props.appDetail.api_base_url}/conversations/{conversation_id}/variables?user=abc-123&variable_name=customer_name' \ + --header 'Authorization: Bearer {api_key}' + ``` + </CodeGroup> + + <CodeGroup title="Response"> + ```json {{ title: 'Response' }} + { + "limit": 100, + "has_more": false, + "data": [ + { + "id": "variable-uuid-1", + "name": "customer_name", + "value_type": "string", + "value": "John Doe", + "description": "客户名称(从对话中提取)", + "created_at": 1650000000000, + "updated_at": 1650000000000 + }, + { + "id": "variable-uuid-2", + "name": "order_details", + "value_type": "json", + "value": "{\"product\":\"Widget\",\"quantity\":5,\"price\":19.99}", + "description": "客户的订单详情", + "created_at": 1650000000000, + "updated_at": 1650000000000 + } + ] + } + ``` + </CodeGroup> + </Col> +</Row> + +--- + <Heading url='/audio-to-text' method='POST' @@ -1010,7 +1173,9 @@ import { Row, Col, Properties, Property, Heading, SubProperty } from '../md.tsx' "tags": [ "tag1", "tag2" - ] + ], + "mode": "advanced-chat", + "author_name": "Dify" } ``` </CodeGroup> @@ -1036,6 +1201,13 @@ import { Row, Col, Properties, Property, Heading, SubProperty } from '../md.tsx' - `enabled` (bool) 是否开启 - `speech_to_text` (object) 语音转文本 - `enabled` (bool) 是否开启 + - `text_to_speech` (object) 文本转语音 + - `enabled` (bool) 是否开启 + - `voice` (string) 语音类型 + - `language` (string) 语言 + - `autoPlay` (string) 自动播放 + - `enabled` 开启 + - `disabled` 关闭 - `retriever_resource` (object) 引用和归属 - `enabled` (bool) 是否开启 - `annotation_reply` (object) 标记回复 @@ -1130,15 +1302,15 @@ import { Row, Col, Properties, Property, Heading, SubProperty } from '../md.tsx' /> <Row> <Col> - 用于获取工具icon + 用于获取工具 icon ### Response - `tool_icons`(object[string]) 工具图标 - `工具名称` (string) - `icon` (object|string) - (object) 图标 - - `background` (string) hex格式的背景色 + - `background` (string) hex 格式的背景色 - `content`(string) emoji - - (string) 图标URL + - (string) 图标 URL </Col> <Col> <CodeGroup title="Request" tag="POST" label="/meta" targetCode={`curl -X GET '${props.appDetail.api_base_url}/meta' \\\n-H 'Authorization: Bearer {api_key}'`}> @@ -1166,7 +1338,63 @@ import { Row, Col, Properties, Property, Heading, SubProperty } from '../md.tsx' </Row> --- ---- +<Heading + url='/site' + method='GET' + title='获取应用 WebApp 设置' + name='#site' +/> +<Row> + <Col> + 用于获取应用的 WebApp 设置 + ### Response + - `title` (string) WebApp 名称 + - `chat_color_theme` (string) 聊天颜色主题,hex 格式 + - `chat_color_theme_inverted` (bool) 聊天颜色主题是否反转 + - `icon_type` (string) 图标类型,`emoji`-表情,`image`-图片 + - `icon` (string) 图标,如果是 `emoji` 类型,则是 emoji 表情符号,如果是 `image` 类型,则是图片 URL + - `icon_background` (string) hex 格式的背景色 + - `icon_url` (string) 图标 URL + - `description` (string) 描述 + - `copyright` (string) 版权信息 + - `privacy_policy` (string) 隐私政策链接 + - `custom_disclaimer` (string) 自定义免责声明 + - `default_language` (string) 默认语言 + - `show_workflow_steps` (bool) 是否显示工作流详情 + - `use_icon_as_answer_icon` (bool) 是否使用 WebApp 图标替换聊天中的 🤖 + </Col> + <Col> + <CodeGroup title="Request" tag="POST" label="/meta" targetCode={`curl -X GET '${props.appDetail.api_base_url}/site' \\\n-H 'Authorization: Bearer {api_key}'`}> + ```bash {{ title: 'cURL' }} + curl -X GET '${props.appDetail.api_base_url}/site' \ + -H 'Authorization: Bearer {api_key}' + ``` + + </CodeGroup> + + <CodeGroup title="Response"> + ```json {{ title: 'Response' }} + { + "title": "My App", + "chat_color_theme": "#ff4a4a", + "chat_color_theme_inverted": false, + "icon_type": "emoji", + "icon": "😄", + "icon_background": "#FFEAD5", + "icon_url": null, + "description": "This is my app.", + "copyright": "all rights reserved", + "privacy_policy": "", + "custom_disclaimer": "All generated by AI", + "default_language": "en-US", + "show_workflow_steps": false, + "use_icon_as_answer_icon": false, + } + ``` + </CodeGroup> + </Col> +</Row> +___ <Heading url='/apps/annotations' @@ -1261,13 +1489,11 @@ import { Row, Col, Properties, Property, Heading, SubProperty } from '../md.tsx' <CodeGroup title="Response"> ```json {{ title: 'Response' }} { - { - "id": "69d48372-ad81-4c75-9c46-2ce197b4d402", - "question": "What is your name?", - "answer": "I am Dify.", - "hit_count": 0, - "created_at": 1735625869 - } + "id": "69d48372-ad81-4c75-9c46-2ce197b4d402", + "question": "What is your name?", + "answer": "I am Dify.", + "hit_count": 0, + "created_at": 1735625869 } ``` </CodeGroup> @@ -1301,10 +1527,10 @@ import { Row, Col, Properties, Property, Heading, SubProperty } from '../md.tsx' title="Request" tag="PUT" label="/apps/annotations/{annotation_id}" - targetCode={`curl --location --request POST '${props.appDetail.api_base_url}/apps/annotations/{annotation_id}' \\\n--header 'Authorization: Bearer {api_key}' \\\n--header 'Content-Type: application/json' \\\n--data-raw '{"question": "What is your name?","answer": "I am Dify."}'`} + targetCode={`curl --location --request PUT '${props.appDetail.api_base_url}/apps/annotations/{annotation_id}' \\\n--header 'Authorization: Bearer {api_key}' \\\n--header 'Content-Type: application/json' \\\n--data-raw '{"question": "What is your name?","answer": "I am Dify."}'`} > ```bash {{ title: 'cURL' }} - curl --location --request POST '${props.appDetail.api_base_url}/apps/annotations/{annotation_id}' \ + curl --location --request PUT '${props.appDetail.api_base_url}/apps/annotations/{annotation_id}' \ --header 'Authorization: Bearer {api_key}' \ --header 'Content-Type: application/json' \ --data-raw '{ @@ -1317,13 +1543,11 @@ import { Row, Col, Properties, Property, Heading, SubProperty } from '../md.tsx' <CodeGroup title="Response"> ```json {{ title: 'Response' }} { - { - "id": "69d48372-ad81-4c75-9c46-2ce197b4d402", - "question": "What is your name?", - "answer": "I am Dify.", - "hit_count": 0, - "created_at": 1735625869 - } + "id": "69d48372-ad81-4c75-9c46-2ce197b4d402", + "question": "What is your name?", + "answer": "I am Dify.", + "hit_count": 0, + "created_at": 1735625869 } ``` </CodeGroup> @@ -1360,8 +1584,8 @@ import { Row, Col, Properties, Property, Heading, SubProperty } from '../md.tsx' </CodeGroup> <CodeGroup title="Response"> - ```json {{ title: 'Response' }} - {"result": "success"} + ```text {{ title: 'Response' }} + 204 No Content ``` </CodeGroup> </Col> @@ -1381,11 +1605,11 @@ import { Row, Col, Properties, Property, Heading, SubProperty } from '../md.tsx' <Property name='action' type='string' key='action'> 动作,只能是 'enable' 或 'disable' </Property> - <Property name='embedding_model_provider' type='string' key='embedding_model_provider'> - 指定的嵌入模型提供商, 必须先在系统内设定好接入的模型,对应的是provider字段 + <Property name='embedding_provider_name' type='string' key='embedding_provider_name'> + 指定的嵌入模型提供商,必须先在系统内设定好接入的模型,对应的是 provider 字段 </Property> - <Property name='embedding_model' type='string' key='embedding_model'> - 指定的嵌入模型,对应的是model字段 + <Property name='embedding_model_name' type='string' key='embedding_model_name'> + 指定的嵌入模型,对应的是 model 字段 </Property> <Property name='score_threshold' type='number' key='score_threshold'> 相似度阈值,当相似度大于该阈值时,系统会自动回复,否则不回复 @@ -1393,7 +1617,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty } from '../md.tsx' </Properties> </Col> <Col sticky> - 嵌入模型的提供商和模型名称可以通过以下接口获取:v1/workspaces/current/models/model-types/text-embedding, 具体见:通过 API 维护知识库。 使用的Authorization是Dataset的API Token。 + 嵌入模型的提供商和模型名称可以通过以下接口获取:v1/workspaces/current/models/model-types/text-embedding,具体见:通过 API 维护知识库。使用的 Authorization 是 Dataset 的 API Token。 <CodeGroup title="Request" tag="POST" diff --git a/web/app/components/develop/template/template_chat.en.mdx b/web/app/components/develop/template/template_chat.en.mdx index 3347d877fb..add80fc9ea 100644 --- a/web/app/components/develop/template/template_chat.en.mdx +++ b/web/app/components/develop/template/template_chat.en.mdx @@ -287,7 +287,7 @@ Chat applications support session persistence, allowing previous chat history to - `file` (File) Required The file to be uploaded. - `user` (string) Required - User identifier, defined by the developer's rules, must be unique within the application. + User identifier, defined by the developer's rules, must be unique within the application. The Service API does not share conversations created by the WebApp. ### Response After a successful upload, the server will return the file's ID and related information. @@ -315,7 +315,7 @@ Chat applications support session persistence, allowing previous chat history to </Col> <Col sticky> ### Request Example - <CodeGroup title="Request" tag="POST" label="/files/upload" targetCode={`curl -X POST '${props.appDetail.api_base_url}/files/upload' \\\n--header 'Authorization: Bearer {api_key}' \\\n--form 'file=@localfile;type=image/[png|jpeg|jpg|webp|gif] \\\n--form 'user=abc-123'`}> + <CodeGroup title="Request" tag="POST" label="/files/upload" targetCode={`curl -X POST '${props.appDetail.api_base_url}/files/upload' \\\n--header 'Authorization: Bearer {api_key}' \\\n--form 'file=@localfile;type=image/[png|jpeg|jpg|webp|gif]' \\\n--form 'user=abc-123'`}> ```bash {{ title: 'cURL' }} curl -X POST '${props.appDetail.api_base_url}/files/upload' \ @@ -357,7 +357,7 @@ Chat applications support session persistence, allowing previous chat history to - `task_id` (string) Task ID, can be obtained from the streaming chunk return ### Request Body - `user` (string) Required - User identifier, used to define the identity of the end-user, must be consistent with the user passed in the send message interface. + User identifier, used to define the identity of the end-user, must be consistent with the user passed in the message sending interface. The Service API does not share conversations created by the WebApp. ### Response - `result` (string) Always returns "success" </Col> @@ -450,6 +450,69 @@ Chat applications support session persistence, allowing previous chat history to --- +<Heading + url='/app/feedbacks' + method='GET' + title='Get feedbacks of application' + name='#app-feedbacks' +/> +<Row> + <Col> + Get application's feedbacks. + + ### Query + <Properties> + <Property name='page' type='string' key='page'> + (optional)pagination,default:1 + </Property> + </Properties> + + <Properties> + <Property name='limit' type='string' key='limit'> + (optional) records per page default:20 + </Property> + </Properties> + + ### Response + - `data` (List) return apps feedback list. + </Col> + <Col sticky> + + <CodeGroup title="Request" tag="GET" label="/app/feedbacks" targetCode={`curl -X GET '${props.appDetail.api_base_url}/app/feedbacks?page=1&limit=20'`}> + + ```bash {{ title: 'cURL' }} + curl -X GET '${props.appDetail.api_base_url}/app/feedbacks?page=1&limit=20' \ + --header 'Authorization: Bearer {api_key}' \ + --header 'Content-Type: application/json' + ``` + + </CodeGroup> + + <CodeGroup title="Response"> + ```json {{ title: 'Response' }} + { + "data": [ + { + "id": "8c0fbed8-e2f9-49ff-9f0e-15a35bdd0e25", + "app_id": "f252d396-fe48-450e-94ec-e184218e7346", + "conversation_id": "2397604b-9deb-430e-b285-4726e51fd62d", + "message_id": "709c0b0f-0a96-4a4e-91a4-ec0889937b11", + "rating": "like", + "content": "message feedback information-3", + "from_source": "user", + "from_end_user_id": "74286412-9a1a-42c1-929c-01edb1d381d5", + "from_account_id": null, + "created_at": "2025-04-24T09:24:38", + "updated_at": "2025-04-24T09:24:38" + } + ] + } + ``` + </CodeGroup> + </Col> +</Row> +--- + <Heading url='/messages/{message_id}/suggested' method='GET' @@ -535,33 +598,33 @@ Chat applications support session persistence, allowing previous chat history to ### Response - `data` (array[object]) Message list - - `id` (string) Message ID - - `conversation_id` (string) Conversation ID - - `inputs` (object) User input parameters. - - `query` (string) User input / question content. - - `message_files` (array[object]) Message files - - `id` (string) ID - - `type` (string) File type, image for images - - `url` (string) Preview image URL - - `belongs_to` (string) belongs to,user or assistant - - `agent_thoughts` (array[object]) Agent thought(Empty if it's a Basic Assistant) - - `id` (string) Agent thought ID, every iteration has a unique agent thought ID - - `message_id` (string) Unique message ID - - `position` (int) Position of current agent thought, each message may have multiple thoughts in order. - - `thought` (string) What LLM is thinking about - - `observation` (string) Response from tool calls - - `tool` (string) A list of tools represents which tools are called,split by ; - - `tool_input` (string) Input of tools in JSON format. Like: `{"dalle3": {"prompt": "a cute cat"}}`. - - `created_at` (int) Creation timestamp, e.g., 1705395332 - - `message_files` (array[string]) Refer to message_file event - - `file_id` (string) File ID - - `answer` (string) Response message content - - `created_at` (timestamp) Creation timestamp, e.g., 1705395332 - - `feedback` (object) Feedback information - - `rating` (string) Upvote as `like` / Downvote as `dislike` - - `retriever_resources` (array[RetrieverResource]) Citation and Attribution List - - `has_more` (bool) Whether there is a next page - - `limit` (int) Number of returned items, if input exceeds system limit, returns system limit amount + - `id` (string) Message ID + - `conversation_id` (string) Conversation ID + - `inputs` (object) User input parameters. + - `query` (string) User input / question content. + - `message_files` (array[object]) Message files + - `id` (string) ID + - `type` (string) File type, image for images + - `url` (string) Preview image URL + - `belongs_to` (string) belongs to,user or assistant + - `agent_thoughts` (array[object]) Agent thought(Empty if it's a Basic Assistant) + - `id` (string) Agent thought ID, every iteration has a unique agent thought ID + - `message_id` (string) Unique message ID + - `position` (int) Position of current agent thought, each message may have multiple thoughts in order. + - `thought` (string) What LLM is thinking about + - `observation` (string) Response from tool calls + - `tool` (string) A list of tools represents which tools are called,split by ; + - `tool_input` (string) Input of tools in JSON format. Like: `{"dalle3": {"prompt": "a cute cat"}}`. + - `created_at` (int) Creation timestamp, e.g., 1705395332 + - `message_files` (array[string]) Refer to message_file event + - `file_id` (string) File ID + - `answer` (string) Response message content + - `created_at` (timestamp) Creation timestamp, e.g., 1705395332 + - `feedback` (object) Feedback information + - `rating` (string) Upvote as `like` / Downvote as `dislike` + - `retriever_resources` (array[RetrieverResource]) Citation and Attribution List + - `has_more` (bool) Whether there is a next page + - `limit` (int) Number of returned items, if input exceeds system limit, returns system limit amount </Col> <Col sticky> @@ -798,10 +861,8 @@ Chat applications support session persistence, allowing previous chat history to </CodeGroup> <CodeGroup title="Response"> - ```json {{ title: 'Response' }} - { - "result": "success" - } + ```text {{ title: 'Response' }} + 204 No Content ``` </CodeGroup> </Col> @@ -878,6 +939,106 @@ Chat applications support session persistence, allowing previous chat history to --- +<Heading + url='/conversations/:conversation_id/variables' + method='GET' + title='Get Conversation Variables' + name='#conversation-variables' +/> +<Row> + <Col> + Retrieve variables from a specific conversation. This endpoint is useful for extracting structured data that was captured during the conversation. + + ### Path Parameters + + <Properties> + <Property name='conversation_id' type='string' key='conversation_id'> + The ID of the conversation to retrieve variables from. + </Property> + </Properties> + + ### Query Parameters + + <Properties> + <Property name='user' type='string' key='user'> + The user identifier, defined by the developer, must ensure uniqueness within the application + </Property> + <Property name='last_id' type='string' key='last_id'> + (Optional) The ID of the last record on the current page, default is null. + </Property> + <Property name='limit' type='int' key='limit'> + (Optional) How many records to return in one request, default is the most recent 20 entries. Maximum 100, minimum 1. + </Property> + </Properties> + + ### Response + + - `limit` (int) Number of items per page + - `has_more` (bool) Whether there is a next page + - `data` (array[object]) List of variables + - `id` (string) Variable ID + - `name` (string) Variable name + - `value_type` (string) Variable type (string, number, object, etc.) + - `value` (string) Variable value + - `description` (string) Variable description + - `created_at` (int) Creation timestamp + - `updated_at` (int) Last update timestamp + + ### Errors + - 404, `conversation_not_exists`, Conversation not found + + </Col> + <Col sticky> + + <CodeGroup title="Request" tag="GET" label="/conversations/:conversation_id/variables" targetCode={`curl -X GET '${props.appDetail.api_base_url}/conversations/{conversation_id}/variables?user=abc-123' \\\n--header 'Authorization: Bearer {api_key}'`}> + + ```bash {{ title: 'cURL' }} + curl -X GET '${props.appDetail.api_base_url}/conversations/{conversation_id}/variables?user=abc-123' \ + --header 'Authorization: Bearer {api_key}' + ``` + + </CodeGroup> + + <CodeGroup title="Request with variable name filter"> + ```bash {{ title: 'cURL' }} + curl -X GET '${props.appDetail.api_base_url}/conversations/{conversation_id}/variables?user=abc-123&variable_name=customer_name' \ + --header 'Authorization: Bearer {api_key}' + ``` + </CodeGroup> + + <CodeGroup title="Response"> + ```json {{ title: 'Response' }} + { + "limit": 100, + "has_more": false, + "data": [ + { + "id": "variable-uuid-1", + "name": "customer_name", + "value_type": "string", + "value": "John Doe", + "description": "Customer name extracted from the conversation", + "created_at": 1650000000000, + "updated_at": 1650000000000 + }, + { + "id": "variable-uuid-2", + "name": "order_details", + "value_type": "json", + "value": "{\"product\":\"Widget\",\"quantity\":5,\"price\":19.99}", + "description": "Order details from the customer", + "created_at": 1650000000000, + "updated_at": 1650000000000 + } + ] + } + ``` + </CodeGroup> + </Col> +</Row> + +--- + <Heading url='/audio-to-text' method='POST' @@ -990,6 +1151,8 @@ Chat applications support session persistence, allowing previous chat history to - `name` (string) application name - `description` (string) application description - `tags` (array[string]) application tags + - `mode` (string) application mode + - `author_name` (string) application author name </Col> <Col> <CodeGroup title="Request" tag="GET" label="/info" targetCode={`curl -X GET '${props.appDetail.api_base_url}/info' \\\n-H 'Authorization: Bearer {api_key}'`}> @@ -1006,7 +1169,9 @@ Chat applications support session persistence, allowing previous chat history to "tags": [ "tag1", "tag2" - ] + ], + "mode": "advanced-chat", + "author_name": "Dify" } ``` </CodeGroup> @@ -1040,6 +1205,13 @@ Chat applications support session persistence, allowing previous chat history to - `enabled` (bool) Whether it is enabled - `speech_to_text` (object) Speech to text - `enabled` (bool) Whether it is enabled + - `text_to_speech` (object) Text to speech + - `enabled` (bool) Whether it is enabled + - `voice` (string) Voice type + - `language` (string) Language + - `autoPlay` (string) Auto play + - `enabled` Enabled + - `disabled` Disabled - `retriever_resource` (object) Citation and Attribution - `enabled` (bool) Whether it is enabled - `annotation_reply` (object) Annotation reply @@ -1095,6 +1267,12 @@ Chat applications support session persistence, allowing previous chat history to "speech_to_text": { "enabled": true }, + "text_to_speech": { + "enabled": true, + "voice": "sambert-zhinan-v1", + "language": "zh-Hans", + "autoPlay": "disabled" + }, "retriever_resource": { "enabled": true }, @@ -1180,6 +1358,64 @@ Chat applications support session persistence, allowing previous chat history to </Row> --- +<Heading + url='/site' + method='GET' + title='Get Application WebApp Settings' + name='#site' +/> +<Row> + <Col> + Used to get the WebApp settings of the application. + ### Response + - `title` (string) WebApp name + - `chat_color_theme` (string) Chat color theme, in hex format + - `chat_color_theme_inverted` (bool) Whether the chat color theme is inverted + - `icon_type` (string) Icon type, `emoji` - emoji, `image` - picture + - `icon` (string) Icon. If it's `emoji` type, it's an emoji symbol; if it's `image` type, it's an image URL + - `icon_background` (string) Background color in hex format + - `icon_url` (string) Icon URL + - `description` (string) Description + - `copyright` (string) Copyright information + - `privacy_policy` (string) Privacy policy link + - `custom_disclaimer` (string) Custom disclaimer + - `default_language` (string) Default language + - `show_workflow_steps` (bool) Whether to show workflow details + - `use_icon_as_answer_icon` (bool) Whether to replace 🤖 in chat with the WebApp icon + </Col> + <Col> + <CodeGroup title="Request" tag="POST" label="/meta" targetCode={`curl -X GET '${props.appDetail.api_base_url}/site' \\\n-H 'Authorization: Bearer {api_key}'`}> + ```bash {{ title: 'cURL' }} + curl -X GET '${props.appDetail.api_base_url}/site' \ + -H 'Authorization: Bearer {api_key}' + ``` + + </CodeGroup> + + <CodeGroup title="Response"> + ```json {{ title: 'Response' }} + { + "title": "My App", + "chat_color_theme": "#ff4a4a", + "chat_color_theme_inverted": false, + "icon_type": "emoji", + "icon": "😄", + "icon_background": "#FFEAD5", + "icon_url": null, + "description": "This is my app.", + "copyright": "all rights reserved", + "privacy_policy": "", + "custom_disclaimer": "All generated by AI", + "default_language": "en-US", + "show_workflow_steps": false, + "use_icon_as_answer_icon": false, + } + ``` + </CodeGroup> + </Col> +</Row> +___ + <Heading url='/apps/annotations' method='GET' @@ -1372,8 +1608,8 @@ Chat applications support session persistence, allowing previous chat history to </CodeGroup> <CodeGroup title="Response"> - ```json {{ title: 'Response' }} - {"result": "success"} + ```text {{ title: 'Response' }} + 204 No Content ``` </CodeGroup> </Col> diff --git a/web/app/components/develop/template/template_chat.ja.mdx b/web/app/components/develop/template/template_chat.ja.mdx index 2fc1709775..8368326e40 100644 --- a/web/app/components/develop/template/template_chat.ja.mdx +++ b/web/app/components/develop/template/template_chat.ja.mdx @@ -1,12 +1,12 @@ import { CodeGroup } from '../code.tsx' import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from '../md.tsx' -# チャットアプリAPI +# チャットアプリ API -チャットアプリケーションはセッションの持続性をサポートしており、以前のチャット履歴を応答のコンテキストとして使用できます。これは、チャットボットやカスタマーサービスAIなどに適用できます。 +チャットアプリケーションはセッションの持続性をサポートしており、以前のチャット履歴を応答のコンテキストとして使用できます。これは、チャットボットやカスタマーサービス AI などに適用できます。 <div> - ### ベースURL + ### ベース URL <CodeGroup title="コード" targetCode={props.appDetail.api_base_url}> ```javascript ``` @@ -14,10 +14,10 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from ### 認証 - サービスAPIは`API-Key`認証を使用します。 - <i>**APIキーの漏洩を防ぐため、APIキーはクライアント側で共有または保存せず、サーバー側で保存することを強くお勧めします。**</i> + サービス API は `API-Key` 認証を使用します。 + <i>**API キーの漏洩を防ぐため、API キーはクライアント側で共有または保存せず、サーバー側で保存することを強くお勧めします。**</i> - すべてのAPIリクエストにおいて、以下のように`Authorization`HTTPヘッダーにAPIキーを含めてください: + すべての API リクエストにおいて、以下のように `Authorization`HTTP ヘッダーに API キーを含めてください: <CodeGroup title="コード"> ```javascript @@ -279,7 +279,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from <Row> <Col> メッセージ送信時に使用するためのファイルをアップロードします(現在は画像のみサポート)。画像とテキストのマルチモーダル理解を可能にします。 - png、jpg、jpeg、webp、gif形式をサポートしています。 + png、jpg、jpeg、webp、gif 形式をサポートしています。 アップロードされたファイルは現在のエンドユーザーのみが使用できます。 ### リクエストボディ @@ -287,35 +287,35 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from - `file` (File) 必須 アップロードするファイル。 - `user` (string) 必須 - ユーザー識別子、開発者のルールで定義され、アプリケーション内で一意でなければなりません。 + ユーザー識別子、開発者のルールで定義され、アプリケーション内で一意でなければなりません。サービス API は WebApp によって作成された会話を共有しません。 ### 応答 - アップロードが成功すると、サーバーはファイルのIDと関連情報を返します。 + アップロードが成功すると、サーバーはファイルの ID と関連情報を返します。 - `id` (uuid) ID - `name` (string) ファイル名 - `size` (int) ファイルサイズ(バイト) - `extension` (string) ファイル拡張子 - - `mime_type` (string) ファイルのMIMEタイプ + - `mime_type` (string) ファイルの MIME タイプ - `created_by` (uuid) エンドユーザーID - `created_at` (timestamp) 作成タイムスタンプ、例:1705395332 ### エラー - 400, `no_file_uploaded`, ファイルが提供されなければなりません - - 400, `too_many_files`, 現在は1つのファイルのみ受け付けます + - 400, `too_many_files`, 現在は 1 つのファイルのみ受け付けます - 400, `unsupported_preview`, ファイルはプレビューをサポートしていません - 400, `unsupported_estimate`, ファイルは推定をサポートしていません - 413, `file_too_large`, ファイルが大きすぎます - 415, `unsupported_file_type`, サポートされていない拡張子、現在はドキュメントファイルのみ受け付けます - - 503, `s3_connection_failed`, S3サービスに接続できません - - 503, `s3_permission_denied`, S3にファイルをアップロードする権限がありません - - 503, `s3_file_too_large`, ファイルがS3のサイズ制限を超えています + - 503, `s3_connection_failed`, S3 サービスに接続できません + - 503, `s3_permission_denied`, S3 にファイルをアップロードする権限がありません + - 503, `s3_file_too_large`, ファイルが S3 のサイズ制限を超えています - 500, 内部サーバーエラー </Col> <Col sticky> ### リクエスト例 - <CodeGroup title="リクエスト" tag="POST" label="/files/upload" targetCode={`curl -X POST '${props.appDetail.api_base_url}/files/upload' \\\n--header 'Authorization: Bearer {api_key}' \\\n--form 'file=@localfile;type=image/[png|jpeg|jpg|webp|gif] \\\n--form 'user=abc-123'`}> + <CodeGroup title="リクエスト" tag="POST" label="/files/upload" targetCode={`curl -X POST '${props.appDetail.api_base_url}/files/upload' \\\n--header 'Authorization: Bearer {api_key}' \\\n--form 'file=@localfile;type=image/[png|jpeg|jpg|webp|gif]' \\\n--form 'user=abc-123'`}> ```bash {{ title: 'cURL' }} curl -X POST '${props.appDetail.api_base_url}/files/upload' \ @@ -354,10 +354,10 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from <Col> ストリーミングモードでのみサポートされています。 ### パス - - `task_id` (string) タスクID、ストリーミングチャンクの返り値から取得できます + - `task_id` (string) タスク ID、ストリーミングチャンクの返り値から取得できます ### リクエストボディ - `user` (string) 必須 - ユーザー識別子、エンドユーザーのアイデンティティを定義するために使用され、メッセージ送信インターフェースで渡されたユーザーと一致している必要があります。 + ユーザー識別子、エンドユーザーのアイデンティティを定義するために使用され、メッセージ送信インターフェースで渡されたユーザーと一致している必要があります。サービス API は WebApp によって作成された会話を共有しません。 ### 応答 - `result` (string) 常に"success"を返します </Col> @@ -450,6 +450,70 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from --- +<Heading + url='/app/feedbacks' + method='GET' + title='アプリのメッセージの「いいね」とフィードバックを取得' + name='#app-feedbacks' +/> +<Row> + <Col> + アプリのエンドユーザーからのフィードバックや「いいね」を取得します。 + + ### クエリ + <Properties> + <Property name='page' type='string' key='page'> + (任意)ページ番号。デフォルト値:1 + </Property> + </Properties> + + <Properties> + <Property name='limit' type='string' key='limit'> + (任意)1ページあたりの件数。デフォルト値:20 + </Property> + </Properties> + + ### レスポンス + - `data` (リスト) このアプリの「いいね」とフィードバックの一覧を返します。 + </Col> + <Col sticky> + + <CodeGroup title="Request" tag="GET" label="/app/feedbacks" targetCode={`curl -X GET '${props.appDetail.api_base_url}/app/feedbacks?page=1&limit=20'`}> + + ```bash {{ title: 'cURL' }} + curl -X GET '${props.appDetail.api_base_url}/app/feedbacks?page=1&limit=20' \ + --header 'Authorization: Bearer {api_key}' \ + --header 'Content-Type: application/json' + ``` + + </CodeGroup> + + <CodeGroup title="Response"> + ```json {{ title: 'Response' }} + { + "data": [ + { + "id": "8c0fbed8-e2f9-49ff-9f0e-15a35bdd0e25", + "app_id": "f252d396-fe48-450e-94ec-e184218e7346", + "conversation_id": "2397604b-9deb-430e-b285-4726e51fd62d", + "message_id": "709c0b0f-0a96-4a4e-91a4-ec0889937b11", + "rating": "like", + "content": "message feedback information-3", + "from_source": "user", + "from_end_user_id": "74286412-9a1a-42c1-929c-01edb1d381d5", + "from_account_id": null, + "created_at": "2025-04-24T09:24:38", + "updated_at": "2025-04-24T09:24:38" + } + ] + } + ``` + </CodeGroup> + </Col> +</Row> +--- + + <Heading url='/messages/{message_id}/suggested' method='GET' @@ -535,33 +599,33 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from ### 応答 - `data` (array[object]) メッセージリスト - - `id` (string) メッセージID - - `conversation_id` (string) 会話ID - - `inputs` (object) ユーザー入力パラメータ。 - - `query` (string) ユーザー入力/質問内容。 - - `message_files` (array[object]) メッセージファイル - - `id` (string) ID - - `type` (string) ファイルタイプ、画像の場合はimage - - `url` (string) プレビュー画像URL - - `belongs_to` (string) 所属、ユーザーまたはアシスタント - - `agent_thoughts` (array[object]) エージェントの思考(基本アシスタントの場合は空) - - `id` (string) エージェント思考ID、各反復には一意のエージェント思考IDがあります - - `message_id` (string) 一意のメッセージID - - `position` (int) 現在のエージェント思考の位置、各メッセージには順番に複数の思考が含まれる場合があります。 - - `thought` (string) LLMが考えていること - - `observation` (string) ツール呼び出しからの応答 - - `tool` (string) 呼び出されたツールのリスト、;で区切られます - - `tool_input` (string) ツールの入力、JSON形式。例:`{"dalle3": {"prompt": "a cute cat"}}`。 - - `created_at` (int) 作成タイムスタンプ、例:1705395332 - - `message_files` (array[string]) message_fileイベントを参照 - - `file_id` (string) ファイルID - - `answer` (string) 応答メッセージ内容 - - `created_at` (timestamp) 作成タイムスタンプ、例:1705395332 - - `feedback` (object) フィードバック情報 - - `rating` (string) アップボートは`like` / ダウンボートは`dislike` - - `retriever_resources` (array[RetrieverResource]) 引用と帰属リスト - - `has_more` (bool) 次のページがあるかどうか - - `limit` (int) 返されたアイテムの数、入力がシステム制限を超える場合、システム制限の数を返します + - `id` (string) メッセージID + - `conversation_id` (string) 会話ID + - `inputs` (object) ユーザー入力パラメータ。 + - `query` (string) ユーザー入力/質問内容。 + - `message_files` (array[object]) メッセージファイル + - `id` (string) ID + - `type` (string) ファイルタイプ、画像の場合はimage + - `url` (string) プレビュー画像URL + - `belongs_to` (string) 所属、ユーザーまたはアシスタント + - `agent_thoughts` (array[object]) エージェントの思考(基本アシスタントの場合は空) + - `id` (string) エージェント思考ID、各反復には一意のエージェント思考IDがあります + - `message_id` (string) 一意のメッセージID + - `position` (int) 現在のエージェント思考の位置、各メッセージには順番に複数の思考が含まれる場合があります。 + - `thought` (string) LLMが考えていること + - `observation` (string) ツール呼び出しからの応答 + - `tool` (string) 呼び出されたツールのリスト、;で区切られます + - `tool_input` (string) ツールの入力、JSON形式。例:`{"dalle3": {"prompt": "a cute cat"}}`。 + - `created_at` (int) 作成タイムスタンプ、例:1705395332 + - `message_files` (array[string]) message_fileイベントを参照 + - `file_id` (string) ファイルID + - `answer` (string) 応答メッセージ内容 + - `created_at` (timestamp) 作成タイムスタンプ、例:1705395332 + - `feedback` (object) フィードバック情報 + - `rating` (string) アップボートは`like` / ダウンボートは`dislike` + - `retriever_resources` (array[RetrieverResource]) 引用と帰属リスト + - `has_more` (bool) 次のページがあるかどうか + - `limit` (int) 返されたアイテムの数、入力がシステム制限を超える場合、システム制限の数を返します </Col> <Col sticky> @@ -681,7 +745,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from /> <Row> <Col> - 現在のユーザーの会話リストを取得し、デフォルトで最新の20件を返します。 + 現在のユーザーの会話リストを取得し、デフォルトで最新の 20 件を返します。 ### クエリ @@ -797,10 +861,8 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from </CodeGroup> <CodeGroup title="応答"> - ```json {{ title: '応答' }} - { - "result": "success" - } + ```text {{ title: '応答' }} + 204 No Content ``` </CodeGroup> </Col> @@ -876,6 +938,106 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from --- +<Heading + url='/conversations/:conversation_id/variables' + method='GET' + title='会話変数の取得' + name='#conversation-variables' +/> +<Row> + <Col> + 特定の会話から変数を取得します。このエンドポイントは、会話中に取得された構造化データを抽出するのに役立ちます。 + + ### パスパラメータ + + <Properties> + <Property name='conversation_id' type='string' key='conversation_id'> + 変数を取得する会話のID。 + </Property> + </Properties> + + ### クエリパラメータ + + <Properties> + <Property name='user' type='string' key='user'> + ユーザー識別子。開発者によって定義されたルールに従い、アプリケーション内で一意である必要があります。 + </Property> + <Property name='last_id' type='string' key='last_id'> + (Optional)現在のページの最後のレコードのID、デフォルトはnullです。 + </Property> + <Property name='limit' type='int' key='limit'> + (Optional)1回のリクエストで返すレコードの数、デフォルトは最新の20件です。最大100、最小1。 + </Property> + </Properties> + + ### レスポンス + + - `limit` (int) ページごとのアイテム数 + - `has_more` (bool) さらにアイテムがあるかどうか + - `data` (array[object]) 変数のリスト + - `id` (string) 変数 ID + - `name` (string) 変数名 + - `value_type` (string) 変数タイプ(文字列、数値、真偽値など) + - `value` (string) 変数値 + - `description` (string) 変数の説明 + - `created_at` (int) 作成タイムスタンプ + - `updated_at` (int) 最終更新タイムスタンプ + + ### エラー + - 404, `conversation_not_exists`, 会話が見つかりません + + </Col> + <Col sticky> + + <CodeGroup title="Request" tag="GET" label="/conversations/:conversation_id/variables" targetCode={`curl -X GET '${props.appDetail.api_base_url}/conversations/{conversation_id}/variables?user=abc-123' \\\n--header 'Authorization: Bearer {api_key}'`}> + + ```bash {{ title: 'cURL' }} + curl -X GET '${props.appDetail.api_base_url}/conversations/{conversation_id}/variables?user=abc-123' \ + --header 'Authorization: Bearer {api_key}' + ``` + + </CodeGroup> + + <CodeGroup title="Request with variable name filter"> + ```bash {{ title: 'cURL' }} + curl -X GET '${props.appDetail.api_base_url}/conversations/{conversation_id}/variables?user=abc-123&variable_name=customer_name' \ + --header 'Authorization: Bearer {api_key}' + ``` + </CodeGroup> + + <CodeGroup title="Response"> + ```json {{ title: 'Response' }} + { + "limit": 100, + "has_more": false, + "data": [ + { + "id": "variable-uuid-1", + "name": "customer_name", + "value_type": "string", + "value": "John Doe", + "description": "会話から抽出された顧客名", + "created_at": 1650000000000, + "updated_at": 1650000000000 + }, + { + "id": "variable-uuid-2", + "name": "order_details", + "value_type": "json", + "value": "{\"product\":\"Widget\",\"quantity\":5,\"price\":19.99}", + "description": "顧客の注文詳細", + "created_at": 1650000000000, + "updated_at": 1650000000000 + } + ] + } + ``` + </CodeGroup> + </Col> +</Row> + +--- + <Heading url='/audio-to-text' method='POST' @@ -884,7 +1046,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from /> <Row> <Col> - このエンドポイントはmultipart/form-dataリクエストを必要とします。 + このエンドポイントは multipart/form-data リクエストを必要とします。 ### リクエストボディ @@ -988,6 +1150,8 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from - `name` (string) アプリケーションの名前 - `description` (string) アプリケーションの説明 - `tags` (array[string]) アプリケーションのタグ + - `mode` (string) アプリケーションのモード + - `author_name` (string) 作者の名前 </Col> <Col> <CodeGroup title="Request" tag="GET" label="/info" targetCode={`curl -X GET '${props.appDetail.api_base_url}/info' \\\n-H 'Authorization: Bearer {api_key}'`}> @@ -1004,7 +1168,9 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from "tags": [ "tag1", "tag2" - ] + ], + "mode": "chat", + "author_name": "Dify" } ``` </CodeGroup> @@ -1030,6 +1196,13 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from - `enabled` (bool) 有効かどうか - `speech_to_text` (object) 音声からテキストへ - `enabled` (bool) 有効かどうか + - `text_to_speech` (object) テキストから音声へ + - `enabled` (bool) 有効かどうか + - `voice` (string) 音声タイプ + - `language` (string) 言語 + - `autoPlay` (string) 自動再生 + - `enabled` 有効 + - `disabled` 無効 - `retriever_resource` (object) 引用と帰属 - `enabled` (bool) 有効かどうか - `annotation_reply` (object) 注釈返信 @@ -1085,6 +1258,12 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from "speech_to_text": { "enabled": true }, + "text_to_speech": { + "enabled": true, + "voice": "sambert-zhinan-v1", + "language": "zh-Hans", + "autoPlay": "disabled" + }, "retriever_resource": { "enabled": true }, @@ -1140,9 +1319,9 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from - `tool_name` (string) - `icon` (object|string) - (object) アイコンオブジェクト - - `background` (string) 背景色(16進数形式) + - `background` (string) 背景色(16 進数形式) - `content`(string) 絵文字 - - (string) アイコンのURL + - (string) アイコンの URL </Col> <Col> <CodeGroup title="リクエスト" tag="GET" label="/meta" targetCode={`curl -X GET '${props.appDetail.api_base_url}/meta' \\\n-H 'Authorization: Bearer {api_key}'`}> @@ -1168,3 +1347,63 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from </CodeGroup> </Col> </Row> + +--- + +<Heading + url='/site' + method='GET' + title='アプリのWebApp設定を取得' + name='#site' +/> +<Row> + <Col> + アプリの WebApp 設定を取得するために使用します。 + ### 応答 + - `title` (string) WebApp 名 + - `chat_color_theme` (string) チャットの色テーマ、16 進数形式 + - `chat_color_theme_inverted` (bool) チャットの色テーマを反転するかどうか + - `icon_type` (string) アイコンタイプ、`emoji`-絵文字、`image`-画像 + - `icon` (string) アイコン。`emoji`タイプの場合は絵文字、`image`タイプの場合は画像 URL + - `icon_background` (string) 16 進数形式の背景色 + - `icon_url` (string) アイコンの URL + - `description` (string) 説明 + - `copyright` (string) 著作権情報 + - `privacy_policy` (string) プライバシーポリシーのリンク + - `custom_disclaimer` (string) カスタム免責事項 + - `default_language` (string) デフォルト言語 + - `show_workflow_steps` (bool) ワークフローの詳細を表示するかどうか + - `use_icon_as_answer_icon` (bool) WebApp のアイコンをチャット内の🤖に置き換えるかどうか + </Col> + <Col> + <CodeGroup title="Request" tag="POST" label="/meta" targetCode={`curl -X GET '${props.appDetail.api_base_url}/site' \\\n-H 'Authorization: Bearer {api_key}'`}> + ```bash {{ title: 'cURL' }} + curl -X GET '${props.appDetail.api_base_url}/site' \ + -H 'Authorization: Bearer {api_key}' + ``` + + </CodeGroup> + + <CodeGroup title="Response"> + ```json {{ title: 'Response' }} + { + "title": "My App", + "chat_color_theme": "#ff4a4a", + "chat_color_theme_inverted": false, + "icon_type": "emoji", + "icon": "😄", + "icon_background": "#FFEAD5", + "icon_url": null, + "description": "This is my app.", + "copyright": "all rights reserved", + "privacy_policy": "", + "custom_disclaimer": "All generated by AI", + "default_language": "en-US", + "show_workflow_steps": false, + "use_icon_as_answer_icon": false, + } + ``` + </CodeGroup> + </Col> +</Row> +___ diff --git a/web/app/components/develop/template/template_chat.zh.mdx b/web/app/components/develop/template/template_chat.zh.mdx index e6f49a56d9..325470ac62 100644 --- a/web/app/components/develop/template/template_chat.zh.mdx +++ b/web/app/components/develop/template/template_chat.zh.mdx @@ -56,7 +56,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty } from '../md.tsx' </Property> <Property name='user' type='string' key='user'> 用户标识,用于定义终端用户的身份,方便检索、统计。 - 由开发者定义规则,需保证用户标识在应用内唯一。 + 由开发者定义规则,需保证用户标识在应用内唯一。服务 API 不会共享 WebApp 创建的对话。 </Property> <Property name='conversation_id' type='string' key='conversation_id'> (选填)会话 ID,需要基于之前的聊天记录继续对话,必须传之前消息的 conversation_id。 @@ -306,7 +306,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty } from '../md.tsx' 要上传的文件。 </Property> <Property name='user' type='string' key='user'> - 用户标识,用于定义终端用户的身份,必须和发送消息接口传入 user 保持一致。 + 用户标识,用于定义终端用户的身份,必须和发送消息接口传入 user 保持一致。服务 API 不会共享 WebApp 创建的对话。 </Property> </Properties> @@ -333,7 +333,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty } from '../md.tsx' </Col> <Col sticky> - <CodeGroup title="Request" tag="POST" label="/files/upload" targetCode={`curl -X POST '${props.appDetail.api_base_url}/files/upload' \\\n--header 'Authorization: Bearer {api_key}' \\\n--form 'file=@localfile;type=image/[png|jpeg|jpg|webp|gif] \\\n--form 'user=abc-123'`}> + <CodeGroup title="Request" tag="POST" label="/files/upload" targetCode={`curl -X POST '${props.appDetail.api_base_url}/files/upload' \\\n--header 'Authorization: Bearer {api_key}' \\\n--form 'file=@localfile;type=image/[png|jpeg|jpg|webp|gif]' \\\n--form 'user=abc-123'`}> ```bash {{ title: 'cURL' }} curl -X POST '${props.appDetail.api_base_url}/files/upload' \ @@ -373,7 +373,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty } from '../md.tsx' ### Request Body - `user` (string) Required - 用户标识,用于定义终端用户的身份,必须和发送消息接口传入 user 保持一致。 + 用户标识,用于定义终端用户的身份,必须和发送消息接口传入 user 保持一致。API 无法访问 WebApp 创建的会话。 ### Response - `result` (string) 固定返回 success </Col> @@ -425,7 +425,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty } from '../md.tsx' 点赞 like, 点踩 dislike, 撤销点赞 null </Property> <Property name='user' type='string' key='user'> - 用户标识,由开发者定义规则,需保证用户标识在应用内唯一。 + 用户标识,由开发者定义规则,需保证用户标识在应用内唯一。服务 API 不会共享 WebApp 创建的对话。 </Property> <Property name='content' type='string' key='content'> 消息反馈的具体信息。 @@ -464,6 +464,69 @@ import { Row, Col, Properties, Property, Heading, SubProperty } from '../md.tsx' --- +<Heading + url='/app/feedbacks' + method='GET' + title='获取APP的消息点赞和反馈' + name='#app-feedbacks' +/> +<Row> + <Col> + 获取应用的终端用户反馈、点赞。 + + ### Query + <Properties> + <Property name='page' type='string' key='page'> + (选填)分页,默认值:1 + </Property> + </Properties> + + <Properties> + <Property name='limit' type='string' key='limit'> + (选填)每页数量,默认值:20 + </Property> + </Properties> + + ### Response + - `data` (List) 返回该APP的点赞、反馈列表。 + </Col> + <Col sticky> + + <CodeGroup title="Request" tag="GET" label="/app/feedbacks" targetCode={`curl -X GET '${props.appDetail.api_base_url}/app/feedbacks?page=1&limit=20'`}> + + ```bash {{ title: 'cURL' }} + curl -X GET '${props.appDetail.api_base_url}/app/feedbacks?page=1&limit=20' \ + --header 'Authorization: Bearer {api_key}' \ + --header 'Content-Type: application/json' + ``` + + </CodeGroup> + + <CodeGroup title="Response"> + ```json {{ title: 'Response' }} + { + "data": [ + { + "id": "8c0fbed8-e2f9-49ff-9f0e-15a35bdd0e25", + "app_id": "f252d396-fe48-450e-94ec-e184218e7346", + "conversation_id": "2397604b-9deb-430e-b285-4726e51fd62d", + "message_id": "709c0b0f-0a96-4a4e-91a4-ec0889937b11", + "rating": "like", + "content": "message feedback information-3", + "from_source": "user", + "from_end_user_id": "74286412-9a1a-42c1-929c-01edb1d381d5", + "from_account_id": null, + "created_at": "2025-04-24T09:24:38", + "updated_at": "2025-04-24T09:24:38" + } + ] + } + ``` + </CodeGroup> + </Col> +</Row> +--- + <Heading url='/messages/{message_id}/suggested' method='GET' @@ -549,34 +612,34 @@ import { Row, Col, Properties, Property, Heading, SubProperty } from '../md.tsx' ### Response - `data` (array[object]) 消息列表 - - `id` (string) 消息 ID - - `conversation_id` (string) 会话 ID - - `inputs` (object) 用户输入参数。 - - `query` (string) 用户输入 / 提问内容。 - - `message_files` (array[object]) 消息文件 - - `id` (string) ID - - `type` (string) 文件类型,image 图片 - - `url` (string) 预览图片地址 - - `belongs_to` (string) 文件归属方,user 或 assistant - - `agent_thoughts` (array[object]) Agent思考内容(仅Agent模式下不为空) - - `id` (string) agent_thought ID,每一轮Agent迭代都会有一个唯一的id - - `message_id` (string) 消息唯一ID - - `position` (int) agent_thought在消息中的位置,如第一轮迭代position为1 - - `thought` (string) agent的思考内容 - - `observation` (string) 工具调用的返回结果 - - `tool` (string) 使用的工具列表,以 ; 分割多个工具 - - `tool_input` (string) 工具的输入,JSON格式的字符串(object)。如:`{"dalle3": {"prompt": "a cute cat"}}` - - `created_at` (int) 创建时间戳,如:1705395332 - - `message_files` (array[string]) 当前agent_thought 关联的文件ID - - `file_id` (string) 文件ID - - `conversation_id` (string) 会话ID - - `answer` (string) 回答消息内容 - - `created_at` (timestamp) 创建时间 - - `feedback` (object) 反馈信息 - - `rating` (string) 点赞 like / 点踩 dislike - - `retriever_resources` (array[RetrieverResource]) 引用和归属分段列表 - - `has_more` (bool) 是否存在下一页 - - `limit` (int) 返回条数,若传入超过系统限制,返回系统限制数量 + - `id` (string) 消息 ID + - `conversation_id` (string) 会话 ID + - `inputs` (object) 用户输入参数。 + - `query` (string) 用户输入 / 提问内容。 + - `message_files` (array[object]) 消息文件 + - `id` (string) ID + - `type` (string) 文件类型,image 图片 + - `url` (string) 预览图片地址 + - `belongs_to` (string) 文件归属方,user 或 assistant + - `agent_thoughts` (array[object]) Agent思考内容(仅Agent模式下不为空) + - `id` (string) agent_thought ID,每一轮Agent迭代都会有一个唯一的id + - `message_id` (string) 消息唯一ID + - `position` (int) agent_thought在消息中的位置,如第一轮迭代position为1 + - `thought` (string) agent的思考内容 + - `observation` (string) 工具调用的返回结果 + - `tool` (string) 使用的工具列表,以 ; 分割多个工具 + - `tool_input` (string) 工具的输入,JSON格式的字符串(object)。如:`{"dalle3": {"prompt": "a cute cat"}}` + - `created_at` (int) 创建时间戳,如:1705395332 + - `message_files` (array[string]) 当前agent_thought 关联的文件ID + - `file_id` (string) 文件ID + - `conversation_id` (string) 会话ID + - `answer` (string) 回答消息内容 + - `created_at` (timestamp) 创建时间 + - `feedback` (object) 反馈信息 + - `rating` (string) 点赞 like / 点踩 dislike + - `retriever_resources` (array[RetrieverResource]) 引用和归属分段列表 + - `has_more` (bool) 是否存在下一页 + - `limit` (int) 返回条数,若传入超过系统限制,返回系统限制数量 </Col> <Col sticky> ### Request Example @@ -811,10 +874,8 @@ import { Row, Col, Properties, Property, Heading, SubProperty } from '../md.tsx' </CodeGroup> <CodeGroup title="Response"> - ```json {{ title: 'Response' }} - { - "result": "success" - } + ```text {{ title: 'Response' }} + 204 No Content ``` </CodeGroup> </Col> @@ -893,6 +954,106 @@ import { Row, Col, Properties, Property, Heading, SubProperty } from '../md.tsx' --- +<Heading + url='/conversations/:conversation_id/variables' + method='GET' + title='获取对话变量' + name='#conversation-variables' +/> +<Row> + <Col> + 从特定对话中检索变量。此端点对于提取对话过程中捕获的结构化数据非常有用。 + + ### 路径参数 + + <Properties> + <Property name='conversation_id' type='string' key='conversation_id'> + 要从中检索变量的对话ID。 + </Property> + </Properties> + + ### 查询参数 + + <Properties> + <Property name='user' type='string' key='user'> + 用户标识符,由开发人员定义的规则,在应用程序内必须唯一。 + </Property> + <Property name='last_id' type='string' key='last_id'> + (选填)当前页最后面一条记录的 ID,默认 null + </Property> + <Property name='limit' type='int' key='limit'> + (选填)一次请求返回多少条记录,默认 20 条,最大 100 条,最小 1 条。 + </Property> + </Properties> + + ### 响应 + + - `limit` (int) 每页项目数 + - `has_more` (bool) 是否有更多项目 + - `data` (array[object]) 变量列表 + - `id` (string) 变量 ID + - `name` (string) 变量名称 + - `value_type` (string) 变量类型(字符串、数字、布尔等) + - `value` (string) 变量值 + - `description` (string) 变量描述 + - `created_at` (int) 创建时间戳 + - `updated_at` (int) 最后更新时间戳 + + ### 错误 + - 404, `conversation_not_exists`, 对话不存在 + + </Col> + <Col sticky> + + <CodeGroup title="Request" tag="GET" label="/conversations/:conversation_id/variables" targetCode={`curl -X GET '${props.appDetail.api_base_url}/conversations/{conversation_id}/variables?user=abc-123' \\\n--header 'Authorization: Bearer {api_key}'`}> + + ```bash {{ title: 'cURL' }} + curl -X GET '${props.appDetail.api_base_url}/conversations/{conversation_id}/variables?user=abc-123' \ + --header 'Authorization: Bearer {api_key}' + ``` + + </CodeGroup> + + <CodeGroup title="Request with variable name filter"> + ```bash {{ title: 'cURL' }} + curl -X GET '${props.appDetail.api_base_url}/conversations/{conversation_id}/variables?user=abc-123&variable_name=customer_name' \ + --header 'Authorization: Bearer {api_key}' + ``` + </CodeGroup> + + <CodeGroup title="Response"> + ```json {{ title: 'Response' }} + { + "limit": 100, + "has_more": false, + "data": [ + { + "id": "variable-uuid-1", + "name": "customer_name", + "value_type": "string", + "value": "John Doe", + "description": "客户名称(从对话中提取)", + "created_at": 1650000000000, + "updated_at": 1650000000000 + }, + { + "id": "variable-uuid-2", + "name": "order_details", + "value_type": "json", + "value": "{\"product\":\"Widget\",\"quantity\":5,\"price\":19.99}", + "description": "客户的订单详情", + "created_at": 1650000000000, + "updated_at": 1650000000000 + } + ] + } + ``` + </CodeGroup> + </Col> +</Row> + +--- + <Heading url='/audio-to-text' method='POST' @@ -1001,6 +1162,8 @@ import { Row, Col, Properties, Property, Heading, SubProperty } from '../md.tsx' - `name` (string) 应用名称 - `description` (string) 应用描述 - `tags` (array[string]) 应用标签 + - `mode` (string) 应用模式 + - 'author_name' (string) 作者名称 </Col> <Col> <CodeGroup title="Request" tag="GET" label="/info" targetCode={`curl -X GET '${props.appDetail.api_base_url}/info' \\\n-H 'Authorization: Bearer {api_key}'`}> @@ -1017,7 +1180,9 @@ import { Row, Col, Properties, Property, Heading, SubProperty } from '../md.tsx' "tags": [ "tag1", "tag2" - ] + ], + "mode": "chat", + "author_name": "Dify" } ``` </CodeGroup> @@ -1043,6 +1208,13 @@ import { Row, Col, Properties, Property, Heading, SubProperty } from '../md.tsx' - `enabled` (bool) 是否开启 - `speech_to_text` (object) 语音转文本 - `enabled` (bool) 是否开启 + - `text_to_speech` (object) 文本转语音 + - `enabled` (bool) 是否开启 + - `voice` (string) 语音类型 + - `language` (string) 语言 + - `autoPlay` (string) 自动播放 + - `enabled` 开启 + - `disabled` 关闭 - `retriever_resource` (object) 引用和归属 - `enabled` (bool) 是否开启 - `annotation_reply` (object) 标记回复 @@ -1137,15 +1309,15 @@ import { Row, Col, Properties, Property, Heading, SubProperty } from '../md.tsx' /> <Row> <Col> - 用于获取工具icon + 用于获取工具 icon ### Response - `tool_icons`(object[string]) 工具图标 - `工具名称` (string) - `icon` (object|string) - (object) 图标 - - `background` (string) hex格式的背景色 + - `background` (string) hex 格式的背景色 - `content`(string) emoji - - (string) 图标URL + - (string) 图标 URL </Col> <Col> <CodeGroup title="Request" tag="POST" label="/meta" targetCode={`curl -X GET '${props.appDetail.api_base_url}/meta' \\\n-H 'Authorization: Bearer {api_key}'`}> @@ -1171,3 +1343,63 @@ import { Row, Col, Properties, Property, Heading, SubProperty } from '../md.tsx' </CodeGroup> </Col> </Row> + +--- + +<Heading + url='/site' + method='GET' + title='获取应用 WebApp 设置' + name='#site' +/> +<Row> + <Col> + 用于获取应用的 WebApp 设置 + ### Response + - `title` (string) WebApp 名称 + - `chat_color_theme` (string) 聊天颜色主题,hex 格式 + - `chat_color_theme_inverted` (bool) 聊天颜色主题是否反转 + - `icon_type` (string) 图标类型,`emoji`-表情,`image`-图片 + - `icon` (string) 图标,如果是 `emoji` 类型,则是 emoji 表情符号,如果是 `image` 类型,则是图片 URL + - `icon_background` (string) hex 格式的背景色 + - `icon_url` (string) 图标 URL + - `description` (string) 描述 + - `copyright` (string) 版权信息 + - `privacy_policy` (string) 隐私政策链接 + - `custom_disclaimer` (string) 自定义免责声明 + - `default_language` (string) 默认语言 + - `show_workflow_steps` (bool) 是否显示工作流详情 + - `use_icon_as_answer_icon` (bool) 是否使用 WebApp 图标替换聊天中的 🤖 + </Col> + <Col> + <CodeGroup title="Request" tag="POST" label="/meta" targetCode={`curl -X GET '${props.appDetail.api_base_url}/site' \\\n-H 'Authorization: Bearer {api_key}'`}> + ```bash {{ title: 'cURL' }} + curl -X GET '${props.appDetail.api_base_url}/site' \ + -H 'Authorization: Bearer {api_key}' + ``` + + </CodeGroup> + + <CodeGroup title="Response"> + ```json {{ title: 'Response' }} + { + "title": "My App", + "chat_color_theme": "#ff4a4a", + "chat_color_theme_inverted": false, + "icon_type": "emoji", + "icon": "😄", + "icon_background": "#FFEAD5", + "icon_url": null, + "description": "This is my app.", + "copyright": "all rights reserved", + "privacy_policy": "", + "custom_disclaimer": "All generated by AI", + "default_language": "en-US", + "show_workflow_steps": false, + "use_icon_as_answer_icon": false, + } + ``` + </CodeGroup> + </Col> +</Row> +___ diff --git a/web/app/components/develop/template/template_workflow.en.mdx b/web/app/components/develop/template/template_workflow.en.mdx index 0f15c4406b..0ceeadf4ed 100644 --- a/web/app/components/develop/template/template_workflow.en.mdx +++ b/web/app/components/develop/template/template_workflow.en.mdx @@ -64,6 +64,8 @@ Workflow applications offers non-session support and is ideal for translation, a - `user` (string) Required User identifier, used to define the identity of the end-user for retrieval and statistics. Should be uniquely defined by the developer within the application. + <br/> + <i>The user identifier should be consistent with the user passed in the message sending interface. The Service API does not share conversations created by the WebApp.</i> ### Response When `response_mode` is `blocking`, return a CompletionResponse object. @@ -90,7 +92,7 @@ Workflow applications offers non-session support and is ideal for translation, a Each streaming chunk starts with `data:`, separated by two newline characters `\n\n`, as shown below: <CodeGroup> ```streaming {{ title: 'Response' }} - data: {"event": "message", "task_id": "900bbd43-dc0b-4383-a372-aa6e6c414227", "id": "663c5084-a254-4040-8ad3-51f2a3c1a77c", "answer": "Hi", "created_at": 1705398420}\n\n + data: {"event": "text_chunk", "workflow_run_id": "b85e5fc5-751b-454d-b14e-dc5f240b0a31", "task_id": "bd029338-b068-4d34-a331-fc85478922c2", "data": {"text": "\u4e3a\u4e86", "from_variable_selector": ["1745912968134", "text"]}}\n\n ``` </CodeGroup> The structure of the streaming chunks varies depending on the `event`: @@ -116,6 +118,13 @@ Workflow applications offers non-session support and is ideal for translation, a - `predecessor_node_id` (string) optional Prefix node ID, used for canvas display execution path - `inputs` (object) Contents of all preceding node variables used in the node - `created_at` (timestamp) timestamp of start, e.g., 1705395332 + - `event: text_chunk` Text fragment + - `task_id` (string) Task ID, used for request tracking and the below Stop Generate API + - `workflow_run_id` (string) Unique ID of workflow execution + - `event` (string) fixed to `text_chunk` + - `data` (object) detail + - `text` (string) Text content + - `from_variable_selector` (array) Text source path, helping developers understand which node and variable generated the text - `event: node_finished` node execution ends, success or failure in different states in the same event - `task_id` (string) Task ID, used for request tracking and the below Stop Generate API - `workflow_run_id` (string) Unique ID of workflow execution @@ -394,7 +403,7 @@ Workflow applications offers non-session support and is ideal for translation, a - `task_id` (string) Task ID, can be obtained from the streaming chunk return ### Request Body - `user` (string) Required - User identifier, used to define the identity of the end-user, must be consistent with the user passed in the send message interface. + User identifier, used to define the identity of the end-user, must be consistent with the user passed in the message sending interface. The Service API does not share conversations created by the WebApp. ### Response - `result` (string) Always returns "success" </Col> @@ -441,7 +450,7 @@ Workflow applications offers non-session support and is ideal for translation, a - `file` (File) Required The file to be uploaded. - `user` (string) Required - User identifier, defined by the developer's rules, must be unique within the application. + User identifier, defined by the developer's rules, must be unique within the application. The Service API does not share conversations created by the WebApp. ### Response After a successful upload, the server will return the file's ID and related information. @@ -469,7 +478,7 @@ Workflow applications offers non-session support and is ideal for translation, a </Col> <Col sticky> ### Request Example - <CodeGroup title="Request" tag="POST" label="/files/upload" targetCode={`curl -X POST '${props.appDetail.api_base_url}/files/upload' \\\n--header 'Authorization: Bearer {api_key}' \\\n--form 'file=@localfile;type=image/[png|jpeg|jpg|webp|gif] \\\n--form 'user=abc-123'`}> + <CodeGroup title="Request" tag="POST" label="/files/upload" targetCode={`curl -X POST '${props.appDetail.api_base_url}/files/upload' \\\n--header 'Authorization: Bearer {api_key}' \\\n--form 'file=@localfile;type=image/[png|jpeg|jpg|webp|gif]' \\\n--form 'user=abc-123'`}> ```bash {{ title: 'cURL' }} curl -X POST '${props.appDetail.api_base_url}/files/upload' \ @@ -618,6 +627,8 @@ Workflow applications offers non-session support and is ideal for translation, a - `name` (string) application name - `description` (string) application description - `tags` (array[string]) application tags + - `mode` (string) application mode + - `author_name` (string) application author name </Col> <Col> <CodeGroup title="Request" tag="GET" label="/info" targetCode={`curl -X GET '${props.appDetail.api_base_url}/info' \\\n-H 'Authorization: Bearer {api_key}'`}> @@ -634,7 +645,9 @@ Workflow applications offers non-session support and is ideal for translation, a "tags": [ "tag1", "tag2" - ] + ], + "mode": "workflow", + "author_name": "Dify" } ``` </CodeGroup> @@ -730,3 +743,56 @@ Workflow applications offers non-session support and is ideal for translation, a </CodeGroup> </Col> </Row> +--- + +<Heading + url='/site' + method='GET' + title='Get Application WebApp Settings' + name='#site' +/> +<Row> + <Col> + Used to get the WebApp settings of the application. + ### Response + - `title` (string) WebApp name + - `icon_type` (string) Icon type, `emoji` - emoji, `image` - picture + - `icon` (string) Icon. If it's `emoji` type, it's an emoji symbol; if it's `image` type, it's an image URL. + - `icon_background` (string) Background color in hex format + - `icon_url` (string) Icon URL + - `description` (string) Description + - `copyright` (string) Copyright information + - `privacy_policy` (string) Privacy policy link + - `custom_disclaimer` (string) Custom disclaimer + - `default_language` (string) Default language + - `show_workflow_steps` (bool) Whether to show workflow details + </Col> + <Col> + <CodeGroup title="Request" tag="POST" label="/meta" targetCode={`curl -X GET '${props.appDetail.api_base_url}/site' \\\n-H 'Authorization: Bearer {api_key}'`}> + ```bash {{ title: 'cURL' }} + curl -X GET '${props.appDetail.api_base_url}/site' \ + -H 'Authorization: Bearer {api_key}' + ``` + + </CodeGroup> + + <CodeGroup title="Response"> + ```json {{ title: 'Response' }} + { + "title": "My App", + "icon_type": "emoji", + "icon": "😄", + "icon_background": "#FFEAD5", + "icon_url": null, + "description": "This is my app.", + "copyright": "all rights reserved", + "privacy_policy": "", + "custom_disclaimer": "All generated by AI", + "default_language": "en-US", + "show_workflow_steps": false, + } + ``` + </CodeGroup> + </Col> +</Row> +___ diff --git a/web/app/components/develop/template/template_workflow.ja.mdx b/web/app/components/develop/template/template_workflow.ja.mdx index 0239b40224..4f7e423ea5 100644 --- a/web/app/components/develop/template/template_workflow.ja.mdx +++ b/web/app/components/develop/template/template_workflow.ja.mdx @@ -1,12 +1,12 @@ import { CodeGroup } from '../code.tsx' import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from '../md.tsx' -# ワークフローアプリAPI +# ワークフローアプリ API -ワークフローアプリケーションは、セッションをサポートせず、翻訳、記事作成、要約AIなどに最適です。 +ワークフローアプリケーションは、セッションをサポートせず、翻訳、記事作成、要約 AI などに最適です。 <div> - ### ベースURL + ### ベース URL <CodeGroup title="コード" targetCode={props.appDetail.api_base_url}> ```javascript ``` @@ -14,10 +14,10 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from ### 認証 - サービスAPIは`API-Key`認証を使用します。 - <i>**APIキーの漏洩を防ぐため、APIキーはクライアント側で共有または保存せず、サーバー側で保存することを強くお勧めします。**</i> + サービス API は `API-Key` 認証を使用します。 + <i>**API キーの漏洩を防ぐため、API キーはクライアント側で共有または保存せず、サーバー側で保存することを強くお勧めします。**</i> - すべてのAPIリクエストにおいて、以下のように`Authorization`HTTPヘッダーにAPIキーを含めてください: + すべての API リクエストにおいて、以下のように `Authorization`HTTP ヘッダーに API キーを含めてください: <CodeGroup title="コード"> ```javascript @@ -61,7 +61,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from 応答の返却モードを指定します。サポートされているモード: - `streaming` ストリーミングモード(推奨)、SSE([Server-Sent Events](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events))を通じてタイプライターのような出力を実装します。 - `blocking` ブロッキングモード、実行完了後に結果を返します。(プロセスが長い場合、リクエストが中断される可能性があります) - <i>Cloudflareの制限により、100秒後に応答がない場合、リクエストは中断されます。</i> + <i>Cloudflare の制限により、100 秒後に応答がない場合、リクエストは中断されます。</i> - `user` (string) 必須 ユーザー識別子、エンドユーザーのアイデンティティを定義するために使用されます。 アプリケーション内で開発者によって一意に定義される必要があります。 @@ -69,67 +69,74 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from ### 応答 - `response_mode`が`blocking`の場合、CompletionResponseオブジェクトを返します。 - `response_mode`が`streaming`の場合、ChunkCompletionResponseストリームを返します。 + `response_mode`が`blocking`の場合、CompletionResponse オブジェクトを返します。 + `response_mode`が`streaming`の場合、ChunkCompletionResponse ストリームを返します。 ### CompletionResponse アプリの結果を返します。`Content-Type`は`application/json`です。 - - `workflow_run_id` (string) ワークフロー実行の一意のID - - `task_id` (string) タスクID、リクエスト追跡と以下のStop Generate APIに使用 + - `workflow_run_id` (string) ワークフロー実行の一意の ID + - `task_id` (string) タスク ID、リクエスト追跡と以下の Stop Generate API に使用 - `data` (object) 結果の詳細 - - `id` (string) ワークフロー実行のID - - `workflow_id` (string) 関連するワークフローのID + - `id` (string) ワークフロー実行の ID + - `workflow_id` (string) 関連するワークフローの ID - `status` (string) 実行のステータス、`running` / `succeeded` / `failed` / `stopped` - `outputs` (json) オプションの出力内容 - `error` (string) オプションのエラー理由 - `elapsed_time` (float) オプションの使用時間(秒) - `total_tokens` (int) オプションの使用トークン数 - - `total_steps` (int) デフォルト0 + - `total_steps` (int) デフォルト 0 - `created_at` (timestamp) 開始時間 - `finished_at` (timestamp) 終了時間 ### ChunkCompletionResponse アプリによって出力されたストリームチャンクを返します。`Content-Type`は`text/event-stream`です。 - 各ストリーミングチャンクは`data:`で始まり、2つの改行文字`\n\n`で区切られます。以下のように表示されます: + 各ストリーミングチャンクは`data:`で始まり、2 つの改行文字`\n\n`で区切られます。以下のように表示されます: <CodeGroup> ```streaming {{ title: '応答' }} - data: {"event": "message", "task_id": "900bbd43-dc0b-4383-a372-aa6e6c414227", "id": "663c5084-a254-4040-8ad3-51f2a3c1a77c", "answer": "Hi", "created_at": 1705398420}\n\n + data: {"event": "text_chunk", "workflow_run_id": "b85e5fc5-751b-454d-b14e-dc5f240b0a31", "task_id": "bd029338-b068-4d34-a331-fc85478922c2", "data": {"text": "\u4e3a\u4e86", "from_variable_selector": ["1745912968134", "text"]}}\n\n ``` </CodeGroup> ストリーミングチャンクの構造は`event`に応じて異なります: - `event: workflow_started` ワークフローが実行を開始 - - `task_id` (string) タスクID、リクエスト追跡と以下のStop Generate APIに使用 - - `workflow_run_id` (string) ワークフロー実行の一意のID + - `task_id` (string) タスク ID、リクエスト追跡と以下の Stop Generate API に使用 + - `workflow_run_id` (string) ワークフロー実行の一意の ID - `event` (string) `workflow_started`に固定 - `data` (object) 詳細 - - `id` (string) ワークフロー実行の一意のID - - `workflow_id` (string) 関連するワークフローのID - - `sequence_number` (int) 自己増加シリアル番号、アプリ内で自己増加し、1から始まります + - `id` (string) ワークフロー実行の一意の ID + - `workflow_id` (string) 関連するワークフローの ID + - `sequence_number` (int) 自己増加シリアル番号、アプリ内で自己増加し、1 から始まります - `created_at` (timestamp) 作成タイムスタンプ、例:1705395332 - `event: node_started` ノード実行開始 - - `task_id` (string) タスクID、リクエスト追跡と以下のStop Generate APIに使用 - - `workflow_run_id` (string) ワークフロー実行の一意のID + - `task_id` (string) タスク ID、リクエスト追跡と以下の Stop Generate API に使用 + - `workflow_run_id` (string) ワークフロー実行の一意の ID - `event` (string) `node_started`に固定 - `data` (object) 詳細 - - `id` (string) ワークフロー実行の一意のID - - `node_id` (string) ノードのID + - `id` (string) ワークフロー実行の一意の ID + - `node_id` (string) ノードの ID - `node_type` (string) ノードのタイプ - `title` (string) ノードの名前 - `index` (int) 実行シーケンス番号、トレースノードシーケンスを表示するために使用 - - `predecessor_node_id` (string) オプションのプレフィックスノードID、キャンバス表示実行パスに使用 + - `predecessor_node_id` (string) オプションのプレフィックスノード ID、キャンバス表示実行パスに使用 - `inputs` (object) ノードで使用されるすべての前のノード変数の内容 - `created_at` (timestamp) 開始のタイムスタンプ、例:1705395332 + - `event: text_chunk` テキストフラグメント + - `task_id` (string) タスク ID、リクエスト追跡と以下の Stop Generate API に使用 + - `workflow_run_id` (string) ワークフロー実行の一意の ID + - `event` (string) `text_chunk`に固定 + - `data` (object) 詳細 + - `text` (string) テキスト内容 + - `from_variable_selector` (array) テキスト生成元パス(開発者がどのノードのどの変数から生成されたかを理解するための情報) - `event: node_finished` ノード実行終了、同じイベントで異なる状態で成功または失敗 - - `task_id` (string) タスクID、リクエスト追跡と以下のStop Generate APIに使用 - - `workflow_run_id` (string) ワークフロー実行の一意のID + - `task_id` (string) タスク ID、リクエスト追跡と以下の Stop Generate API に使用 + - `workflow_run_id` (string) ワークフロー実行の一意の ID - `event` (string) `node_finished`に固定 - `data` (object) 詳細 - - `id` (string) ワークフロー実行の一意のID - - `node_id` (string) ノードのID + - `id` (string) ワークフロー実行の一意の ID + - `node_id` (string) ノードの ID - `node_type` (string) ノードのタイプ - `title` (string) ノードの名前 - `index` (int) 実行シーケンス番号、トレースノードシーケンスを表示するために使用 - - `predecessor_node_id` (string) オプションのプレフィックスノードID、キャンバス表示実行パスに使用 + - `predecessor_node_id` (string) オプションのプレフィックスノード ID、キャンバス表示実行パスに使用 - `inputs` (object) ノードで使用されるすべての前のノード変数の内容 - `process_data` (json) オプションのノードプロセスデータ - `outputs` (json) オプションの出力内容 @@ -142,31 +149,31 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from - `currency` (string) オプション 例:`USD` / `RMB` - `created_at` (timestamp) 開始のタイムスタンプ、例:1705395332 - `event: workflow_finished` ワークフロー実行終了、同じイベントで異なる状態で成功または失敗 - - `task_id` (string) タスクID、リクエスト追跡と以下のStop Generate APIに使用 - - `workflow_run_id` (string) ワークフロー実行の一意のID + - `task_id` (string) タスク ID、リクエスト追跡と以下の Stop Generate API に使用 + - `workflow_run_id` (string) ワークフロー実行の一意の ID - `event` (string) `workflow_finished`に固定 - `data` (object) 詳細 - - `id` (string) ワークフロー実行のID - - `workflow_id` (string) 関連するワークフローのID + - `id` (string) ワークフロー実行の ID + - `workflow_id` (string) 関連するワークフローの ID - `status` (string) 実行のステータス、`running` / `succeeded` / `failed` / `stopped` - `outputs` (json) オプションの出力内容 - `error` (string) オプションのエラー理由 - `elapsed_time` (float) オプションの使用時間(秒) - `total_tokens` (int) オプションの使用トークン数 - - `total_steps` (int) デフォルト0 + - `total_steps` (int) デフォルト 0 - `created_at` (timestamp) 開始時間 - `finished_at` (timestamp) 終了時間 - - `event: tts_message` TTSオーディオストリームイベント、つまり音声合成出力。内容はMp3形式のオーディオブロックで、base64文字列としてエンコードされています。再生時には、base64をデコードしてプレーヤーに入力するだけです。(このメッセージは自動再生が有効な場合にのみ利用可能) - - `task_id` (string) タスクID、リクエスト追跡と以下の停止応答インターフェースに使用 - - `message_id` (string) 一意のメッセージID - - `audio` (string) 音声合成後のオーディオ、base64テキストコンテンツとしてエンコードされており、再生時にはbase64をデコードしてプレーヤーに入力するだけです + - `event: tts_message` TTS オーディオストリームイベント、つまり音声合成出力。内容は Mp3 形式のオーディオブロックで、base64 文字列としてエンコードされています。再生時には、base64 をデコードしてプレーヤーに入力するだけです。(このメッセージは自動再生が有効な場合にのみ利用可能) + - `task_id` (string) タスク ID、リクエスト追跡と以下の停止応答インターフェースに使用 + - `message_id` (string) 一意のメッセージ ID + - `audio` (string) 音声合成後のオーディオ、base64 テキストコンテンツとしてエンコードされており、再生時には base64 をデコードしてプレーヤーに入力するだけです - `created_at` (int) 作成タイムスタンプ、例:1705395332 - - `event: tts_message_end` TTSオーディオストリーム終了イベント。このイベントを受信すると、オーディオストリームの終了を示します。 - - `task_id` (string) タスクID、リクエスト追跡と以下の停止応答インターフェースに使用 - - `message_id` (string) 一意のメッセージID + - `event: tts_message_end` TTS オーディオストリーム終了イベント。このイベントを受信すると、オーディオストリームの終了を示します。 + - `task_id` (string) タスク ID、リクエスト追跡と以下の停止応答インターフェースに使用 + - `message_id` (string) 一意のメッセージ ID - `audio` (string) 終了イベントにはオーディオがないため、これは空の文字列です - `created_at` (int) 作成タイムスタンプ、例:1705395332 - - `event: ping` 接続を維持するために10秒ごとに送信されるPingイベント。 + - `event: ping` 接続を維持するために 10 秒ごとに送信される Ping イベント。 ### エラー - 400, `invalid_param`, 異常なパラメータ入力 @@ -335,12 +342,12 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from /> <Row> <Col> - ワークフロー実行IDに基づいて、ワークフロータスクの現在の実行結果を取得します。 + ワークフロー実行 ID に基づいて、ワークフロータスクの現在の実行結果を取得します。 ### パス - `workflow_id` (string) ワークフローID、ストリーミングチャンクの返り値から取得可能 ### 応答 - - `id` (string) ワークフロー実行のID - - `workflow_id` (string) 関連するワークフローのID + - `id` (string) ワークフロー実行の ID + - `workflow_id` (string) 関連するワークフローの ID - `status` (string) 実行のステータス、`running` / `succeeded` / `failed` / `stopped` - `inputs` (json) 入力内容 - `outputs` (json) 出力内容 @@ -394,10 +401,10 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from <Col> ストリーミングモードでのみサポートされています。 ### パス - - `task_id` (string) タスクID、ストリーミングチャンクの返り値から取得可能 + - `task_id` (string) タスク ID、ストリーミングチャンクの返り値から取得可能 ### リクエストボディ - `user` (string) 必須 - ユーザー識別子、エンドユーザーのアイデンティティを定義するために使用され、送信メッセージインターフェースで渡されたユーザーと一致している必要があります。 + ユーザー識別子、エンドユーザーのアイデンティティを定義するために使用され、送信メッセージインターフェースで渡されたユーザーと一致している必要があります。サービス API は WebApp によって作成された会話を共有しません。 ### 応答 - `result` (string) 常に"success"を返します </Col> @@ -444,35 +451,35 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from - `file` (File) 必須 アップロードするファイル。 - `user` (string) 必須 - ユーザー識別子、開発者のルールで定義され、アプリケーション内で一意でなければなりません。 + ユーザー識別子、開発者のルールで定義され、アプリケーション内で一意でなければなりません。サービス API は WebApp によって作成された会話を共有しません。 ### 応答 - アップロードが成功すると、サーバーはファイルのIDと関連情報を返します。 + アップロードが成功すると、サーバーはファイルの ID と関連情報を返します。 - `id` (uuid) ID - `name` (string) ファイル名 - `size` (int) ファイルサイズ(バイト) - `extension` (string) ファイル拡張子 - - `mime_type` (string) ファイルのMIMEタイプ + - `mime_type` (string) ファイルの MIME タイプ - `created_by` (uuid) エンドユーザーID - `created_at` (timestamp) 作成タイムスタンプ、例:1705395332 ### エラー - 400, `no_file_uploaded`, ファイルが提供されていません - - 400, `too_many_files`, 現在は1つのファイルのみ受け付けています + - 400, `too_many_files`, 現在は 1 つのファイルのみ受け付けています - 400, `unsupported_preview`, ファイルはプレビューをサポートしていません - 400, `unsupported_estimate`, ファイルは推定をサポートしていません - 413, `file_too_large`, ファイルが大きすぎます - 415, `unsupported_file_type`, サポートされていない拡張子、現在はドキュメントファイルのみ受け付けています - - 503, `s3_connection_failed`, S3サービスに接続できません - - 503, `s3_permission_denied`, S3にファイルをアップロードする権限がありません - - 503, `s3_file_too_large`, ファイルがS3のサイズ制限を超えています + - 503, `s3_connection_failed`, S3 サービスに接続できません + - 503, `s3_permission_denied`, S3 にファイルをアップロードする権限がありません + - 503, `s3_file_too_large`, ファイルが S3 のサイズ制限を超えています - 500, 内部サーバーエラー </Col> <Col sticky> ### リクエスト例 - <CodeGroup title="リクエスト" tag="POST" label="/files/upload" targetCode={`curl -X POST '${props.appDetail.api_base_url}/files/upload' \\\n--header 'Authorization: Bearer {api_key}' \\\n--form 'file=@localfile;type=image/[png|jpeg|jpg|webp|gif] \\\n--form 'user=abc-123'`}> + <CodeGroup title="リクエスト" tag="POST" label="/files/upload" targetCode={`curl -X POST '${props.appDetail.api_base_url}/files/upload' \\\n--header 'Authorization: Bearer {api_key}' \\\n--form 'file=@localfile;type=image/[png|jpeg|jpg|webp|gif]' \\\n--form 'user=abc-123'`}> ```bash {{ title: 'cURL' }} curl -X POST '${props.appDetail.api_base_url}/files/upload' \ @@ -543,7 +550,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from - `error` (string) オプションのエラー理由 - `elapsed_time` (float) 使用される総秒数 - `total_tokens` (int) 使用されるトークン数 - - `total_steps` (int) デフォルト0 + - `total_steps` (int) デフォルト 0 - `created_at` (timestamp) 開始時間 - `finished_at` (timestamp) 終了時間 - `created_from` (string) 作成元 @@ -553,7 +560,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from - `id` (string) ID - `type` (string) タイプ - `is_anonymous` (bool) 匿名かどうか - - `session_id` (string) セッションID + - `session_id` (string) セッション ID - `created_at` (timestamp) 作成時間 </Col> <Col sticky> @@ -621,6 +628,8 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from - `name` (string) アプリケーションの名前 - `description` (string) アプリケーションの説明 - `tags` (array[string]) アプリケーションのタグ + - `mode` (string) アプリケーションのモード + - `author_name` (string) 作者の名前 </Col> <Col> <CodeGroup title="Request" tag="GET" label="/info" targetCode={`curl -X GET '${props.appDetail.api_base_url}/info' \\\n-H 'Authorization: Bearer {api_key}'`}> @@ -637,7 +646,9 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from "tags": [ "tag1", "tag2" - ] + ], + "mode": "workflow", + "author_name": "Dify" } ``` </CodeGroup> @@ -733,3 +744,57 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from </CodeGroup> </Col> </Row> +——— + +<Heading + url='/site' + method='GET' + title='アプリのWebApp設定を取得' + name='#site' +/> +<Row> + <Col> + アプリの WebApp 設定を取得するために使用します。 + ### 応答 + - `title` (string) WebApp 名 + - `icon_type` (string) アイコンタイプ、`emoji`-絵文字、`image`-画像 + - `icon` (string) アイコン。`emoji`タイプの場合は絵文字、`image`タイプの場合は画像 URL + - `icon_background` (string) 16 進数形式の背景色 + - `icon_url` (string) アイコンの URL + - `description` (string) 説明 + - `copyright` (string) 著作権情報 + - `privacy_policy` (string) プライバシーポリシーのリンク + - `custom_disclaimer` (string) カスタム免責事項 + - `default_language` (string) デフォルト言語 + - `show_workflow_steps` (bool) ワークフローの詳細を表示するかどうか + </Col> + <Col> + <CodeGroup title="Request" tag="POST" label="/meta" targetCode={`curl -X GET '${props.appDetail.api_base_url}/site' \\\n-H 'Authorization: Bearer {api_key}'`}> + ```bash {{ title: 'cURL' }} + curl -X GET '${props.appDetail.api_base_url}/site' \ + -H 'Authorization: Bearer {api_key}' + ``` + + </CodeGroup> + + <CodeGroup title="Response"> + ```json {{ title: 'Response' }} + { + "title": "My App", + "icon_type": "emoji", + "icon": "😄", + "icon_background": "#FFEAD5", + "icon_url": null, + "description": "This is my app.", + "copyright": "all rights reserved", + "privacy_policy": "", + "custom_disclaimer": "All generated by AI", + "default_language": "en-US", + "show_workflow_steps": false, + } + ``` + </CodeGroup> + </Col> +</Row> +___ + diff --git a/web/app/components/develop/template/template_workflow.zh.mdx b/web/app/components/develop/template/template_workflow.zh.mdx index 939df2703d..d599cb503f 100644 --- a/web/app/components/develop/template/template_workflow.zh.mdx +++ b/web/app/components/develop/template/template_workflow.zh.mdx @@ -14,7 +14,7 @@ Workflow 应用无会话支持,适合用于翻译/文章写作/总结 AI 等 ### Authentication - Dify Service API 使用 `API-Key` 进行鉴权。 + Service API 使用 `API-Key` 进行鉴权。 <i>**强烈建议开发者把 `API-Key` 放在后端存储,而非分享或者放在客户端存储,以免 `API-Key` 泄露,导致财产损失。**</i> 所有 API 请求都应在 **`Authorization`** HTTP Header 中包含您的 `API-Key`,如下所示: @@ -59,7 +59,7 @@ Workflow 应用无会话支持,适合用于翻译/文章写作/总结 AI 等 <i>由于 Cloudflare 限制,请求会在 100 秒超时无返回后中断。</i> - `user` (string) Required 用户标识,用于定义终端用户的身份,方便检索、统计。 - 由开发者定义规则,需保证用户标识在应用内唯一。 + 由开发者定义规则,需保证用户标识在应用内唯一。API 无法访问 WebApp 创建的会话。 ### Response @@ -87,7 +87,7 @@ Workflow 应用无会话支持,适合用于翻译/文章写作/总结 AI 等 每个流式块均为 data: 开头,块之间以 `\n\n` 即两个换行符分隔,如下所示: <CodeGroup> ```streaming {{ title: 'Response' }} - data: {"event": "message", "task_id": "900bbd43-dc0b-4383-a372-aa6e6c414227", "id": "663c5084-a254-4040-8ad3-51f2a3c1a77c", "answer": "Hi", "created_at": 1705398420}\n\n + data: {"event": "text_chunk", "workflow_run_id": "b85e5fc5-751b-454d-b14e-dc5f240b0a31", "task_id": "bd029338-b068-4d34-a331-fc85478922c2", "data": {"text": "\u4e3a\u4e86", "from_variable_selector": ["1745912968134", "text"]}}\n\n ``` </CodeGroup> 流式块中根据 `event` 不同,结构也不同,包含以下类型: @@ -113,6 +113,13 @@ Workflow 应用无会话支持,适合用于翻译/文章写作/总结 AI 等 - `predecessor_node_id` (string) 前置节点 ID,用于画布展示执行路径 - `inputs` (object) 节点中所有使用到的前置节点变量内容 - `created_at` (timestamp) 开始时间 + - `event: text_chunk` 文本片段 + - `task_id` (string) 任务 ID,用于请求跟踪和下方的停止响应接口 + - `workflow_run_id` (string) workflow 执行 ID + - `event` (string) 固定为 `text_chunk` + - `data` (object) 详细内容 + - `text` (string) 文本内容 + - `from_variable_selector` (array) 文本来源路径,帮助开发者了解文本是由哪个节点的哪个变量生成的 - `event: node_finished` node 执行结束,成功失败同一事件中不同状态 - `task_id` (string) 任务 ID,用于请求跟踪和下方的停止响应接口 - `workflow_run_id` (string) workflow 执行 ID @@ -339,7 +346,7 @@ Workflow 应用无会话支持,适合用于翻译/文章写作/总结 AI 等 - `total_tokens` (int) 任务执行总 tokens - `created_at` (timestamp) 任务开始时间 - `finished_at` (timestamp) 任务结束时间 - - `elapsed_time` (float) 耗时(s) + - `elapsed_time` (float) 耗时 (s) </Col> <Col sticky> ### Request Example @@ -387,7 +394,7 @@ Workflow 应用无会话支持,适合用于翻译/文章写作/总结 AI 等 - `task_id` (string) 任务 ID,可在流式返回 Chunk 中获取 ### Request Body - `user` (string) Required - 用户标识,用于定义终端用户的身份,必须和发送消息接口传入 user 保持一致。 + 用户标识,用于定义终端用户的身份,必须和发送消息接口传入 user 保持一致。API 无法访问 WebApp 创建的会话。 ### Response - `result` (string) 固定返回 "success" </Col> @@ -436,7 +443,7 @@ Workflow 应用无会话支持,适合用于翻译/文章写作/总结 AI 等 要上传的文件。 </Property> <Property name='user' type='string' key='user'> - 用户标识,用于定义终端用户的身份,必须和发送消息接口传入 user 保持一致。 + 用户标识,用于定义终端用户的身份,必须和发送消息接口传入 user 保持一致。服务 API 不会共享 WebApp 创建的对话。 </Property> </Properties> @@ -463,7 +470,7 @@ Workflow 应用无会话支持,适合用于翻译/文章写作/总结 AI 等 </Col> <Col sticky> - <CodeGroup title="Request" tag="POST" label="/files/upload" targetCode={`curl -X POST '${props.appDetail.api_base_url}/files/upload' \\\n--header 'Authorization: Bearer {api_key}' \\\n--form 'file=@localfile;type=image/[png|jpeg|jpg|webp|gif] \\\n--form 'user=abc-123'`}> + <CodeGroup title="Request" tag="POST" label="/files/upload" targetCode={`curl -X POST '${props.appDetail.api_base_url}/files/upload' \\\n--header 'Authorization: Bearer {api_key}' \\\n--form 'file=@localfile;type=image/[png|jpeg|jpg|webp|gif]' \\\n--form 'user=abc-123'`}> ```bash {{ title: 'cURL' }} curl -X POST '${props.appDetail.api_base_url}/files/upload' \ @@ -498,7 +505,7 @@ Workflow 应用无会话支持,适合用于翻译/文章写作/总结 AI 等 /> <Row> <Col> - 倒序返回workflow日志 + 倒序返回 workflow 日志 ### Query @@ -527,10 +534,10 @@ Workflow 应用无会话支持,适合用于翻译/文章写作/总结 AI 等 - `workflow_run` (object) Workflow 执行日志 - `id` (string) 标识 - `version` (string) 版本 - - `status` (string) 执行状态, `running` / `succeeded` / `failed` / `stopped` + - `status` (string) 执行状态,`running` / `succeeded` / `failed` / `stopped` - `error` (string) (可选) 错误 - `elapsed_time` (float) 耗时,单位秒 - - `total_tokens` (int) 消耗的token数量 + - `total_tokens` (int) 消耗的 token 数量 - `total_steps` (int) 执行步骤长度 - `created_at` (timestamp) 开始时间 - `finished_at` (timestamp) 结束时间 @@ -608,6 +615,8 @@ Workflow 应用无会话支持,适合用于翻译/文章写作/总结 AI 等 - `name` (string) 应用名称 - `description` (string) 应用描述 - `tags` (array[string]) 应用标签 + - `mode` (string) 应用模式 + - 'author_name' (string) 作者名称 </Col> <Col> <CodeGroup title="Request" tag="GET" label="/info" targetCode={`curl -X GET '${props.appDetail.api_base_url}/info' \\\n-H 'Authorization: Bearer {api_key}'`}> @@ -624,7 +633,9 @@ Workflow 应用无会话支持,适合用于翻译/文章写作/总结 AI 等 "tags": [ "tag1", "tag2" - ] + ], + "mode": "workflow", + "author_name": "Dify" } ``` </CodeGroup> @@ -720,3 +731,57 @@ Workflow 应用无会话支持,适合用于翻译/文章写作/总结 AI 等 </CodeGroup> </Col> </Row> + +--- + +<Heading + url='/site' + method='GET' + title='获取应用 WebApp 设置' + name='#site' +/> +<Row> + <Col> + 用于获取应用的 WebApp 设置 + ### Response + - `title` (string) WebApp 名称 + - `icon_type` (string) 图标类型,`emoji`-表情,`image`-图片 + - `icon` (string) 图标,如果是 `emoji` 类型,则是 emoji 表情符号,如果是 `image` 类型,则是图片 URL + - `icon_background` (string) hex 格式的背景色 + - `icon_url` (string) 图标 URL + - `description` (string) 描述 + - `copyright` (string) 版权信息 + - `privacy_policy` (string) 隐私政策链接 + - `custom_disclaimer` (string) 自定义免责声明 + - `default_language` (string) 默认语言 + - `show_workflow_steps` (bool) 是否显示工作流详情 + </Col> + <Col> + <CodeGroup title="Request" tag="POST" label="/meta" targetCode={`curl -X GET '${props.appDetail.api_base_url}/site' \\\n-H 'Authorization: Bearer {api_key}'`}> + ```bash {{ title: 'cURL' }} + curl -X GET '${props.appDetail.api_base_url}/site' \ + -H 'Authorization: Bearer {api_key}' + ``` + + </CodeGroup> + + <CodeGroup title="Response"> + ```json {{ title: 'Response' }} + { + "title": "My App", + "icon_type": "emoji", + "icon": "😄", + "icon_background": "#FFEAD5", + "icon_url": null, + "description": "This is my app.", + "copyright": "all rights reserved", + "privacy_policy": "", + "custom_disclaimer": "All generated by AI", + "default_language": "en-US", + "show_workflow_steps": false, + } + ``` + </CodeGroup> + </Col> +</Row> +___ diff --git a/web/app/components/explore/app-list/index.tsx b/web/app/components/explore/app-list/index.tsx index e217dda2b2..7e2d990bc8 100644 --- a/web/app/components/explore/app-list/index.tsx +++ b/web/app/components/explore/app-list/index.tsx @@ -1,12 +1,10 @@ 'use client' -import React, { useMemo, useState } from 'react' -import { useRouter } from 'next/navigation' +import React, { useCallback, useMemo, useState } from 'react' import { useTranslation } from 'react-i18next' import { useContext } from 'use-context-selector' import useSWR from 'swr' import { useDebounceFn } from 'ahooks' -import Toast from '../../base/toast' import s from './style.module.css' import cn from '@/utils/classnames' import ExploreContext from '@/context/explore-context' @@ -14,17 +12,16 @@ import type { App } from '@/models/explore' import Category from '@/app/components/explore/category' import AppCard from '@/app/components/explore/app-card' import { fetchAppDetail, fetchAppList } from '@/service/explore' -import { importDSL } from '@/service/apps' import { useTabSearchParams } from '@/hooks/use-tab-searchparams' import CreateAppModal from '@/app/components/explore/create-app-modal' import type { CreateAppModalProps } from '@/app/components/explore/create-app-modal' import Loading from '@/app/components/base/loading' -import { NEED_REFRESH_APP_LIST_KEY } from '@/config' -import { useAppContext } from '@/context/app-context' -import { getRedirection } from '@/utils/app-redirection' import Input from '@/app/components/base/input' -import { DSLImportMode } from '@/models/app' -import { usePluginDependencies } from '@/app/components/workflow/plugin-dependency/hooks' +import { + DSLImportMode, +} from '@/models/app' +import { useImportDSL } from '@/hooks/use-import-dsl' +import DSLConfirmModal from '@/app/components/app/create-from-dsl-modal/dsl-confirm-modal' type AppsProps = { onSuccess?: () => void @@ -39,8 +36,6 @@ const Apps = ({ onSuccess, }: AppsProps) => { const { t } = useTranslation() - const { isCurrentWorkspaceEditor } = useAppContext() - const { push } = useRouter() const { hasEditPermission } = useContext(ExploreContext) const allCategoriesEn = t('explore.apps.allCategories', { lng: 'en' }) @@ -115,7 +110,14 @@ const Apps = ({ const [currApp, setCurrApp] = React.useState<App | null>(null) const [isShowCreateModal, setIsShowCreateModal] = React.useState(false) - const { handleCheckPluginDependencies } = usePluginDependencies() + + const { + handleImportDSL, + handleImportDSLConfirm, + versions, + isFetching, + } = useImportDSL() + const [showDSLConfirmModal, setShowDSLConfirmModal] = useState(false) const onCreate: CreateAppModalProps['onConfirm'] = async ({ name, icon_type, @@ -123,36 +125,34 @@ const Apps = ({ icon_background, description, }) => { - const { export_data, mode } = await fetchAppDetail( + const { export_data } = await fetchAppDetail( currApp?.app.id as string, ) - try { - const app = await importDSL({ - mode: DSLImportMode.YAML_CONTENT, - yaml_content: export_data, - name, - icon_type, - icon, - icon_background, - description, - }) - setIsShowCreateModal(false) - Toast.notify({ - type: 'success', - message: t('app.newApp.appCreated'), - }) - if (onSuccess) - onSuccess() - if (app.app_id) - await handleCheckPluginDependencies(app.app_id) - localStorage.setItem(NEED_REFRESH_APP_LIST_KEY, '1') - getRedirection(isCurrentWorkspaceEditor, { id: app.app_id!, mode }, push) - } - catch { - Toast.notify({ type: 'error', message: t('app.newApp.appCreateFailed') }) + const payload = { + mode: DSLImportMode.YAML_CONTENT, + yaml_content: export_data, + name, + icon_type, + icon, + icon_background, + description, } + await handleImportDSL(payload, { + onSuccess: () => { + setIsShowCreateModal(false) + }, + onPending: () => { + setShowDSLConfirmModal(true) + }, + }) } + const onConfirmDSL = useCallback(async () => { + await handleImportDSLConfirm({ + onSuccess, + }) + }, [handleImportDSLConfirm, onSuccess]) + if (!categories || categories.length === 0) { return ( <div className="flex h-full items-center"> @@ -225,9 +225,20 @@ const Apps = ({ appDescription={currApp?.app.description || ''} show={isShowCreateModal} onConfirm={onCreate} + confirmDisabled={isFetching} onHide={() => setIsShowCreateModal(false)} /> )} + { + showDSLConfirmModal && ( + <DSLConfirmModal + versions={versions} + onCancel={() => setShowDSLConfirmModal(false)} + onConfirm={onConfirmDSL} + confirmDisabled={isFetching} + /> + ) + } </div> ) } diff --git a/web/app/components/explore/app-list/style.module.css b/web/app/components/explore/app-list/style.module.css index fb6b1ee94f..241130a03d 100644 --- a/web/app/components/explore/app-list/style.module.css +++ b/web/app/components/explore/app-list/style.module.css @@ -26,4 +26,4 @@ .appList { grid-template-columns: repeat(2, minmax(0, 1fr)) } -} \ No newline at end of file +} diff --git a/web/app/components/explore/category.tsx b/web/app/components/explore/category.tsx index 4456131600..51daaa95dd 100644 --- a/web/app/components/explore/category.tsx +++ b/web/app/components/explore/category.tsx @@ -31,8 +31,8 @@ const Category: FC<ICategoryProps> = ({ const isAllCategories = !list.includes(value as AppCategory) || value === allCategoriesEn const itemClassName = (isSelected: boolean) => cn( - 'flex h-[32px] cursor-pointer items-center rounded-lg border-[0.5px] border-transparent px-3 py-[7px] font-medium leading-[18px] text-gray-700 hover:bg-gray-200', - isSelected && 'border-gray-200 bg-white text-primary-600 shadow-xs hover:bg-white', + 'flex h-[32px] cursor-pointer items-center rounded-lg border-[0.5px] border-transparent px-3 py-[7px] font-medium leading-[18px] text-text-tertiary hover:bg-components-main-nav-nav-button-bg-active', + isSelected && 'border-components-main-nav-nav-button-border bg-components-main-nav-nav-button-bg-active text-components-main-nav-nav-button-text-active shadow-xs', ) return ( @@ -50,7 +50,7 @@ const Category: FC<ICategoryProps> = ({ className={itemClassName(name === value)} onClick={() => onChange(name)} > - {categoryI18n[name] ? t(`explore.category.${name}`) : name} + {(categoryI18n as any)[name] ? t(`explore.category.${name}`) : name} </div> ))} </div> diff --git a/web/app/components/explore/create-app-modal/index.tsx b/web/app/components/explore/create-app-modal/index.tsx index d6d521833a..f30b286786 100644 --- a/web/app/components/explore/create-app-modal/index.tsx +++ b/web/app/components/explore/create-app-modal/index.tsx @@ -35,6 +35,7 @@ export type CreateAppModalProps = { description: string use_icon_as_answer_icon?: boolean }) => Promise<void> + confirmDisabled?: boolean onHide: () => void } @@ -50,6 +51,7 @@ const CreateAppModal = ({ appMode, appUseIconAsAnswerIcon, onConfirm, + confirmDisabled, onHide, }: CreateAppModalProps) => { const { t } = useTranslation() @@ -160,7 +162,7 @@ const CreateAppModal = ({ </div> <div className='flex flex-row-reverse'> <Button - disabled={(!isEditModal && isAppsFull) || !name.trim()} + disabled={(!isEditModal && isAppsFull) || !name.trim() || confirmDisabled} className='ml-2 w-24 gap-1' variant='primary' onClick={handleSubmit} diff --git a/web/app/components/explore/index.tsx b/web/app/components/explore/index.tsx index 5175b46377..bae2610cba 100644 --- a/web/app/components/explore/index.tsx +++ b/web/app/components/explore/index.tsx @@ -2,12 +2,13 @@ import type { FC } from 'react' import React, { useEffect, useState } from 'react' import { useRouter } from 'next/navigation' -import { useTranslation } from 'react-i18next' import ExploreContext from '@/context/explore-context' import Sidebar from '@/app/components/explore/sidebar' import { useAppContext } from '@/context/app-context' import { fetchMembers } from '@/service/common' import type { InstalledApp } from '@/models/explore' +import { useTranslation } from 'react-i18next' +import useDocumentTitle from '@/hooks/use-document-title' export type IExploreProps = { children: React.ReactNode @@ -16,15 +17,16 @@ export type IExploreProps = { const Explore: FC<IExploreProps> = ({ children, }) => { - const { t } = useTranslation() const router = useRouter() const [controlUpdateInstalledApps, setControlUpdateInstalledApps] = useState(0) const { userProfile, isCurrentWorkspaceDatasetOperator } = useAppContext() const [hasEditPermission, setHasEditPermission] = useState(false) const [installedApps, setInstalledApps] = useState<InstalledApp[]>([]) + const { t } = useTranslation() + + useDocumentTitle(t('common.menus.explore')) useEffect(() => { - document.title = `${t('explore.title')} - Dify`; (async () => { const { accounts } = await fetchMembers({ url: '/workspaces/current/members', params: {} }) if (!accounts) diff --git a/web/app/components/explore/installed-app/index.tsx b/web/app/components/explore/installed-app/index.tsx index 62f9452f88..71013fc2e1 100644 --- a/web/app/components/explore/installed-app/index.tsx +++ b/web/app/components/explore/installed-app/index.tsx @@ -26,15 +26,15 @@ const InstalledApp: FC<IInstalledAppProps> = ({ } return ( - <div className='h-full py-2 pl-0 pr-2 sm:p-2'> + <div className='h-full bg-background-default py-2 pl-0 pr-2 sm:p-2'> {installedApp.app.mode !== 'completion' && installedApp.app.mode !== 'workflow' && ( <ChatWithHistory installedAppInfo={installedApp} className='overflow-hidden rounded-2xl shadow-md' /> )} {installedApp.app.mode === 'completion' && ( - <TextGenerationApp isInstalledApp installedAppInfo={installedApp}/> + <TextGenerationApp isInstalledApp installedAppInfo={installedApp} /> )} {installedApp.app.mode === 'workflow' && ( - <TextGenerationApp isWorkflow isInstalledApp installedAppInfo={installedApp}/> + <TextGenerationApp isWorkflow isInstalledApp installedAppInfo={installedApp} /> )} </div> ) diff --git a/web/app/components/explore/item-operation/style.module.css b/web/app/components/explore/item-operation/style.module.css index 514ffabe36..7d4a46a840 100644 --- a/web/app/components/explore/item-operation/style.module.css +++ b/web/app/components/explore/item-operation/style.module.css @@ -30,4 +30,4 @@ body .btn:hover { .deleteActionItem:hover .deleteActionItemChild { @apply text-red-500; -} \ No newline at end of file +} diff --git a/web/app/components/header/account-about/index.tsx b/web/app/components/header/account-about/index.tsx index 16dd63ea7a..280e276be9 100644 --- a/web/app/components/header/account-about/index.tsx +++ b/web/app/components/header/account-about/index.tsx @@ -7,8 +7,9 @@ import Modal from '@/app/components/base/modal' import Button from '@/app/components/base/button' import type { LangGeniusVersionResponse } from '@/models/common' import { IS_CE_EDITION } from '@/config' -import LogoSite from '@/app/components/base/logo/logo-site' +import DifyLogo from '@/app/components/base/logo/dify-logo' import { noop } from 'lodash-es' +import { useGlobalPublicStore } from '@/context/global-public-context' type IAccountSettingProps = { langeniusVersionInfo: LangGeniusVersionResponse @@ -21,6 +22,7 @@ export default function AccountAbout({ }: IAccountSettingProps) { const { t } = useTranslation() const isLatest = langeniusVersionInfo.current_version === langeniusVersionInfo.latest_version + const systemFeatures = useGlobalPublicStore(s => s.systemFeatures) return ( <Modal @@ -28,21 +30,28 @@ export default function AccountAbout({ onClose={noop} className='!w-[480px] !max-w-[480px] !px-6 !py-4' > - <div className='relative pt-4'> - <div className='absolute -right-4 -top-2 flex h-8 w-8 cursor-pointer items-center justify-center' onClick={onCancel}> + <div> + <div className='absolute right-4 top-4 flex h-8 w-8 cursor-pointer items-center justify-center' onClick={onCancel}> <RiCloseLine className='h-4 w-4 text-text-tertiary' /> </div> - <div> - <LogoSite className='mx-auto mb-2' /> - <div className='mb-3 text-center text-xs font-normal text-text-tertiary'>Version {langeniusVersionInfo?.current_version}</div> - <div className='mb-4 text-center text-xs font-normal text-text-secondary'> + <div className='flex flex-col items-center gap-4 py-8'> + {systemFeatures.branding.enabled && systemFeatures.branding.workspace_logo + ? <img + src={systemFeatures.branding.workspace_logo} + className='block h-7 w-auto object-contain' + alt='logo' + /> + : <DifyLogo size='large' className='mx-auto' />} + + <div className='text-center text-xs font-normal text-text-tertiary'>Version {langeniusVersionInfo?.current_version}</div> + <div className='flex flex-col items-center gap-2 text-center text-xs font-normal text-text-secondary'> <div>© {dayjs().year()} LangGenius, Inc., Contributors.</div> <div className='text-text-accent'> { IS_CE_EDITION ? <Link href={'https://github.com/langgenius/dify/blob/main/LICENSE'} target='_blank' rel='noopener noreferrer'>Open Source License</Link> : <> - <Link href='https://dify.ai/privacy' target='_blank' rel='noopener noreferrer'>Privacy Policy</Link>,<span> </span> + <Link href='https://dify.ai/privacy' target='_blank' rel='noopener noreferrer'>Privacy Policy</Link>,  <Link href='https://dify.ai/terms' target='_blank' rel='noopener noreferrer'>Terms of Service</Link> </> } @@ -51,7 +60,7 @@ export default function AccountAbout({ </div> <div className='-mx-8 mb-4 h-[0.5px] bg-divider-regular' /> <div className='flex items-center justify-between'> - <div className='text-xs font-medium text-text-primary'> + <div className='text-xs font-medium text-text-tertiary'> { isLatest ? t('common.about.latestAvailable', { version: langeniusVersionInfo.latest_version }) @@ -59,7 +68,7 @@ export default function AccountAbout({ } </div> <div className='flex items-center'> - <Button className='mr-2'> + <Button className='mr-2' size='small'> <Link href={'https://github.com/langgenius/dify/releases'} target='_blank' rel='noopener noreferrer' @@ -69,7 +78,7 @@ export default function AccountAbout({ </Button> { !isLatest && !IS_CE_EDITION && ( - <Button variant='primary'> + <Button variant='primary' size='small'> <Link href={langeniusVersionInfo.release_notes} target='_blank' rel='noopener noreferrer' diff --git a/web/app/components/header/account-dropdown/index.tsx b/web/app/components/header/account-dropdown/index.tsx index 66b61d7ec1..08a61b7391 100644 --- a/web/app/components/header/account-dropdown/index.tsx +++ b/web/app/components/header/account-dropdown/index.tsx @@ -2,7 +2,6 @@ import { useTranslation } from 'react-i18next' import { Fragment, useState } from 'react' import { useRouter } from 'next/navigation' -import { useContext, useContextSelector } from 'use-context-selector' import { RiAccountCircleLine, RiArrowRightUpLine, @@ -14,6 +13,7 @@ import { RiMap2Line, RiSettings3Line, RiStarLine, + RiTShirt2Line, } from '@remixicon/react' import Link from 'next/link' import { Menu, MenuButton, MenuItem, MenuItems, Transition } from '@headlessui/react' @@ -23,16 +23,16 @@ import GithubStar from '../github-star' import Support from './support' import Compliance from './compliance' import PremiumBadge from '@/app/components/base/premium-badge' -import I18n from '@/context/i18n' import Avatar from '@/app/components/base/avatar' +import ThemeSwitcher from '@/app/components/base/theme-switcher' import { logout } from '@/service/common' -import AppContext, { useAppContext } from '@/context/app-context' +import { useAppContext } from '@/context/app-context' import { useProviderContext } from '@/context/provider-context' import { useModalContext } from '@/context/modal-context' -import { LanguagesSupported } from '@/i18n/language' -import { LicenseStatus } from '@/types/feature' import { IS_CLOUD_EDITION } from '@/config' import cn from '@/utils/classnames' +import { useGlobalPublicStore } from '@/context/global-public-context' +import { useDocLink } from '@/context/i18n' export default function AppSelector() { const itemClassName = ` @@ -41,10 +41,10 @@ export default function AppSelector() { ` const router = useRouter() const [aboutVisible, setAboutVisible] = useState(false) - const systemFeatures = useContextSelector(AppContext, v => v.systemFeatures) + const { systemFeatures } = useGlobalPublicStore() - const { locale } = useContext(I18n) const { t } = useTranslation() + const docLink = useDocLink() const { userProfile, langeniusVersionInfo, isCurrentWorkspaceOwner } = useAppContext() const { isEducationAccount } = useProviderContext() const { setShowAccountSettingModal } = useModalContext() @@ -83,8 +83,8 @@ export default function AppSelector() { <MenuItems className=" absolute right-0 mt-1.5 w-60 max-w-80 - origin-top-right divide-y divide-divider-subtle rounded-xl bg-components-panel-bg-blur - shadow-lg focus:outline-none + origin-top-right divide-y divide-divider-subtle rounded-xl bg-components-panel-bg-blur shadow-lg + backdrop-blur-sm focus:outline-none " > <MenuItem disabled> @@ -126,69 +126,78 @@ export default function AppSelector() { </div> </MenuItem> </div> - <div className='p-1'> - <MenuItem> - <Link - className={cn(itemClassName, 'group justify-between', - 'data-[active]:bg-state-base-hover', - )} - href={ - locale !== LanguagesSupported[1] ? 'https://docs.dify.ai/' : `https://docs.dify.ai/v/${locale.toLowerCase()}/` - } - target='_blank' rel='noopener noreferrer'> - <RiBookOpenLine className='size-4 shrink-0 text-text-tertiary' /> - <div className='system-md-regular grow px-1 text-text-secondary'>{t('common.userProfile.helpCenter')}</div> - <RiArrowRightUpLine className='size-[14px] shrink-0 text-text-tertiary' /> - </Link> - </MenuItem> - <Support /> - {IS_CLOUD_EDITION && isCurrentWorkspaceOwner && <Compliance />} - </div> - <div className='p-1'> - <MenuItem> - <Link - className={cn(itemClassName, 'group justify-between', - 'data-[active]:bg-state-base-hover', - )} - href='https://roadmap.dify.ai' - target='_blank' rel='noopener noreferrer'> - <RiMap2Line className='size-4 shrink-0 text-text-tertiary' /> - <div className='system-md-regular grow px-1 text-text-secondary'>{t('common.userProfile.roadmap')}</div> - <RiArrowRightUpLine className='size-[14px] shrink-0 text-text-tertiary' /> - </Link> - </MenuItem> - {systemFeatures.license.status === LicenseStatus.NONE && <MenuItem> - <Link - className={cn(itemClassName, 'group justify-between', - 'data-[active]:bg-state-base-hover', - )} - href='https://github.com/langgenius/dify' - target='_blank' rel='noopener noreferrer'> - <RiGithubLine className='size-4 shrink-0 text-text-tertiary' /> - <div className='system-md-regular grow px-1 text-text-secondary'>{t('common.userProfile.github')}</div> - <div className='flex items-center gap-0.5 rounded-[5px] border border-divider-deep bg-components-badge-bg-dimm px-[5px] py-[3px]'> - <RiStarLine className='size-3 shrink-0 text-text-tertiary' /> - <GithubStar className='system-2xs-medium-uppercase text-text-tertiary' /> - </div> - </Link> - </MenuItem>} - { - document?.body?.getAttribute('data-public-site-about') !== 'hide' && ( - <MenuItem> - <div className={cn(itemClassName, 'justify-between', + {!systemFeatures.branding.enabled && <> + <div className='p-1'> + <MenuItem> + <Link + className={cn(itemClassName, 'group justify-between', 'data-[active]:bg-state-base-hover', - )} onClick={() => setAboutVisible(true)}> - <RiInformation2Line className='size-4 shrink-0 text-text-tertiary' /> - <div className='system-md-regular grow px-1 text-text-secondary'>{t('common.userProfile.about')}</div> - <div className='flex shrink-0 items-center'> - <div className='system-xs-regular mr-2 text-text-tertiary'>{langeniusVersionInfo.current_version}</div> - <Indicator color={langeniusVersionInfo.current_version === langeniusVersionInfo.latest_version ? 'green' : 'orange'} /> - </div> + )} + href={docLink('/introduction')} + target='_blank' rel='noopener noreferrer'> + <RiBookOpenLine className='size-4 shrink-0 text-text-tertiary' /> + <div className='system-md-regular grow px-1 text-text-secondary'>{t('common.userProfile.helpCenter')}</div> + <RiArrowRightUpLine className='size-[14px] shrink-0 text-text-tertiary' /> + </Link> + </MenuItem> + <Support /> + {IS_CLOUD_EDITION && isCurrentWorkspaceOwner && <Compliance />} + </div> + <div className='p-1'> + <MenuItem> + <Link + className={cn(itemClassName, 'group justify-between', + 'data-[active]:bg-state-base-hover', + )} + href='https://roadmap.dify.ai' + target='_blank' rel='noopener noreferrer'> + <RiMap2Line className='size-4 shrink-0 text-text-tertiary' /> + <div className='system-md-regular grow px-1 text-text-secondary'>{t('common.userProfile.roadmap')}</div> + <RiArrowRightUpLine className='size-[14px] shrink-0 text-text-tertiary' /> + </Link> + </MenuItem> + <MenuItem> + <Link + className={cn(itemClassName, 'group justify-between', + 'data-[active]:bg-state-base-hover', + )} + href='https://github.com/langgenius/dify' + target='_blank' rel='noopener noreferrer'> + <RiGithubLine className='size-4 shrink-0 text-text-tertiary' /> + <div className='system-md-regular grow px-1 text-text-secondary'>{t('common.userProfile.github')}</div> + <div className='flex items-center gap-0.5 rounded-[5px] border border-divider-deep bg-components-badge-bg-dimm px-[5px] py-[3px]'> + <RiStarLine className='size-3 shrink-0 text-text-tertiary' /> + <GithubStar className='system-2xs-medium-uppercase text-text-tertiary' /> </div> - </MenuItem> - ) - } - </div> + </Link> + </MenuItem> + { + document?.body?.getAttribute('data-public-site-about') !== 'hide' && ( + <MenuItem> + <div className={cn(itemClassName, 'justify-between', + 'data-[active]:bg-state-base-hover', + )} onClick={() => setAboutVisible(true)}> + <RiInformation2Line className='size-4 shrink-0 text-text-tertiary' /> + <div className='system-md-regular grow px-1 text-text-secondary'>{t('common.userProfile.about')}</div> + <div className='flex shrink-0 items-center'> + <div className='system-xs-regular mr-2 text-text-tertiary'>{langeniusVersionInfo.current_version}</div> + <Indicator color={langeniusVersionInfo.current_version === langeniusVersionInfo.latest_version ? 'green' : 'orange'} /> + </div> + </div> + </MenuItem> + ) + } + </div> + </>} + <MenuItem disabled> + <div className='p-1'> + <div className={cn(itemClassName, 'hover:bg-transparent')}> + <RiTShirt2Line className='size-4 shrink-0 text-text-tertiary' /> + <div className='system-md-regular grow px-1 text-text-secondary'>{t('common.theme.theme')}</div> + <ThemeSwitcher /> + </div> + </div> + </MenuItem> <MenuItem> <div className='p-1' onClick={() => handleLogout()}> <div diff --git a/web/app/components/header/account-dropdown/workplace-selector/index.module.css b/web/app/components/header/account-dropdown/workplace-selector/index.module.css index f3840ea018..c3184f7e18 100644 --- a/web/app/components/header/account-dropdown/workplace-selector/index.module.css +++ b/web/app/components/header/account-dropdown/workplace-selector/index.module.css @@ -2,4 +2,4 @@ left: 4px; transform: translateX(-100%); box-shadow: 0px 12px 16px -4px rgba(16, 24, 40, 0.08), 0px 4px 6px -2px rgba(16, 24, 40, 0.03); -} \ No newline at end of file +} diff --git a/web/app/components/header/account-dropdown/workplace-selector/index.tsx b/web/app/components/header/account-dropdown/workplace-selector/index.tsx index da3f8bae6d..98668c61d2 100644 --- a/web/app/components/header/account-dropdown/workplace-selector/index.tsx +++ b/web/app/components/header/account-dropdown/workplace-selector/index.tsx @@ -61,7 +61,8 @@ const WorkplaceSelector = () => { <MenuItems className={cn( ` - shadows-shadow-lg absolute left-[-15px] mt-1 flex w-[280px] flex-col items-start rounded-xl bg-components-panel-bg-blur backdrop-blur-[5px] + shadows-shadow-lg absolute left-[-15px] mt-1 flex max-h-[400px] w-[280px] flex-col items-start overflow-y-auto rounded-xl + bg-components-panel-bg-blur backdrop-blur-[5px] `, )} > diff --git a/web/app/components/header/account-setting/Integrations-page/index.module.css b/web/app/components/header/account-setting/Integrations-page/index.module.css index 5b7ef2cce3..7c2a883639 100644 --- a/web/app/components/header/account-setting/Integrations-page/index.module.css +++ b/web/app/components/header/account-setting/Integrations-page/index.module.css @@ -21,4 +21,4 @@ .salesforce-icon { background: url(../../assets/salesforce.svg) center center no-repeat; background-size: 24px auto; -} \ No newline at end of file +} diff --git a/web/app/components/header/account-setting/data-source-page/panel/style.module.css b/web/app/components/header/account-setting/data-source-page/panel/style.module.css index a11d4758fb..ac9be02205 100644 --- a/web/app/components/header/account-setting/data-source-page/panel/style.module.css +++ b/web/app/components/header/account-setting/data-source-page/panel/style.module.css @@ -14,4 +14,4 @@ .workspace-item:last-of-type { margin-bottom: 0; -} \ No newline at end of file +} diff --git a/web/app/components/header/account-setting/language-page/index.module.css b/web/app/components/header/account-setting/language-page/index.module.css index 5b7ef2cce3..7c2a883639 100644 --- a/web/app/components/header/account-setting/language-page/index.module.css +++ b/web/app/components/header/account-setting/language-page/index.module.css @@ -21,4 +21,4 @@ .salesforce-icon { background: url(../../assets/salesforce.svg) center center no-repeat; background-size: 24px auto; -} \ No newline at end of file +} diff --git a/web/app/components/header/account-setting/members-page/index.tsx b/web/app/components/header/account-setting/members-page/index.tsx index 939834edeb..df7aae6b62 100644 --- a/web/app/components/header/account-setting/members-page/index.tsx +++ b/web/app/components/header/account-setting/members-page/index.tsx @@ -25,6 +25,7 @@ import { LanguagesSupported } from '@/i18n/language' import cn from '@/utils/classnames' import Tooltip from '@/app/components/base/tooltip' import { RiPencilLine } from '@remixicon/react' +import { useGlobalPublicStore } from '@/context/global-public-context' dayjs.extend(relativeTime) const MembersPage = () => { @@ -38,7 +39,7 @@ const MembersPage = () => { } const { locale } = useContext(I18n) - const { userProfile, currentWorkspace, isCurrentWorkspaceOwner, isCurrentWorkspaceManager, systemFeatures } = useAppContext() + const { userProfile, currentWorkspace, isCurrentWorkspaceOwner, isCurrentWorkspaceManager } = useAppContext() const { data, mutate } = useSWR( { url: '/workspaces/current/members', @@ -46,6 +47,7 @@ const MembersPage = () => { }, fetchMembers, ) + const { systemFeatures } = useGlobalPublicStore() const [inviteModalVisible, setInviteModalVisible] = useState(false) const [invitationResults, setInvitationResults] = useState<InvitationResult[]>([]) const [invitedModalVisible, setInvitedModalVisible] = useState(false) diff --git a/web/app/components/header/account-setting/members-page/invite-modal/index.tsx b/web/app/components/header/account-setting/members-page/invite-modal/index.tsx index 107166bc31..e999253ea7 100644 --- a/web/app/components/header/account-setting/members-page/invite-modal/index.tsx +++ b/web/app/components/header/account-setting/members-page/invite-modal/index.tsx @@ -1,5 +1,5 @@ 'use client' -import { useCallback, useState } from 'react' +import { useCallback, useEffect, useState } from 'react' import { useContext } from 'use-context-selector' import { RiCloseLine } from '@remixicon/react' import { useTranslation } from 'react-i18next' @@ -18,6 +18,7 @@ import I18n from '@/context/i18n' import 'react-multi-email/dist/style.css' import { noop } from 'lodash-es' +import { useProviderContextSelector } from '@/context/provider-context' type IInviteModalProps = { isEmailSetup: boolean onCancel: () => void @@ -30,13 +31,27 @@ const InviteModal = ({ onSend, }: IInviteModalProps) => { const { t } = useTranslation() + const licenseLimit = useProviderContextSelector(s => s.licenseLimit) + const refreshLicenseLimit = useProviderContextSelector(s => s.refreshLicenseLimit) const [emails, setEmails] = useState<string[]>([]) const { notify } = useContext(ToastContext) + const [isLimited, setIsLimited] = useState(false) + const [isLimitExceeded, setIsLimitExceeded] = useState(false) + const [usedSize, setUsedSize] = useState(licenseLimit.workspace_members.size ?? 0) + useEffect(() => { + const limited = licenseLimit.workspace_members.limit > 0 + const used = emails.length + licenseLimit.workspace_members.size + setIsLimited(limited) + setUsedSize(used) + setIsLimitExceeded(limited && (used > licenseLimit.workspace_members.limit)) + }, [licenseLimit, emails]) const { locale } = useContext(I18n) const [role, setRole] = useState<string>('normal') const handleSend = useCallback(async () => { + if (isLimitExceeded) + return if (emails.map((email: string) => emailRegex.test(email)).every(Boolean)) { try { const { result, invitation_results } = await inviteMember({ @@ -45,6 +60,7 @@ const InviteModal = ({ }) if (result === 'success') { + refreshLicenseLimit() onCancel() onSend(invitation_results) } @@ -54,7 +70,7 @@ const InviteModal = ({ else { notify({ type: 'error', message: t('common.members.emailInvalid') }) } - }, [role, emails, notify, onCancel, onSend, t]) + }, [isLimitExceeded, emails, role, locale, onCancel, onSend, notify, t]) return ( <div className={cn(s.wrap)}> @@ -82,7 +98,7 @@ const InviteModal = ({ <div> <div className='mb-2 text-sm font-medium text-text-primary'>{t('common.members.email')}</div> - <div className='mb-8 flex h-36 items-stretch'> + <div className='mb-8 flex h-36 flex-col items-stretch'> <ReactMultiEmail className={cn('w-full border-components-input-border-active !bg-components-input-bg-normal px-3 pt-2 outline-none', 'appearance-none overflow-y-auto rounded-lg text-sm !text-text-primary', @@ -101,6 +117,14 @@ const InviteModal = ({ } placeholder={t('common.members.emailPlaceholder') || ''} /> + <div className={ + cn('system-xs-regular flex items-center justify-end text-text-tertiary', + (isLimited && usedSize > licenseLimit.workspace_members.limit) ? 'text-text-destructive' : '')} + > + <span>{usedSize}</span> + <span>/</span> + <span>{isLimited ? licenseLimit.workspace_members.limit : t('common.license.unlimited')}</span> + </div> </div> <div className='mb-6'> <RoleSelector value={role} onChange={setRole} /> @@ -109,7 +133,7 @@ const InviteModal = ({ tabIndex={0} className='w-full' onClick={handleSend} - disabled={!emails.length} + disabled={!emails.length || isLimitExceeded} variant='primary' > {t('common.members.sendInvite')} diff --git a/web/app/components/header/account-setting/members-page/invited-modal/index.module.css b/web/app/components/header/account-setting/members-page/invited-modal/index.module.css index 506b6a0c89..96470439f0 100644 --- a/web/app/components/header/account-setting/members-page/invited-modal/index.module.css +++ b/web/app/components/header/account-setting/members-page/invited-modal/index.module.css @@ -18,4 +18,4 @@ .copyIcon.copied { background-image: url(./assets/copied.svg); -} \ No newline at end of file +} diff --git a/web/app/components/header/account-setting/members-page/invited-modal/invitation-link.tsx b/web/app/components/header/account-setting/members-page/invited-modal/invitation-link.tsx index 0ef1b14569..68a575f503 100644 --- a/web/app/components/header/account-setting/members-page/invited-modal/invitation-link.tsx +++ b/web/app/components/header/account-setting/members-page/invited-modal/invitation-link.tsx @@ -1,6 +1,5 @@ 'use client' import React, { useCallback, useEffect, useRef, useState } from 'react' -import { basePath } from '@/utils/var' import { t } from 'i18next' import copy from 'copy-to-clipboard' import s from './index.module.css' @@ -19,7 +18,8 @@ const InvitationLink = ({ const selector = useRef(`invite-link-${randomString(4)}`) const copyHandle = useCallback(() => { - copy(`${!value.url.startsWith('http') ? window.location.origin : ''}${basePath}${value.url}`) + // No prefix is needed here because the backend has already processed it + copy(`${!value.url.startsWith('http') ? window.location.origin : ''}${value.url}`) setIsCopied(true) }, [value]) @@ -42,7 +42,7 @@ const InvitationLink = ({ <Tooltip popupContent={isCopied ? `${t('appApi.copied')}` : `${t('appApi.copy')}`} > - <div className='r-0 absolute left-0 top-0 w-full cursor-pointer truncate pl-2 pr-2' onClick={copyHandle}>{basePath + value.url}</div> + <div className='r-0 absolute left-0 top-0 w-full cursor-pointer truncate pl-2 pr-2 text-text-primary' onClick={copyHandle}>{value.url}</div> </Tooltip> </div> <div className="h-4 shrink-0 border bg-divider-regular" /> diff --git a/web/app/components/header/account-setting/model-provider-page/index.tsx b/web/app/components/header/account-setting/model-provider-page/index.tsx index 7c4e2eac3c..4aa98daf66 100644 --- a/web/app/components/header/account-setting/model-provider-page/index.tsx +++ b/web/app/components/header/account-setting/model-provider-page/index.tsx @@ -23,7 +23,7 @@ import { import InstallFromMarketplace from './install-from-marketplace' import { useProviderContext } from '@/context/provider-context' import cn from '@/utils/classnames' -import { useSelector as useAppContextSelector } from '@/context/app-context' +import { useGlobalPublicStore } from '@/context/global-public-context' type Props = { searchText: string @@ -40,7 +40,7 @@ const ModelProviderPage = ({ searchText }: Props) => { const { data: speech2textDefaultModel } = useDefaultModel(ModelTypeEnum.speech2text) const { data: ttsDefaultModel } = useDefaultModel(ModelTypeEnum.tts) const { modelProviders: providers } = useProviderContext() - const { enable_marketplace } = useAppContextSelector(s => s.systemFeatures) + const { enable_marketplace } = useGlobalPublicStore(s => s.systemFeatures) const defaultModelNotConfigured = !textGenerationDefaultModel && !embeddingsDefaultModel && !speech2textDefaultModel && !rerankDefaultModel && !ttsDefaultModel const [configuredProviders, notConfiguredProviders] = useMemo(() => { const configuredProviders: ModelProvider[] = [] diff --git a/web/app/components/header/account-setting/model-provider-page/install-from-marketplace.tsx b/web/app/components/header/account-setting/model-provider-page/install-from-marketplace.tsx index 38f35d6fb4..818007a26f 100644 --- a/web/app/components/header/account-setting/model-provider-page/install-from-marketplace.tsx +++ b/web/app/components/header/account-setting/model-provider-page/install-from-marketplace.tsx @@ -1,4 +1,5 @@ import { useCallback, useState } from 'react' +import { useTheme } from 'next-themes' import { useTranslation } from 'react-i18next' import Link from 'next/link' import { @@ -29,6 +30,7 @@ const InstallFromMarketplace = ({ searchText, }: InstallFromMarketplaceProps) => { const { t } = useTranslation() + const { theme } = useTheme() const [collapse, setCollapse] = useState(false) const locale = getLocaleOnClient() const { @@ -53,7 +55,7 @@ const InstallFromMarketplace = ({ </div> <div className='mb-2 flex items-center pt-2'> <span className='system-sm-regular pr-1 text-text-tertiary'>{t('common.modelProvider.discoverMore')}</span> - <Link target="_blank" href={`${MARKETPLACE_URL_PREFIX}`} className='system-sm-medium inline-flex items-center text-text-accent'> + <Link target="_blank" href={`${MARKETPLACE_URL_PREFIX}${theme ? `?theme=${theme}` : ''}`} className='system-sm-medium inline-flex items-center text-text-accent'> {t('plugin.marketplace.difyMarketplace')} <RiArrowRightUpLine className='h-4 w-4' /> </Link> diff --git a/web/app/components/header/account-setting/model-provider-page/model-icon/index.tsx b/web/app/components/header/account-setting/model-provider-page/model-icon/index.tsx index 9019051989..df9a9cbcf7 100644 --- a/web/app/components/header/account-setting/model-provider-page/model-icon/index.tsx +++ b/web/app/components/header/account-setting/model-provider-page/model-icon/index.tsx @@ -3,10 +3,9 @@ import type { Model, ModelProvider, } from '../declarations' -import { basePath } from '@/utils/var' import { useLanguage } from '../hooks' import { Group } from '@/app/components/base/icons/src/vender/other' -import { OpenaiBlue, OpenaiViolet } from '@/app/components/base/icons/src/public/llm' +import { OpenaiBlue, OpenaiTeal, OpenaiViolet, OpenaiYellow } from '@/app/components/base/icons/src/public/llm' import cn from '@/utils/classnames' import { renderI18nObject } from '@/i18n' @@ -23,6 +22,10 @@ const ModelIcon: FC<ModelIconProps> = ({ isDeprecated = false, }) => { const language = useLanguage() + if (provider?.provider && ['openai', 'langgenius/openai/openai'].includes(provider.provider) && modelName?.startsWith('o')) + return <div className='flex items-center justify-center'><OpenaiYellow className={cn('h-5 w-5', className)} /></div> + if (provider?.provider && ['openai', 'langgenius/openai/openai'].includes(provider.provider) && modelName?.includes('gpt-4.1')) + return <div className='flex items-center justify-center'><OpenaiTeal className={cn('h-5 w-5', className)} /></div> if (provider?.provider && ['openai', 'langgenius/openai/openai'].includes(provider.provider) && modelName?.includes('gpt-4o')) return <div className='flex items-center justify-center'><OpenaiBlue className={cn('h-5 w-5', className)} /></div> if (provider?.provider && ['openai', 'langgenius/openai/openai'].includes(provider.provider) && modelName?.startsWith('gpt-4')) @@ -31,7 +34,7 @@ const ModelIcon: FC<ModelIconProps> = ({ if (provider?.icon_small) { return ( <div className={cn('flex h-5 w-5 items-center justify-center', isDeprecated && 'opacity-50', className)}> - <img alt='model-icon' src={basePath + renderI18nObject(provider.icon_small, language)}/> + <img alt='model-icon' src={renderI18nObject(provider.icon_small, language)}/> </div> ) } diff --git a/web/app/components/header/account-setting/model-provider-page/model-modal/Input.tsx b/web/app/components/header/account-setting/model-provider-page/model-modal/Input.tsx index f339f535d7..a19e330315 100644 --- a/web/app/components/header/account-setting/model-provider-page/model-modal/Input.tsx +++ b/web/app/components/header/account-setting/model-provider-page/model-modal/Input.tsx @@ -39,6 +39,7 @@ const Input: FC<InputProps> = ({ return ( <div className='relative'> <input + autoComplete="new-password" tabIndex={0} className={` block h-8 w-full appearance-none rounded-lg border border-transparent bg-components-input-bg-normal px-3 text-sm diff --git a/web/app/components/header/account-setting/model-provider-page/provider-icon/index.tsx b/web/app/components/header/account-setting/model-provider-page/provider-icon/index.tsx index 9dd4af468d..253269d920 100644 --- a/web/app/components/header/account-setting/model-provider-page/provider-icon/index.tsx +++ b/web/app/components/header/account-setting/model-provider-page/provider-icon/index.tsx @@ -1,6 +1,5 @@ import type { FC } from 'react' import type { ModelProvider } from '../declarations' -import { basePath } from '@/utils/var' import { useLanguage } from '../hooks' import { Openai } from '@/app/components/base/icons/src/vender/other' import { AnthropicDark, AnthropicLight } from '@/app/components/base/icons/src/public/llm' @@ -41,7 +40,7 @@ const ProviderIcon: FC<ProviderIconProps> = ({ <div className={cn('inline-flex items-center gap-2', className)}> <img alt='provider-icon' - src={basePath + renderI18nObject(provider.icon_small, language)} + src={renderI18nObject(provider.icon_small, language)} className='h-6 w-6' /> <div className='system-md-semibold text-text-primary'> diff --git a/web/app/components/header/dataset-nav/index.tsx b/web/app/components/header/dataset-nav/index.tsx index 8c997f2115..4894a62484 100644 --- a/web/app/components/header/dataset-nav/index.tsx +++ b/web/app/components/header/dataset-nav/index.tsx @@ -14,6 +14,7 @@ import Nav from '../nav' import type { NavItem } from '../nav/nav-selector' import { fetchDatasetDetail, fetchDatasets } from '@/service/datasets' import type { DataSetListResponse } from '@/models/datasets' +import { basePath } from '@/utils/var' const getKey = (pageIndex: number, previousPageData: DataSetListResponse) => { if (!pageIndex || previousPageData.has_more) @@ -56,7 +57,7 @@ const DatasetNav = () => { icon_background: dataset.icon_background, })) as NavItem[]} createText={t('common.menus.newDataset')} - onCreate={() => router.push('/datasets/create')} + onCreate={() => router.push(`${basePath}/datasets/create`)} onLoadmore={handleLoadmore} /> ) diff --git a/web/app/components/header/github-star/index.tsx b/web/app/components/header/github-star/index.tsx index b087b9e41a..e825dcdd14 100644 --- a/web/app/components/header/github-star/index.tsx +++ b/web/app/components/header/github-star/index.tsx @@ -2,6 +2,11 @@ import { useQuery } from '@tanstack/react-query' import type { FC } from 'react' import type { GithubRepo } from '@/models/common' +import { RiLoader2Line } from '@remixicon/react' + +const defaultData = { + stargazers_count: 98570, +} const getStar = async () => { const res = await fetch('https://api.github.com/repos/langgenius/dify') @@ -13,15 +18,21 @@ const getStar = async () => { } const GithubStar: FC<{ className: string }> = (props) => { - const { isFetching, data } = useQuery<GithubRepo>({ + const { isFetching, isError, data } = useQuery<GithubRepo>({ queryKey: ['github-star'], queryFn: getStar, enabled: process.env.NODE_ENV !== 'development', - initialData: { stargazers_count: 81204 }, + retry: false, + placeholderData: defaultData, }) + if (isFetching) - return null - return <span {...props}>{data.stargazers_count.toLocaleString()}</span> + return <RiLoader2Line className='size-3 shrink-0 animate-spin text-text-tertiary' /> + + if (isError) + return <span {...props}>{defaultData.stargazers_count.toLocaleString()}</span> + + return <span {...props}>{data?.stargazers_count.toLocaleString()}</span> } export default GithubStar diff --git a/web/app/components/header/index.module.css b/web/app/components/header/index.module.css index a95938a3ec..9e23bc10a6 100644 --- a/web/app/components/header/index.module.css +++ b/web/app/components/header/index.module.css @@ -12,4 +12,4 @@ width: 12px; height: 12px; background: url(./assets/alpha.svg) center center no-repeat; -} \ No newline at end of file +} diff --git a/web/app/components/header/index.tsx b/web/app/components/header/index.tsx index 13c587dc19..a9c26e0070 100644 --- a/web/app/components/header/index.tsx +++ b/web/app/components/header/index.tsx @@ -13,7 +13,7 @@ import ExploreNav from './explore-nav' import ToolsNav from './tools-nav' import { WorkspaceProvider } from '@/context/workspace-context' import { useAppContext } from '@/context/app-context' -import LogoSite from '@/app/components/base/logo/logo-site' +import DifyLogo from '@/app/components/base/logo/dify-logo' import WorkplaceSelector from '@/app/components/header/account-dropdown/workplace-selector' import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints' import { useProviderContext } from '@/context/provider-context' @@ -21,6 +21,7 @@ import { useModalContext } from '@/context/modal-context' import PlanBadge from './plan-badge' import LicenseNav from './license-env' import { Plan } from '../billing/type' +import { useGlobalPublicStore } from '@/context/global-public-context' const navClassName = ` flex items-center relative mr-0 sm:mr-3 px-3 h-8 rounded-xl @@ -36,6 +37,7 @@ const Header = () => { const [isShowNavMenu, { toggle, setFalse: hideNavMenu }] = useBoolean(false) const { enableBilling, plan } = useProviderContext() const { setShowPricingModal, setShowAccountSettingModal } = useModalContext() + const systemFeatures = useGlobalPublicStore(s => s.systemFeatures) const isFreePlan = plan.type === Plan.sandbox const handlePlanClick = useCallback(() => { if (isFreePlan) @@ -49,7 +51,7 @@ const Header = () => { // eslint-disable-next-line react-hooks/exhaustive-deps }, [selectedSegment]) return ( - <div className='flex flex-1 items-center justify-between bg-background-body px-4'> + <div className='relative flex flex-1 items-center justify-between bg-background-body'> <div className='flex items-center'> {isMobile && <div className='flex h-8 w-8 cursor-pointer items-center justify-center' @@ -59,9 +61,15 @@ const Header = () => { </div>} { !isMobile - && <div className='flex w-64 shrink-0 items-center gap-1.5 self-stretch p-2 pl-3'> - <Link href="/apps" className='flex h-8 w-8 shrink-0 items-center justify-center gap-2'> - <LogoSite className='object-contain' /> + && <div className='flex shrink-0 items-center gap-1.5 self-stretch pl-3'> + <Link href="/apps" className='flex h-8 shrink-0 items-center justify-center gap-2 px-0.5'> + {systemFeatures.branding.enabled && systemFeatures.branding.workspace_logo + ? <img + src={systemFeatures.branding.workspace_logo} + className='block h-[22px] w-auto object-contain' + alt='logo' + /> + : <DifyLogo />} </Link> <div className='font-light text-divider-deep'>/</div> <div className='flex items-center gap-0.5'> @@ -76,7 +84,13 @@ const Header = () => { {isMobile && ( <div className='flex'> <Link href="/apps" className='mr-4 flex items-center'> - <LogoSite /> + {systemFeatures.branding.enabled && systemFeatures.branding.workspace_logo + ? <img + src={systemFeatures.branding.workspace_logo} + className='block h-[22px] w-auto object-contain' + alt='logo' + /> + : <DifyLogo />} </Link> <div className='font-light text-divider-deep'>/</div> {enableBilling ? <PlanBadge allowHover sandboxAsUpgrade plan={plan.type} onClick={handlePlanClick} /> : <LicenseNav />} @@ -84,7 +98,7 @@ const Header = () => { )} { !isMobile && ( - <div className='flex items-center'> + <div className='absolute left-1/2 top-1/2 flex -translate-x-1/2 -translate-y-1/2 items-center'> {!isCurrentWorkspaceDatasetOperator && <ExploreNav className={navClassName} />} {!isCurrentWorkspaceDatasetOperator && <AppNav />} {(isCurrentWorkspaceEditor || isCurrentWorkspaceDatasetOperator) && <DatasetNav />} @@ -92,7 +106,7 @@ const Header = () => { </div> ) } - <div className='flex shrink-0 items-center'> + <div className='flex shrink-0 items-center pr-3'> <EnvNav /> <div className='mr-2'> <PluginsNav /> diff --git a/web/app/components/header/license-env/index.tsx b/web/app/components/header/license-env/index.tsx index 86c53d7c83..8946143415 100644 --- a/web/app/components/header/license-env/index.tsx +++ b/web/app/components/header/license-env/index.tsx @@ -1,16 +1,15 @@ 'use client' -import AppContext from '@/context/app-context' import { LicenseStatus } from '@/types/feature' import { useTranslation } from 'react-i18next' -import { useContextSelector } from 'use-context-selector' import dayjs from 'dayjs' import PremiumBadge from '../../base/premium-badge' import { RiHourglass2Fill } from '@remixicon/react' +import { useGlobalPublicStore } from '@/context/global-public-context' const LicenseNav = () => { const { t } = useTranslation() - const systemFeatures = useContextSelector(AppContext, s => s.systemFeatures) + const { systemFeatures } = useGlobalPublicStore() if (systemFeatures.license?.status === LicenseStatus.EXPIRING) { const expiredAt = systemFeatures.license?.expired_at diff --git a/web/app/components/header/maintenance-notice.tsx b/web/app/components/header/maintenance-notice.tsx index 78715bb53e..f9c00dd01e 100644 --- a/web/app/components/header/maintenance-notice.tsx +++ b/web/app/components/header/maintenance-notice.tsx @@ -1,11 +1,10 @@ import { useState } from 'react' -import { useContext } from 'use-context-selector' -import I18n from '@/context/i18n' import { X } from '@/app/components/base/icons/src/vender/line/general' import { NOTICE_I18N } from '@/i18n/language' +import { useLanguage } from '@/app/components/header/account-setting/model-provider-page/hooks' const MaintenanceNotice = () => { - const { locale } = useContext(I18n) + const locale = useLanguage() const [showNotice, setShowNotice] = useState(localStorage.getItem('hide-maintenance-notice') !== '1') const handleJumpNotice = () => { diff --git a/web/app/components/header/nav/nav-selector/index.tsx b/web/app/components/header/nav/nav-selector/index.tsx index 3af75ab009..473abf03c1 100644 --- a/web/app/components/header/nav/nav-selector/index.tsx +++ b/web/app/components/header/nav/nav-selector/index.tsx @@ -6,7 +6,7 @@ import { RiArrowDownSLine, RiArrowRightSLine, } from '@remixicon/react' -import { Menu, MenuButton, MenuItems, Transition } from '@headlessui/react' +import { Menu, MenuButton, MenuItem, MenuItems, Transition } from '@headlessui/react' import { useRouter } from 'next/navigation' import { debounce } from 'lodash-es' import cn from '@/utils/classnames' @@ -70,15 +70,15 @@ const NavSelector = ({ curNav, navs, createText, isApp, onCreate, onLoadmore }: <MenuItems className=" absolute -left-11 right-0 mt-1.5 w-60 max-w-80 - origin-top-right divide-y divide-gray-100 rounded-lg bg-white + origin-top-right divide-y divide-divider-regular rounded-lg bg-components-panel-bg-blur shadow-lg " > <div className="overflow-auto px-1 py-1" style={{ maxHeight: '50vh' }} onScroll={handleScroll}> { navs.map(nav => ( - <MenuItems key={nav.id}> - <div className='flex w-full cursor-pointer items-center truncate rounded-lg px-3 py-[6px] text-[14px] font-normal text-gray-700 hover:bg-gray-100' onClick={() => { + <MenuItem key={nav.id}> + <div className='flex w-full cursor-pointer items-center truncate rounded-lg px-3 py-[6px] text-[14px] font-normal text-text-secondary hover:bg-state-base-hover' onClick={() => { if (curNav?.id === nav.id) return setAppDetail() @@ -112,21 +112,21 @@ const NavSelector = ({ curNav, navs, createText, isApp, onCreate, onLoadmore }: {nav.name} </div> </div> - </MenuItems> + </MenuItem> )) } </div> {!isApp && isCurrentWorkspaceEditor && ( - <MenuButton className='w-full p-1'> + <MenuItem as="div" className='w-full p-1'> <div onClick={() => onCreate('')} className={cn( - 'flex cursor-pointer items-center gap-2 rounded-lg px-3 py-[6px] hover:bg-gray-100', + 'flex cursor-pointer items-center gap-2 rounded-lg px-3 py-[6px] hover:bg-state-base-hover ', )}> - <div className='flex h-6 w-6 shrink-0 items-center justify-center rounded-[6px] border border-[0.5px] border-gray-200 bg-gray-50'> - <RiAddLine className='h-4 w-4 text-gray-500' /> + <div className='flex h-6 w-6 shrink-0 items-center justify-center rounded-[6px] border-[0.5px] border-divider-regular bg-background-default'> + <RiAddLine className='h-4 w-4 text-text-primary' /> </div> - <div className='grow text-left text-[14px] font-normal text-gray-700'>{createText}</div> + <div className='grow text-left text-[14px] font-normal text-text-secondary'>{createText}</div> </div> - </MenuButton> + </MenuItem> )} {isApp && isCurrentWorkspaceEditor && ( <Menu as="div" className="relative h-full w-full"> @@ -134,14 +134,14 @@ const NavSelector = ({ curNav, navs, createText, isApp, onCreate, onLoadmore }: <> <MenuButton className='w-full p-1'> <div className={cn( - 'flex cursor-pointer items-center gap-2 rounded-lg px-3 py-[6px] hover:bg-gray-100', - open && '!bg-gray-100', + 'flex cursor-pointer items-center gap-2 rounded-lg px-3 py-[6px] hover:bg-state-base-hover', + open && '!bg-state-base-hover', )}> - <div className='flex h-6 w-6 shrink-0 items-center justify-center rounded-[6px] border border-[0.5px] border-gray-200 bg-gray-50'> - <RiAddLine className='h-4 w-4 text-gray-500' /> + <div className='flex h-6 w-6 shrink-0 items-center justify-center rounded-[6px] border-[0.5px] border-divider-regular bg-background-default'> + <RiAddLine className='h-4 w-4 text-text-primary' /> </div> - <div className='grow text-left text-[14px] font-normal text-gray-700'>{createText}</div> - <RiArrowRightSLine className='h-3.5 w-3.5 shrink-0 text-gray-500' /> + <div className='grow text-left text-[14px] font-normal text-text-secondary'>{createText}</div> + <RiArrowRightSLine className='h-3.5 w-3.5 shrink-0 text-text-primary' /> </div> </MenuButton> <Transition @@ -154,21 +154,21 @@ const NavSelector = ({ curNav, navs, createText, isApp, onCreate, onLoadmore }: leaveTo="transform opacity-0 scale-95" > <MenuItems className={cn( - 'absolute right-[-198px] top-[3px] z-10 min-w-[200px] rounded-lg border-[0.5px] border-gray-200 bg-white shadow-lg', + 'absolute right-[-198px] top-[3px] z-10 min-w-[200px] rounded-lg bg-components-panel-bg-blur shadow-lg', )}> <div className='p-1'> - <div className={cn('flex cursor-pointer items-center rounded-lg px-3 py-[6px] font-normal text-gray-700 hover:bg-gray-100')} onClick={() => onCreate('blank')}> - <FilePlus01 className='mr-2 h-4 w-4 shrink-0 text-gray-600' /> + <div className={cn('flex cursor-pointer items-center rounded-lg px-3 py-[6px] font-normal text-text-secondary hover:bg-state-base-hover')} onClick={() => onCreate('blank')}> + <FilePlus01 className='mr-2 h-4 w-4 shrink-0 text-text-secondary' /> {t('app.newApp.startFromBlank')} </div> - <div className={cn('flex cursor-pointer items-center rounded-lg px-3 py-[6px] font-normal text-gray-700 hover:bg-gray-100')} onClick={() => onCreate('template')}> - <FilePlus02 className='mr-2 h-4 w-4 shrink-0 text-gray-600' /> + <div className={cn('flex cursor-pointer items-center rounded-lg px-3 py-[6px] font-normal text-text-secondary hover:bg-state-base-hover')} onClick={() => onCreate('template')}> + <FilePlus02 className='mr-2 h-4 w-4 shrink-0 text-text-secondary' /> {t('app.newApp.startFromTemplate')} </div> </div> - <div className='border-t border-gray-100 p-1'> - <div className={cn('flex cursor-pointer items-center rounded-lg px-3 py-[6px] font-normal text-gray-700 hover:bg-gray-100')} onClick={() => onCreate('dsl')}> - <FileArrow01 className='mr-2 h-4 w-4 shrink-0 text-gray-600' /> + <div className='border-t border-divider-regular p-1'> + <div className={cn('flex cursor-pointer items-center rounded-lg px-3 py-[6px] font-normal text-text-secondary hover:bg-state-base-hover')} onClick={() => onCreate('dsl')}> + <FileArrow01 className='mr-2 h-4 w-4 shrink-0 text-text-secondary' /> {t('app.importDSL')} </div> </div> diff --git a/web/app/components/header/plugins-nav/downloading-icon.module.css b/web/app/components/header/plugins-nav/downloading-icon.module.css index c11a9f2f2c..bbd6a35e4c 100644 --- a/web/app/components/header/plugins-nav/downloading-icon.module.css +++ b/web/app/components/header/plugins-nav/downloading-icon.module.css @@ -13,15 +13,15 @@ } @keyframes drop { - 0% { - transform: translateY(-4px); + 0% { + transform: translateY(-4px); opacity: 0; } - 5% { - transform: translateY(-4px); + 5% { + transform: translateY(-4px); opacity: 1; } - 65% { + 65% { transform: translateY(2px); opacity: 1; } @@ -29,7 +29,7 @@ transform: translateY(2px); opacity: 0; } - 100% { + 100% { transform: translateY(2px); opacity: 0; } @@ -41,4 +41,4 @@ #downloadingIconArrow { animation: drop 1.2s cubic-bezier(0.4, 0, 1, 1) infinite; -} \ No newline at end of file +} diff --git a/web/app/components/plugins/install-plugin/install-bundle/steps/install-multi.tsx b/web/app/components/plugins/install-plugin/install-bundle/steps/install-multi.tsx index 40be3e65e6..52824ba23b 100644 --- a/web/app/components/plugins/install-plugin/install-bundle/steps/install-multi.tsx +++ b/web/app/components/plugins/install-plugin/install-bundle/steps/install-multi.tsx @@ -4,7 +4,7 @@ import React, { useCallback, useEffect, useMemo, useState } from 'react' import type { Dependency, GitHubItemAndMarketPlaceDependency, PackageDependency, Plugin, VersionInfo } from '../../../types' import MarketplaceItem from '../item/marketplace-item' import GithubItem from '../item/github-item' -import { useFetchPluginsInMarketPlaceByIds, useFetchPluginsInMarketPlaceByInfo } from '@/service/use-plugins' +import { useFetchPluginsInMarketPlaceByInfo } from '@/service/use-plugins' import useCheckInstalled from '@/app/components/plugins/install-plugin/hooks/use-check-installed' import produce from 'immer' import PackageItem from '../item/package-item' @@ -26,7 +26,18 @@ const InstallByDSLList: FC<Props> = ({ isFromMarketPlace, }) => { // DSL has id, to get plugin info to show more info - const { isLoading: isFetchingMarketplaceDataById, data: infoGetById, error: infoByIdError } = useFetchPluginsInMarketPlaceByIds(allPlugins.filter(d => d.type === 'marketplace').map(d => (d as GitHubItemAndMarketPlaceDependency).value.marketplace_plugin_unique_identifier!)) + const { isLoading: isFetchingMarketplaceDataById, data: infoGetById, error: infoByIdError } = useFetchPluginsInMarketPlaceByInfo(allPlugins.filter(d => d.type === 'marketplace').map((d) => { + const dependecy = (d as GitHubItemAndMarketPlaceDependency).value + // split org, name, version by / and : + // and remove @ and its suffix + const [orgPart, nameAndVersionPart] = dependecy.marketplace_plugin_unique_identifier!.split('@')[0].split('/') + const [name, version] = nameAndVersionPart.split(':') + return { + organization: orgPart, + plugin: name, + version, + } + })) // has meta(org,name,version), to get id const { isLoading: isFetchingDataByMeta, data: infoByMeta, error: infoByMetaError } = useFetchPluginsInMarketPlaceByInfo(allPlugins.filter(d => d.type === 'marketplace').map(d => (d as GitHubItemAndMarketPlaceDependency).value!)) @@ -82,8 +93,13 @@ const InstallByDSLList: FC<Props> = ({ }, [allPlugins]) useEffect(() => { - if (!isFetchingMarketplaceDataById && infoGetById?.data.plugins) { - const payloads = infoGetById?.data.plugins + if (!isFetchingMarketplaceDataById && infoGetById?.data.list) { + const sortedList = allPlugins.filter(d => d.type === 'marketplace').map((d) => { + const p = d as GitHubItemAndMarketPlaceDependency + const id = p.value.marketplace_plugin_unique_identifier?.split(':')[0] + return infoGetById.data.list.find(item => item.plugin.plugin_id === id)?.plugin + }) + const payloads = sortedList const failedIndex: number[] = [] const nextPlugins = produce(pluginsRef.current, (draft) => { marketPlaceInDSLIndex.forEach((index, i) => { diff --git a/web/app/components/plugins/install-plugin/install-bundle/steps/install.tsx b/web/app/components/plugins/install-plugin/install-bundle/steps/install.tsx index cff1afff02..db24bdd97a 100644 --- a/web/app/components/plugins/install-plugin/install-bundle/steps/install.tsx +++ b/web/app/components/plugins/install-plugin/install-bundle/steps/install.tsx @@ -8,6 +8,8 @@ import { useTranslation } from 'react-i18next' import InstallMulti from './install-multi' import { useInstallOrUpdate } from '@/service/use-plugins' import useRefreshPluginList from '../../hooks/use-refresh-plugin-list' +import { useCanInstallPluginFromMarketplace } from '@/app/components/plugins/plugin-page/use-permission' +import { useMittContextSelector } from '@/context/mitt-context' const i18nPrefix = 'plugin.installModal' type Props = { @@ -28,6 +30,7 @@ const Install: FC<Props> = ({ isHideButton, }) => { const { t } = useTranslation() + const emit = useMittContextSelector(s => s.emit) const [selectedPlugins, setSelectedPlugins] = React.useState<Plugin[]>([]) const [selectedIndexes, setSelectedIndexes] = React.useState<number[]>([]) const selectedPluginsNum = selectedPlugins.length @@ -62,8 +65,12 @@ const Install: FC<Props> = ({ }) })) const hasInstallSuccess = res.some(r => r.success) - if (hasInstallSuccess) + if (hasInstallSuccess) { refreshPluginList(undefined, true) + emit('plugin:install:success', selectedPlugins.map((p) => { + return `${p.plugin_id}/${p.name}` + })) + } }, }) const handleInstall = () => { @@ -74,6 +81,7 @@ const Install: FC<Props> = ({ installedInfo: installedInfo!, }) } + const { canInstallPluginFromMarketplace } = useCanInstallPluginFromMarketplace() return ( <> <div className='flex flex-col items-start justify-center gap-4 self-stretch px-6 py-3'> @@ -101,7 +109,7 @@ const Install: FC<Props> = ({ <Button variant='primary' className='flex min-w-[72px] space-x-0.5' - disabled={!canInstall || isInstalling || selectedPlugins.length === 0} + disabled={!canInstall || isInstalling || selectedPlugins.length === 0 || !canInstallPluginFromMarketplace} onClick={handleInstall} > {isInstalling && <RiLoader2Line className='h-4 w-4 animate-spin-slow' />} diff --git a/web/app/components/plugins/marketplace/empty/line.tsx b/web/app/components/plugins/marketplace/empty/line.tsx index 19837aa862..35afcc0d03 100644 --- a/web/app/components/plugins/marketplace/empty/line.tsx +++ b/web/app/components/plugins/marketplace/empty/line.tsx @@ -1,17 +1,39 @@ +'use client' +import useTheme from '@/hooks/use-theme' + type LineProps = { className?: string } + const Line = ({ className, }: LineProps) => { + const { theme } = useTheme() + const isDarkMode = theme === 'dark' + + if (isDarkMode) { + return ( + <svg xmlns='http://www.w3.org/2000/svg' width='2' height='240' viewBox='0 0 2 240' fill='none' className={className}> + <path d='M1 0L1 240' stroke='url(#paint0_linear_6295_52176)' /> + <defs> + <linearGradient id='paint0_linear_6295_52176' x1='-7.99584' y1='240' x2='-7.88094' y2='3.95539e-05' gradientUnits='userSpaceOnUse'> + <stop stopOpacity='0.01' /> + <stop offset='0.503965' stopColor='#C8CEDA' stopOpacity='0.14' /> + <stop offset='1' stopOpacity='0.01' /> + </linearGradient> + </defs> + </svg> + ) + } + return ( - <svg xmlns="http://www.w3.org/2000/svg" width="2" height="241" viewBox="0 0 2 241" fill="none" className={className}> - <path d="M1 0.5L1 240.5" stroke="url(#paint0_linear_1989_74474)"/> + <svg xmlns='http://www.w3.org/2000/svg' width='2' height='241' viewBox='0 0 2 241' fill='none' className={className}> + <path d='M1 0.5L1 240.5' stroke='url(#paint0_linear_1989_74474)' /> <defs> - <linearGradient id="paint0_linear_1989_74474" x1="-7.99584" y1="240.5" x2="-7.88094" y2="0.50004" gradientUnits="userSpaceOnUse"> - <stop stopColor="white" stopOpacity="0.01"/> - <stop offset="0.503965" stopColor="#101828" stopOpacity="0.08"/> - <stop offset="1" stopColor="white" stopOpacity="0.01"/> + <linearGradient id='paint0_linear_1989_74474' x1='-7.99584' y1='240.5' x2='-7.88094' y2='0.50004' gradientUnits='userSpaceOnUse'> + <stop stopColor='white' stopOpacity='0.01' /> + <stop offset='0.503965' stopColor='#101828' stopOpacity='0.08' /> + <stop offset='1' stopColor='white' stopOpacity='0.01' /> </linearGradient> </defs> </svg> diff --git a/web/app/components/plugins/marketplace/list/card-wrapper.tsx b/web/app/components/plugins/marketplace/list/card-wrapper.tsx index 75c9aae3d5..27511559d9 100644 --- a/web/app/components/plugins/marketplace/list/card-wrapper.tsx +++ b/web/app/components/plugins/marketplace/list/card-wrapper.tsx @@ -1,4 +1,5 @@ 'use client' +import { useTheme } from 'next-themes' import { RiArrowRightUpLine } from '@remixicon/react' import { getPluginLinkInMarketplace } from '../utils' import Card from '@/app/components/plugins/card' @@ -22,6 +23,7 @@ const CardWrapper = ({ locale, }: CardWrapperProps) => { const { t } = useMixedTranslation(locale) + const { theme } = useTheme() const [isShowInstallFromMarketplace, { setTrue: showInstallFromMarketplace, setFalse: hideInstallFromMarketplace, @@ -54,7 +56,7 @@ const CardWrapper = ({ > {t('plugin.detailPanel.operation.install')} </Button> - <a href={`${getPluginLinkInMarketplace(plugin)}?language=${localeFromLocale}`} target='_blank' className='block w-[calc(50%-4px)] flex-1 shrink-0'> + <a href={`${getPluginLinkInMarketplace(plugin)}?language=${localeFromLocale}${theme ? `&theme=${theme}` : ''}`} target='_blank' className='block w-[calc(50%-4px)] flex-1 shrink-0'> <Button className='w-full gap-0.5' > diff --git a/web/app/components/plugins/permission-setting-modal/modal.tsx b/web/app/components/plugins/permission-setting-modal/modal.tsx index f56c079818..6fd4d8c2dc 100644 --- a/web/app/components/plugins/permission-setting-modal/modal.tsx +++ b/web/app/components/plugins/permission-setting-modal/modal.tsx @@ -49,8 +49,8 @@ const PluginSettingModal: FC<Props> = ({ </div> <div className='flex flex-col items-start justify-center gap-4 self-stretch px-6 py-3'> {[ - { title: t(`${i18nPrefix}.whoCanInstall`), key: 'install_permission', value: tempPrivilege.install_permission }, - { title: t(`${i18nPrefix}.whoCanDebug`), key: 'debug_permission', value: tempPrivilege.debug_permission }, + { title: t(`${i18nPrefix}.whoCanInstall`), key: 'install_permission', value: tempPrivilege?.install_permission || PermissionType.noOne }, + { title: t(`${i18nPrefix}.whoCanDebug`), key: 'debug_permission', value: tempPrivilege?.debug_permission || PermissionType.noOne }, ].map(({ title, key, value }) => ( <div key={key} className='flex flex-col items-start gap-1 self-stretch'> <div className='flex h-6 items-center gap-0.5'> diff --git a/web/app/components/plugins/permission-setting-modal/style.module.css b/web/app/components/plugins/permission-setting-modal/style.module.css index 7ad3180a5a..15bedd84ca 100644 --- a/web/app/components/plugins/permission-setting-modal/style.module.css +++ b/web/app/components/plugins/permission-setting-modal/style.module.css @@ -4,4 +4,4 @@ -webkit-text-fill-color: transparent; background-clip: text; text-fill-color: transparent; -} \ No newline at end of file +} diff --git a/web/app/components/plugins/plugin-detail-panel/action-list.tsx b/web/app/components/plugins/plugin-detail-panel/action-list.tsx index eb47ce3a35..2505b6d5aa 100644 --- a/web/app/components/plugins/plugin-detail-panel/action-list.tsx +++ b/web/app/components/plugins/plugin-detail-panel/action-list.tsx @@ -78,7 +78,7 @@ const ActionList = ({ className='w-full' onClick={() => setShowSettingAuth(true)} disabled={!isCurrentWorkspaceManager} - >{t('tools.auth.unauthorized')}</Button> + >{t('workflow.nodes.tool.authorize')}</Button> )} </div> <div className='flex flex-col gap-2'> diff --git a/web/app/components/plugins/plugin-detail-panel/detail-header.tsx b/web/app/components/plugins/plugin-detail-panel/detail-header.tsx index b63f78f0be..7548c90ac5 100644 --- a/web/app/components/plugins/plugin-detail-panel/detail-header.tsx +++ b/web/app/components/plugins/plugin-detail-panel/detail-header.tsx @@ -1,4 +1,5 @@ import React, { useCallback, useMemo, useState } from 'react' +import { useTheme } from 'next-themes' import { useTranslation } from 'react-i18next' import { useBoolean } from 'ahooks' import { @@ -49,6 +50,7 @@ const DetailHeader = ({ onUpdate, }: Props) => { const { t } = useTranslation() + const { theme } = useTheme() const locale = useGetLanguage() const { checkForUpdates, fetchReleases } = useGitHubReleases() const { setShowUpdatePluginModal } = useModalContext() @@ -85,9 +87,9 @@ const DetailHeader = ({ if (isFromGitHub) return `https://github.com/${meta!.repo}` if (isFromMarketplace) - return `${MARKETPLACE_URL_PREFIX}/plugins/${author}/${name}` + return `${MARKETPLACE_URL_PREFIX}/plugins/${author}/${name}${theme ? `?theme=${theme}` : ''}` return '' - }, [author, isFromGitHub, isFromMarketplace, meta, name]) + }, [author, isFromGitHub, isFromMarketplace, meta, name, theme]) const [isShowUpdateModal, { setTrue: showUpdateModal, diff --git a/web/app/components/plugins/plugin-detail-panel/endpoint-card.tsx b/web/app/components/plugins/plugin-detail-panel/endpoint-card.tsx index 9ab0c5b9a3..cc3688aebc 100644 --- a/web/app/components/plugins/plugin-detail-panel/endpoint-card.tsx +++ b/web/app/components/plugins/plugin-detail-panel/endpoint-card.tsx @@ -149,7 +149,7 @@ const EndpointCard = ({ </ActionButton> </div> </div> - {data.declaration.endpoints.map((endpoint, index) => ( + {data.declaration.endpoints.filter(endpoint => !endpoint.hidden).map((endpoint, index) => ( <div key={index} className='flex h-6 items-center'> <div className='system-xs-regular w-12 shrink-0 text-text-tertiary'>{endpoint.method}</div> <div className='group/item system-xs-regular flex grow items-center truncate text-text-secondary'> diff --git a/web/app/components/plugins/plugin-detail-panel/endpoint-list.tsx b/web/app/components/plugins/plugin-detail-panel/endpoint-list.tsx index 59a457ecbd..b31b45c0c7 100644 --- a/web/app/components/plugins/plugin-detail-panel/endpoint-list.tsx +++ b/web/app/components/plugins/plugin-detail-panel/endpoint-list.tsx @@ -1,6 +1,6 @@ import React, { useMemo } from 'react' import { useTranslation } from 'react-i18next' -import { useContext } from 'use-context-selector' +import { useDocLink } from '@/context/i18n' import { useBoolean } from 'ahooks' import { RiAddLine, @@ -20,8 +20,6 @@ import { useInvalidateEndpointList, } from '@/service/use-endpoints' import type { PluginDetail } from '@/app/components/plugins/types' -import { LanguagesSupported } from '@/i18n/language' -import I18n from '@/context/i18n' import cn from '@/utils/classnames' type Props = { @@ -29,7 +27,7 @@ type Props = { } const EndpointList = ({ detail }: Props) => { const { t } = useTranslation() - const { locale } = useContext(I18n) + const docLink = useDocLink() const pluginUniqueID = detail.plugin_unique_identifier const declaration = detail.declaration.endpoint const showTopBorder = detail.declaration.tool @@ -79,7 +77,7 @@ const EndpointList = ({ detail }: Props) => { </div> <div className='system-xs-regular text-text-tertiary'>{t('plugin.detailPanel.endpointsTip')}</div> <a - href={`https://docs.dify.ai/${locale === LanguagesSupported[1] ? 'v/zh-hans/' : ''}plugins/schema-definition/endpoint`} + href={docLink('/plugins/schema-definition/endpoint')} target='_blank' rel='noopener noreferrer' > diff --git a/web/app/components/plugins/plugin-detail-panel/multiple-tool-selector/index.tsx b/web/app/components/plugins/plugin-detail-panel/multiple-tool-selector/index.tsx index f243d30aff..7f5f22896a 100644 --- a/web/app/components/plugins/plugin-detail-panel/multiple-tool-selector/index.tsx +++ b/web/app/components/plugins/plugin-detail-panel/multiple-tool-selector/index.tsx @@ -141,7 +141,7 @@ const MultipleToolSelector = ({ } panelShowState={panelShowState} onPanelShowStateChange={setPanelShowState} - + isEdit={false} /> {value.length === 0 && ( <div className='system-xs-regular flex justify-center rounded-[10px] bg-background-section p-3 text-text-tertiary'>{t('plugin.detailPanel.toolSelector.empty')}</div> @@ -158,6 +158,7 @@ const MultipleToolSelector = ({ onSelect={item => handleConfigure(item, index)} onDelete={() => handleDelete(index)} supportEnableSwitch + isEdit /> </div> ))} diff --git a/web/app/components/plugins/plugin-detail-panel/tool-selector/index.tsx b/web/app/components/plugins/plugin-detail-panel/tool-selector/index.tsx index 577de19484..ca802414f3 100644 --- a/web/app/components/plugins/plugin-detail-panel/tool-selector/index.tsx +++ b/web/app/components/plugins/plugin-detail-panel/tool-selector/index.tsx @@ -54,6 +54,7 @@ type Props = { scope?: string value?: ToolValue selectedTools?: ToolValue[] + isEdit?: boolean onSelect: (tool: { provider_name: string tool_name: string @@ -77,6 +78,7 @@ type Props = { const ToolSelector: FC<Props> = ({ value, selectedTools, + isEdit, disabled, placement = 'left', offset = 4, @@ -277,7 +279,7 @@ const ToolSelector: FC<Props> = ({ <div className={cn('relative max-h-[642px] min-h-20 w-[361px] rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg-blur pb-4 shadow-lg backdrop-blur-sm', !isShowSettingAuth && 'overflow-y-auto pb-2')}> {!isShowSettingAuth && ( <> - <div className='system-xl-semibold px-4 pb-1 pt-3.5 text-text-primary'>{t('plugin.detailPanel.toolSelector.title')}</div> + <div className='system-xl-semibold px-4 pb-1 pt-3.5 text-text-primary'>{t(`plugin.detailPanel.toolSelector.${isEdit ? 'toolSetting' : 'title'}`)}</div> {/* base form */} <div className='flex flex-col gap-3 px-4 py-2'> <div className='flex flex-col gap-1'> diff --git a/web/app/components/plugins/plugin-detail-panel/tool-selector/reasoning-config-form.tsx b/web/app/components/plugins/plugin-detail-panel/tool-selector/reasoning-config-form.tsx index 0d137502f4..750a8cfff6 100644 --- a/web/app/components/plugins/plugin-detail-panel/tool-selector/reasoning-config-form.tsx +++ b/web/app/components/plugins/plugin-detail-panel/tool-selector/reasoning-config-form.tsx @@ -183,7 +183,7 @@ const ReasoningConfigForm: React.FC<Props> = ({ <> {isString && ( <Input - className={cn(inputsIsFocus[variable] ? 'border-gray-300 bg-gray-50 shadow-xs' : 'border-gray-100 bg-gray-100', 'rounded-lg border px-3 py-[6px]')} + className={cn(inputsIsFocus[variable] ? 'border-components-input-border-active bg-components-input-bg-active shadow-xs' : 'border-components-input-border-hover bg-components-input-bg-normal', 'rounded-lg border px-3 py-[6px]')} value={varInput?.value as string || ''} onChange={handleMixedTypeChange(variable)} nodesOutputVars={nodeOutputVars} diff --git a/web/app/components/plugins/plugin-item/index.tsx b/web/app/components/plugins/plugin-item/index.tsx index 8ce26b737a..b2d5f91caa 100644 --- a/web/app/components/plugins/plugin-item/index.tsx +++ b/web/app/components/plugins/plugin-item/index.tsx @@ -1,6 +1,7 @@ 'use client' import type { FC } from 'react' import React, { useMemo } from 'react' +import { useTheme } from 'next-themes' import { RiArrowRightUpLine, RiBugLine, @@ -38,6 +39,7 @@ const PluginItem: FC<Props> = ({ plugin, }) => { const { t } = useTranslation() + const { theme } = useTheme() const { categoriesMap } = useSingleCategories() const currentPluginID = usePluginPageContext(v => v.currentPluginID) const setCurrentPluginID = usePluginPageContext(v => v.setCurrentPluginID) @@ -106,7 +108,7 @@ const PluginItem: FC<Props> = ({ }><RiErrorWarningLine color='red' className="ml-0.5 h-4 w-4 shrink-0 text-text-accent" /></Tooltip>} <Badge className='ml-1 shrink-0' text={source === PluginSource.github ? plugin.meta!.version : plugin.version} - hasRedCornerMark={(source === PluginSource.marketplace) && !!plugin.latest_unique_identifier && plugin.latest_unique_identifier !== plugin_unique_identifier} + hasRedCornerMark={(source === PluginSource.marketplace) && !!plugin.latest_version && plugin.latest_version !== plugin.version} /> </div> <div className='flex items-center justify-between'> @@ -164,7 +166,7 @@ const PluginItem: FC<Props> = ({ } {source === PluginSource.marketplace && <> - <a href={`${MARKETPLACE_URL_PREFIX}/plugins/${author}/${name}`} target='_blank' className='flex items-center gap-0.5'> + <a href={`${MARKETPLACE_URL_PREFIX}/plugins/${author}/${name}${theme ? `?theme=${theme}` : ''}`} target='_blank' className='flex items-center gap-0.5'> <div className='system-2xs-medium-uppercase text-text-tertiary'>{t('plugin.from')} <span className='text-text-secondary'>marketplace</span></div> <RiArrowRightUpLine className='h-3 w-3 text-text-tertiary' /> </a> diff --git a/web/app/components/plugins/plugin-page/context.tsx b/web/app/components/plugins/plugin-page/context.tsx index ae1ad7d053..52efbb263e 100644 --- a/web/app/components/plugins/plugin-page/context.tsx +++ b/web/app/components/plugins/plugin-page/context.tsx @@ -10,11 +10,11 @@ import { createContext, useContextSelector, } from 'use-context-selector' -import { useSelector as useAppContextSelector } from '@/context/app-context' import type { FilterState } from './filter-management' import { useTabSearchParams } from '@/hooks/use-tab-searchparams' import { noop } from 'lodash-es' import { PLUGIN_PAGE_TABS_MAP, usePluginPageTabs } from '../hooks' +import { useGlobalPublicStore } from '@/context/global-public-context' export type PluginPageContextValue = { containerRef: React.RefObject<HTMLDivElement> @@ -61,7 +61,7 @@ export const PluginPageContextProvider = ({ }) const [currentPluginID, setCurrentPluginID] = useState<string | undefined>() - const { enable_marketplace } = useAppContextSelector(s => s.systemFeatures) + const { enable_marketplace } = useGlobalPublicStore(s => s.systemFeatures) const tabs = usePluginPageTabs() const options = useMemo(() => { return enable_marketplace ? tabs : tabs.filter(tab => tab.value !== PLUGIN_PAGE_TABS_MAP.marketplace) diff --git a/web/app/components/plugins/plugin-page/debug-info.tsx b/web/app/components/plugins/plugin-page/debug-info.tsx index bd59e5d737..28b8a7fc49 100644 --- a/web/app/components/plugins/plugin-page/debug-info.tsx +++ b/web/app/components/plugins/plugin-page/debug-info.tsx @@ -1,6 +1,8 @@ 'use client' import type { FC } from 'react' import React from 'react' +import { useContext } from 'use-context-selector' +import I18n from '@/context/i18n' import { RiArrowRightUpLine, RiBugLine, @@ -9,12 +11,14 @@ import { useTranslation } from 'react-i18next' import KeyValueItem from '../base/key-value-item' import Tooltip from '@/app/components/base/tooltip' import Button from '@/app/components/base/button' +import { getDocsUrl } from '@/app/components/plugins/utils' import { useDebugKey } from '@/service/use-plugins' const i18nPrefix = 'plugin.debugInfo' const DebugInfo: FC = () => { const { t } = useTranslation() + const { locale } = useContext(I18n) const { data: info, isLoading } = useDebugKey() // info.key likes 4580bdb7-b878-471c-a8a4-bfd760263a53 mask the middle part using *. @@ -30,7 +34,7 @@ const DebugInfo: FC = () => { <> <div className='flex items-center gap-1 self-stretch'> <span className='system-sm-semibold flex shrink-0 grow basis-0 flex-col items-start justify-center text-text-secondary'>{t(`${i18nPrefix}.title`)}</span> - <a href='https://docs.dify.ai/plugins/quick-start/develop-plugins/debug-plugin' target='_blank' className='flex cursor-pointer items-center gap-0.5 text-text-accent-light-mode-only'> + <a href={getDocsUrl(locale, '/plugins/quick-start/debug-plugin')} target='_blank' className='flex cursor-pointer items-center gap-0.5 text-text-accent-light-mode-only'> <span className='system-xs-medium'>{t(`${i18nPrefix}.viewDocs`)}</span> <RiArrowRightUpLine className='h-3 w-3' /> </a> diff --git a/web/app/components/plugins/plugin-page/empty/index.tsx b/web/app/components/plugins/plugin-page/empty/index.tsx index 7fee823c02..d17c4f420b 100644 --- a/web/app/components/plugins/plugin-page/empty/index.tsx +++ b/web/app/components/plugins/plugin-page/empty/index.tsx @@ -6,19 +6,20 @@ import InstallFromGitHub from '@/app/components/plugins/install-plugin/install-f import InstallFromLocalPackage from '@/app/components/plugins/install-plugin/install-from-local-package' import { usePluginPageContext } from '../context' import { Group } from '@/app/components/base/icons/src/vender/other' -import { useSelector as useAppContextSelector } from '@/context/app-context' import Line from '../../marketplace/empty/line' import { useInstalledPluginList } from '@/service/use-plugins' import { useTranslation } from 'react-i18next' import { SUPPORT_INSTALL_LOCAL_FILE_EXTENSIONS } from '@/config' import { noop } from 'lodash-es' +import { useGlobalPublicStore } from '@/context/global-public-context' +import Button from '@/app/components/base/button' const Empty = () => { const { t } = useTranslation() const fileInputRef = useRef<HTMLInputElement>(null) const [selectedAction, setSelectedAction] = useState<string | null>(null) const [selectedFile, setSelectedFile] = useState<File | null>(null) - const { enable_marketplace } = useAppContextSelector(s => s.systemFeatures) + const { enable_marketplace } = useGlobalPublicStore(s => s.systemFeatures) const setActiveTab = usePluginPageContext(v => v.setActiveTab) const handleFileChange = (event: React.ChangeEvent<HTMLInputElement>) => { @@ -43,25 +44,25 @@ const Empty = () => { {/* skeleton */} <div className='absolute top-0 z-10 grid h-full w-full grid-cols-2 gap-2 overflow-hidden px-12'> {Array.from({ length: 20 }).fill(0).map((_, i) => ( - <div key={i} className='h-[100px] rounded-xl bg-components-card-bg' /> + <div key={i} className='h-24 rounded-xl bg-components-card-bg' /> ))} </div> {/* mask */} - <div className='absolute z-20 h-full w-full bg-gradient-to-b from-background-gradient-mask-transparent to-white' /> + <div className='absolute z-20 h-full w-full bg-gradient-to-b from-components-panel-bg-transparent to-components-panel-bg' /> <div className='relative z-30 flex h-full items-center justify-center'> <div className='flex flex-col items-center gap-y-3'> - <div className='relative -z-10 flex h-[52px] w-[52px] items-center justify-center rounded-xl + <div className='relative -z-10 flex size-14 items-center justify-center rounded-xl border-[1px] border-dashed border-divider-deep bg-components-card-bg shadow-xl shadow-shadow-shadow-5'> <Group className='h-5 w-5 text-text-tertiary' /> - <Line className='absolute -right-[1px] top-1/2 -translate-y-1/2' /> - <Line className='absolute -left-[1px] top-1/2 -translate-y-1/2' /> + <Line className='absolute right-[-1px] top-1/2 -translate-y-1/2' /> + <Line className='absolute left-[-1px] top-1/2 -translate-y-1/2' /> <Line className='absolute left-1/2 top-0 -translate-x-1/2 -translate-y-1/2 rotate-90' /> <Line className='absolute left-1/2 top-full -translate-x-1/2 -translate-y-1/2 rotate-90' /> </div> - <div className='text-sm font-normal text-text-tertiary'> + <div className='system-md-regular text-text-tertiary'> {text} </div> - <div className='flex w-[240px] flex-col'> + <div className='flex w-[236px] flex-col'> <input type='file' ref={fileInputRef} @@ -79,10 +80,9 @@ const Empty = () => { { icon: Github, text: t('plugin.list.source.github'), action: 'github' }, { icon: FileZip, text: t('plugin.list.source.local'), action: 'local' }, ].map(({ icon: Icon, text, action }) => ( - <div + <Button key={action} - className='flex cursor-pointer items-center gap-x-1 rounded-lg border-[0.5px] bg-components-button-secondary-bg - px-3 py-2 shadow-xs shadow-shadow-shadow-3 hover:bg-state-base-hover' + className='justify-start gap-x-0.5 px-3' onClick={() => { if (action === 'local') fileInputRef.current?.click() @@ -92,9 +92,9 @@ const Empty = () => { setSelectedAction(action) }} > - <Icon className="h-4 w-4 text-text-tertiary" /> - <span className='system-md-regular text-text-secondary'>{text}</span> - </div> + <Icon className='size-4' /> + <span className='px-0.5'>{text}</span> + </Button> ))} </div> </div> diff --git a/web/app/components/plugins/plugin-page/index.tsx b/web/app/components/plugins/plugin-page/index.tsx index 35fc704953..7e17459b33 100644 --- a/web/app/components/plugins/plugin-page/index.tsx +++ b/web/app/components/plugins/plugin-page/index.tsx @@ -25,7 +25,6 @@ import TabSlider from '@/app/components/base/tab-slider' import Tooltip from '@/app/components/base/tooltip' import cn from '@/utils/classnames' import PermissionSetModal from '@/app/components/plugins/permission-setting-modal/modal' -import { useSelector as useAppContextSelector } from '@/context/app-context' import InstallFromMarketplace from '../install-plugin/install-from-marketplace' import { useRouter, @@ -34,14 +33,16 @@ import { import type { Dependency } from '../types' import type { PluginDeclaration, PluginManifestInMarket } from '../types' import { sleep } from '@/utils' +import { getDocsUrl } from '@/app/components/plugins/utils' import { fetchBundleInfoFromMarketPlace, fetchManifestFromMarketPlace } from '@/service/plugins' import { marketplaceApiPrefix } from '@/config' import { SUPPORT_INSTALL_LOCAL_FILE_EXTENSIONS } from '@/config' -import { LanguagesSupported } from '@/i18n/language' import I18n from '@/context/i18n' import { noop } from 'lodash-es' import { PLUGIN_TYPE_SEARCH_MAP } from '../marketplace/plugin-type-switch' import { PLUGIN_PAGE_TABS_MAP } from '../hooks' +import { useGlobalPublicStore } from '@/context/global-public-context' +import useDocumentTitle from '@/hooks/use-document-title' const PACKAGE_IDS_KEY = 'package-ids' const BUNDLE_INFO_KEY = 'bundle-info' @@ -58,8 +59,7 @@ const PluginPage = ({ const { locale } = useContext(I18n) const searchParams = useSearchParams() const { replace } = useRouter() - - document.title = `${t('plugin.metadata.title')} - Dify` + useDocumentTitle(t('plugin.metadata.title')) // just support install one package now const packageId = useMemo(() => { @@ -136,7 +136,7 @@ const PluginPage = ({ const options = usePluginPageContext(v => v.options) const activeTab = usePluginPageContext(v => v.activeTab) const setActiveTab = usePluginPageContext(v => v.setActiveTab) - const { enable_marketplace } = useAppContextSelector(s => s.systemFeatures) + const { enable_marketplace } = useGlobalPublicStore(s => s.systemFeatures) const isPluginsTab = useMemo(() => activeTab === PLUGIN_PAGE_TABS_MAP.plugins, [activeTab]) const isExploringMarketplace = useMemo(() => { @@ -144,10 +144,18 @@ const PluginPage = ({ return activeTab === PLUGIN_PAGE_TABS_MAP.marketplace || values.includes(activeTab) }, [activeTab]) + const handleFileChange = (file: File | null) => { + if (!file || !file.name.endsWith('.difypkg')) { + setCurrentFile(null) + return + } + + setCurrentFile(file) + } const uploaderProps = useUploader({ - onFileChange: setCurrentFile, + onFileChange: handleFileChange, containerRef, - enabled: isPluginsTab, + enabled: isPluginsTab && canManagement, }) const { dragging, fileUploader, fileChangeHandle, removeFile } = uploaderProps @@ -179,7 +187,18 @@ const PluginPage = ({ isExploringMarketplace && ( <> <Link - href={`https://docs.dify.ai/${locale === LanguagesSupported[1] ? 'v/zh-hans/' : ''}plugins/publish-plugins/publish-to-dify-marketplace`} + href='https://github.com/langgenius/dify-plugins/issues/new?template=plugin_request.yaml' + target='_blank' + > + <Button + variant='ghost' + className='text-text-tertiary' + > + {t('plugin.requestAPlugin')} + </Button> + </Link> + <Link + href={getDocsUrl(locale, '/plugins/publish-plugins/publish-to-dify-marketplace/README')} target='_blank' > <Button @@ -187,10 +206,10 @@ const PluginPage = ({ variant='secondary-accent' > <RiBookOpenLine className='mr-1 h-4 w-4' /> - {t('plugin.submitPlugin')} + {t('plugin.publishPlugins')} </Button> </Link> - <div className='mx-2 h-3.5 w-[1px] bg-divider-regular'></div> + <div className='mx-1 h-3.5 w-[1px] shrink-0 bg-divider-regular'></div> </> ) } diff --git a/web/app/components/plugins/plugin-page/install-plugin-dropdown.tsx b/web/app/components/plugins/plugin-page/install-plugin-dropdown.tsx index 875cbd0ab2..abe4f9cb6a 100644 --- a/web/app/components/plugins/plugin-page/install-plugin-dropdown.tsx +++ b/web/app/components/plugins/plugin-page/install-plugin-dropdown.tsx @@ -14,10 +14,10 @@ import { PortalToFollowElemContent, PortalToFollowElemTrigger, } from '@/app/components/base/portal-to-follow-elem' -import { useSelector as useAppContextSelector } from '@/context/app-context' import { useTranslation } from 'react-i18next' import { SUPPORT_INSTALL_LOCAL_FILE_EXTENSIONS } from '@/config' import { noop } from 'lodash-es' +import { useGlobalPublicStore } from '@/context/global-public-context' type Props = { onSwitchToMarketplaceTab: () => void @@ -30,7 +30,7 @@ const InstallPluginDropdown = ({ const [isMenuOpen, setIsMenuOpen] = useState(false) const [selectedAction, setSelectedAction] = useState<string | null>(null) const [selectedFile, setSelectedFile] = useState<File | null>(null) - const { enable_marketplace } = useAppContextSelector(s => s.systemFeatures) + const { enable_marketplace } = useGlobalPublicStore(s => s.systemFeatures) const handleFileChange = (event: React.ChangeEvent<HTMLInputElement>) => { const file = event.target.files?.[0] diff --git a/web/app/components/plugins/plugin-page/plugins-panel.tsx b/web/app/components/plugins/plugin-page/plugins-panel.tsx index 125e6f0a70..a5f411c37e 100644 --- a/web/app/components/plugins/plugin-page/plugins-panel.tsx +++ b/web/app/components/plugins/plugin-page/plugins-panel.tsx @@ -1,5 +1,6 @@ 'use client' import { useMemo } from 'react' +import { useTranslation } from 'react-i18next' import type { FilterState } from './filter-management' import FilterManagement from './filter-management' import List from './list' @@ -7,14 +8,16 @@ import { useInstalledLatestVersion, useInstalledPluginList, useInvalidateInstall import PluginDetailPanel from '@/app/components/plugins/plugin-detail-panel' import { usePluginPageContext } from './context' import { useDebounceFn } from 'ahooks' +import Button from '@/app/components/base/button' import Empty from './empty' import Loading from '../../base/loading' import { PluginSource } from '../types' const PluginsPanel = () => { + const { t } = useTranslation() const filters = usePluginPageContext(v => v.filters) as FilterState const setFilters = usePluginPageContext(v => v.setFilters) - const { data: pluginList, isLoading: isPluginListLoading } = useInstalledPluginList() + const { data: pluginList, isLoading: isPluginListLoading, isFetching, isLastPage, loadNextPage } = useInstalledPluginList() const { data: installedLatestVersion } = useInstalledLatestVersion( pluginList?.plugins .filter(plugin => plugin.source === PluginSource.marketplace) @@ -64,10 +67,16 @@ const PluginsPanel = () => { /> </div> {isPluginListLoading ? <Loading type='app' /> : (filteredList?.length ?? 0) > 0 ? ( - <div className='flex grow flex-wrap content-start items-start gap-2 self-stretch px-12'> + <div className='flex grow flex-wrap content-start items-start justify-center gap-2 self-stretch px-12'> <div className='w-full'> <List pluginList={filteredList || []} /> </div> + {!isLastPage && !isFetching && ( + <Button onClick={loadNextPage}> + {t('workflow.common.loadMore')} + </Button> + )} + {isFetching && <div className='system-md-semibold text-text-secondary'>{t('appLog.detail.loading')}</div>} </div> ) : ( <Empty /> diff --git a/web/app/components/plugins/plugin-page/use-permission.ts b/web/app/components/plugins/plugin-page/use-permission.ts index 9803c713d1..918813fb44 100644 --- a/web/app/components/plugins/plugin-page/use-permission.ts +++ b/web/app/components/plugins/plugin-page/use-permission.ts @@ -3,6 +3,8 @@ import { useAppContext } from '@/context/app-context' import Toast from '../../base/toast' import { useTranslation } from 'react-i18next' import { useInvalidatePermissions, useMutationPermissions, usePermissions } from '@/service/use-plugins' +import { useMemo } from 'react' +import { useGlobalPublicStore } from '@/context/global-public-context' const hasPermission = (permission: PermissionType | undefined, isAdmin: boolean) => { if (!permission) @@ -43,4 +45,17 @@ const usePermission = () => { } } +export const useCanInstallPluginFromMarketplace = () => { + const { enable_marketplace } = useGlobalPublicStore(s => s.systemFeatures) + const { canManagement } = usePermission() + + const canInstallPluginFromMarketplace = useMemo(() => { + return enable_marketplace && canManagement + }, [enable_marketplace, canManagement]) + + return { + canInstallPluginFromMarketplace, + } +} + export default usePermission diff --git a/web/app/components/plugins/provider-card.tsx b/web/app/components/plugins/provider-card.tsx index a0643fba20..c8555ffcee 100644 --- a/web/app/components/plugins/provider-card.tsx +++ b/web/app/components/plugins/provider-card.tsx @@ -1,6 +1,7 @@ 'use client' import React from 'react' import type { FC } from 'react' +import { useTheme } from 'next-themes' import { useTranslation } from 'react-i18next' import { RiArrowRightUpLine } from '@remixicon/react' import Badge from '../base/badge' @@ -28,6 +29,7 @@ const ProviderCard: FC<Props> = ({ }) => { const getValueFromI18nObject = useRenderI18nObject() const { t } = useTranslation() + const { theme } = useTheme() const [isShowInstallFromMarketplace, { setTrue: showInstallFromMarketplace, setFalse: hideInstallFromMarketplace, @@ -74,7 +76,7 @@ const ProviderCard: FC<Props> = ({ className='grow' variant='secondary' > - <a href={`${getPluginLinkInMarketplace(payload)}?language=${locale}`} target='_blank' className='flex items-center gap-0.5'> + <a href={`${getPluginLinkInMarketplace(payload)}?language=${locale}${theme ? `&theme=${theme}` : ''}`} target='_blank' className='flex items-center gap-0.5'> {t('plugin.detailPanel.operation.detail')} <RiArrowRightUpLine className='h-4 w-4' /> </a> diff --git a/web/app/components/plugins/types.ts b/web/app/components/plugins/types.ts index 5ed05d4523..231763aaab 100644 --- a/web/app/components/plugins/types.ts +++ b/web/app/components/plugins/types.ts @@ -36,6 +36,7 @@ export type PluginEndpointDeclaration = { export type EndpointItem = { path: string method: string + hidden?: boolean } export type EndpointListItem = { @@ -324,6 +325,11 @@ export type InstalledPluginListResponse = { plugins: PluginDetail[] } +export type InstalledPluginListWithTotalResponse = { + plugins: PluginDetail[] + total: number +} + export type InstalledLatestVersionResponse = { versions: { [plugin_id: string]: { diff --git a/web/app/components/plugins/utils.ts b/web/app/components/plugins/utils.ts index 95f6d716d9..1b84e3e9f2 100644 --- a/web/app/components/plugins/utils.ts +++ b/web/app/components/plugins/utils.ts @@ -1,3 +1,5 @@ +import { LanguagesSupported } from '@/i18n/language' + import { categoryKeys, tagKeys, @@ -10,3 +12,15 @@ export const getValidTagKeys = (tags: string[]) => { export const getValidCategoryKeys = (category?: string) => { return categoryKeys.find(key => key === category) } + +export const getDocsUrl = (locale: string, path: string) => { + let localePath = 'en' + + if (locale === LanguagesSupported[1]) + localePath = 'zh-hans' + + else if (locale === LanguagesSupported[7]) + localePath = 'ja-jp' + + return `https://docs.dify.ai/${localePath}${path}` +} diff --git a/web/app/components/share/text-generation/index.tsx b/web/app/components/share/text-generation/index.tsx index 9dd86335b6..9dc7ffcd79 100644 --- a/web/app/components/share/text-generation/index.tsx +++ b/web/app/components/share/text-generation/index.tsx @@ -9,10 +9,11 @@ import { import { useBoolean } from 'ahooks' import { usePathname, useRouter, useSearchParams } from 'next/navigation' import TabHeader from '../../base/tab-header' -import { checkOrSetAccessToken } from '../utils' +import { checkOrSetAccessToken, removeAccessToken } from '../utils' import MenuDropdown from './menu-dropdown' import RunBatch from './run-batch' import ResDownload from './run-batch/res-download' +import AppUnavailable from '../../base/app-unavailable' import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints' import RunOnce from '@/app/components/share/text-generation/run-once' import { fetchSavedMessage as doFetchSavedMessage, fetchAppInfo, fetchAppParams, removeMessage, saveMessage } from '@/service/share' @@ -36,8 +37,12 @@ import Toast from '@/app/components/base/toast' import type { VisionFile, VisionSettings } from '@/types/app' import { Resolution, TransferMethod } from '@/types/app' import { useAppFavicon } from '@/hooks/use-app-favicon' -import LogoSite from '@/app/components/base/logo/logo-site' +import DifyLogo from '@/app/components/base/logo/dify-logo' import cn from '@/utils/classnames' +import { useGetAppAccessMode, useGetUserCanAccessApp } from '@/service/access-control' +import { AccessMode } from '@/models/access-control' +import { useGlobalPublicStore } from '@/context/global-public-context' +import useDocumentTitle from '@/hooks/use-document-title' const GROUP_SIZE = 5 // to avoid RPM(Request per minute) limit. The group task finished then the next group. enum TaskStatus { @@ -98,14 +103,25 @@ const TextGeneration: FC<IMainProps> = ({ doSetInputs(newInputs) inputsRef.current = newInputs }, []) + const systemFeatures = useGlobalPublicStore(s => s.systemFeatures) const [appId, setAppId] = useState<string>('') const [siteInfo, setSiteInfo] = useState<SiteInfo | null>(null) - const [canReplaceLogo, setCanReplaceLogo] = useState<boolean>(false) const [customConfig, setCustomConfig] = useState<Record<string, any> | null>(null) const [promptConfig, setPromptConfig] = useState<PromptConfig | null>(null) const [moreLikeThisConfig, setMoreLikeThisConfig] = useState<MoreLikeThisConfig | null>(null) const [textToSpeechConfig, setTextToSpeechConfig] = useState<TextToSpeechConfig | null>(null) + const { isPending: isGettingAccessMode, data: appAccessMode } = useGetAppAccessMode({ + appId, + isInstalledApp, + enabled: systemFeatures.webapp_auth.enabled, + }) + const { isPending: isCheckingPermission, data: userCanAccessResult } = useGetUserCanAccessApp({ + appId, + isInstalledApp, + enabled: systemFeatures.webapp_auth.enabled, + }) + // save message const [savedMessages, setSavedMessages] = useState<SavedMessage[]>([]) const fetchSavedMessage = async () => { @@ -302,10 +318,17 @@ const TextGeneration: FC<IMainProps> = ({ const varLen = promptConfig?.prompt_variables.length || 0 setIsCallBatchAPI(true) const allTaskList: Task[] = payloadData.map((item, i) => { - const inputs: Record<string, string> = {} + const inputs: Record<string, any> = {} if (varLen > 0) { item.slice(0, varLen).forEach((input, index) => { - inputs[promptConfig?.prompt_variables[index].key as string] = input + const varSchema = promptConfig?.prompt_variables[index] + inputs[varSchema?.key as string] = input + if (!input) { + if (varSchema?.type === 'string' || varSchema?.type === 'paragraph') + inputs[varSchema?.key as string] = '' + else + inputs[varSchema?.key as string] = undefined + } }) } return { @@ -388,10 +411,9 @@ const TextGeneration: FC<IMainProps> = ({ useEffect(() => { (async () => { const [appData, appParams]: any = await fetchInitData() - const { app_id: appId, site: siteInfo, can_replace_logo, custom_config } = appData + const { app_id: appId, site: siteInfo, custom_config } = appData setAppId(appId) setSiteInfo(siteInfo as SiteInfo) - setCanReplaceLogo(can_replace_logo) setCustomConfig(custom_config) changeLanguage(siteInfo.default_language) @@ -415,14 +437,7 @@ const TextGeneration: FC<IMainProps> = ({ }, []) // Can Use metadata(https://beta.nextjs.org/docs/api-reference/metadata) to set title. But it only works in server side client. - useEffect(() => { - if (siteInfo?.title) { - if (canReplaceLogo) - document.title = `${siteInfo.title}` - else - document.title = `${siteInfo.title} - Powered by Dify` - } - }, [siteInfo?.title, canReplaceLogo]) + useDocumentTitle(siteInfo?.title || t('share.generation.title')) useAppFavicon({ enable: !isInstalledApp, @@ -521,12 +536,31 @@ const TextGeneration: FC<IMainProps> = ({ </div> ) - if (!appId || !siteInfo || !promptConfig) { + const getSigninUrl = useCallback(() => { + const params = new URLSearchParams(searchParams) + params.delete('message') + params.set('redirect_url', pathname) + return `/webapp-signin?${params.toString()}` + }, [searchParams, pathname]) + + const backToHome = useCallback(() => { + removeAccessToken() + const url = getSigninUrl() + router.replace(url) + }, [getSigninUrl, router]) + + if (!appId || !siteInfo || !promptConfig || (systemFeatures.webapp_auth.enabled && (isGettingAccessMode || isCheckingPermission))) { return ( <div className='flex h-screen items-center'> <Loading type='app' /> </div>) } + if (systemFeatures.webapp_auth.enabled && !userCanAccessResult?.result) { + return <div className='flex h-full flex-col items-center justify-center gap-y-2'> + <AppUnavailable className='h-auto w-auto' code={403} unknownReason='no permission.' /> + {!isInstalledApp && <span className='system-sm-regular cursor-pointer text-text-tertiary' onClick={backToHome}>{t('common.userProfile.logout')}</span>} + </div> + } return ( <div className={cn( @@ -552,7 +586,7 @@ const TextGeneration: FC<IMainProps> = ({ imageUrl={siteInfo.icon_url} /> <div className='system-md-semibold grow truncate text-text-secondary'>{siteInfo.title}</div> - <MenuDropdown data={siteInfo} /> + <MenuDropdown hideLogout={isInstalledApp || appAccessMode?.accessMode === AccessMode.PUBLIC} data={siteInfo} /> </div> {siteInfo.description && ( <div className='system-xs-regular text-text-tertiary'>{siteInfo.description}</div> @@ -624,12 +658,13 @@ const TextGeneration: FC<IMainProps> = ({ !isPC && resultExisted && 'rounded-b-2xl border-b-[0.5px] border-divider-regular', )}> <div className='system-2xs-medium-uppercase text-text-tertiary'>{t('share.chat.poweredBy')}</div> - {customConfig?.replace_webapp_logo && ( - <img src={customConfig?.replace_webapp_logo} alt='logo' className='block h-5 w-auto' /> - )} - {!customConfig?.replace_webapp_logo && ( - <LogoSite className='!h-5' /> - )} + { + systemFeatures.branding.enabled && systemFeatures.branding.workspace_logo + ? <img src={systemFeatures.branding.workspace_logo} alt='logo' className='block h-5 w-auto' /> + : customConfig?.replace_webapp_logo + ? <img src={`${customConfig?.replace_webapp_logo}`} alt='logo' className='block h-5 w-auto' /> + : <DifyLogo size='small' /> + } </div> )} </div> @@ -657,7 +692,7 @@ const TextGeneration: FC<IMainProps> = ({ showResultPanel() }} > - <div className='h-1 w-8 cursor-grab rounded bg-divider-solid'/> + <div className='h-1 w-8 cursor-grab rounded bg-divider-solid' /> </div> )} {renderResWrap} diff --git a/web/app/components/share/text-generation/info-modal.tsx b/web/app/components/share/text-generation/info-modal.tsx index 9ed584be2f..156270fc85 100644 --- a/web/app/components/share/text-generation/info-modal.tsx +++ b/web/app/components/share/text-generation/info-modal.tsx @@ -1,9 +1,9 @@ import React from 'react' +import cn from 'classnames' import Modal from '@/app/components/base/modal' import AppIcon from '@/app/components/base/app-icon' import type { SiteInfo } from '@/models/share' import { appDefaultIconBackground } from '@/config' -import cn from 'classnames' type Props = { data?: SiteInfo diff --git a/web/app/components/share/text-generation/menu-dropdown.tsx b/web/app/components/share/text-generation/menu-dropdown.tsx index 91605f6a9a..adb926c7ca 100644 --- a/web/app/components/share/text-generation/menu-dropdown.tsx +++ b/web/app/components/share/text-generation/menu-dropdown.tsx @@ -6,25 +6,35 @@ import type { Placement } from '@floating-ui/react' import { RiEqualizer2Line, } from '@remixicon/react' +import { usePathname, useRouter } from 'next/navigation' +import Divider from '../../base/divider' +import InfoModal from './info-modal' import ActionButton from '@/app/components/base/action-button' import { PortalToFollowElem, PortalToFollowElemContent, PortalToFollowElemTrigger, } from '@/app/components/base/portal-to-follow-elem' -import InfoModal from './info-modal' +import ThemeSwitcher from '@/app/components/base/theme-switcher' import type { SiteInfo } from '@/models/share' import cn from '@/utils/classnames' +import { useGlobalPublicStore } from '@/context/global-public-context' +import { AccessMode } from '@/models/access-control' type Props = { data?: SiteInfo placement?: Placement + hideLogout?: boolean } const MenuDropdown: FC<Props> = ({ data, placement, + hideLogout, }) => { + const webAppAccessMode = useGlobalPublicStore(s => s.webAppAccessMode) + const router = useRouter() + const pathname = usePathname() const { t } = useTranslation() const [open, doSetOpen] = useState(false) const openRef = useRef(open) @@ -37,6 +47,12 @@ const MenuDropdown: FC<Props> = ({ setOpen(!openRef.current) }, [setOpen]) + const handleLogout = useCallback(() => { + localStorage.removeItem('token') + localStorage.removeItem('webapp_access_token') + router.replace(`/webapp-signin?redirect_url=${pathname}`) + }, [router, pathname]) + const [show, setShow] = useState(false) return ( @@ -59,6 +75,13 @@ const MenuDropdown: FC<Props> = ({ </PortalToFollowElemTrigger> <PortalToFollowElemContent className='z-50'> <div className='w-[224px] rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg-blur shadow-lg backdrop-blur-sm'> + <div className='p-1'> + <div className={cn('system-md-regular flex cursor-pointer items-center rounded-lg py-1.5 pl-3 pr-2 text-text-secondary')}> + <div className='grow'>{t('common.theme.theme')}</div> + <ThemeSwitcher /> + </div> + </div> + <Divider type='horizontal' className='my-0' /> <div className='p-1'> {data?.privacy_policy && ( <a href={data.privacy_policy} target='_blank' className='system-md-regular flex cursor-pointer items-center rounded-lg px-3 py-1.5 text-text-secondary hover:bg-state-base-hover'> @@ -73,6 +96,16 @@ const MenuDropdown: FC<Props> = ({ className='system-md-regular cursor-pointer rounded-lg px-3 py-1.5 text-text-secondary hover:bg-state-base-hover' >{t('common.userProfile.about')}</div> </div> + {!(hideLogout || webAppAccessMode === AccessMode.EXTERNAL_MEMBERS || webAppAccessMode === AccessMode.PUBLIC) && ( + <div className='p-1'> + <div + onClick={handleLogout} + className='system-md-regular cursor-pointer rounded-lg px-3 py-1.5 text-text-secondary hover:bg-state-base-hover' + > + {t('common.userProfile.logout')} + </div> + </div> + )} </div> </PortalToFollowElemContent> </PortalToFollowElem> diff --git a/web/app/components/share/text-generation/run-once/index.tsx b/web/app/components/share/text-generation/run-once/index.tsx index 721dbb42c3..546b21d2b0 100644 --- a/web/app/components/share/text-generation/run-once/index.tsx +++ b/web/app/components/share/text-generation/run-once/index.tsx @@ -45,7 +45,7 @@ const RunOnce: FC<IRunOnceProps> = ({ const onClear = () => { const newInputs: Record<string, any> = {} promptConfig.prompt_variables.forEach((item) => { - if (item.type === 'text-input' || item.type === 'paragraph') + if (item.type === 'string' || item.type === 'paragraph') newInputs[item.key] = '' else newInputs[item.key] = undefined @@ -66,7 +66,7 @@ const RunOnce: FC<IRunOnceProps> = ({ useEffect(() => { const newInputs: Record<string, any> = {} promptConfig.prompt_variables.forEach((item) => { - if (item.type === 'text-input' || item.type === 'paragraph') + if (item.type === 'string' || item.type === 'paragraph') newInputs[item.key] = '' else newInputs[item.key] = undefined diff --git a/web/app/components/share/utils.ts b/web/app/components/share/utils.ts index 9ce891a50c..8a897ab59a 100644 --- a/web/app/components/share/utils.ts +++ b/web/app/components/share/utils.ts @@ -10,8 +10,8 @@ export const getInitialTokenV2 = (): Record<string, any> => ({ version: 2, }) -export const checkOrSetAccessToken = async () => { - const sharedToken = globalThis.location.pathname.split('/').slice(-1)[0] +export const checkOrSetAccessToken = async (appCode?: string) => { + const sharedToken = appCode || globalThis.location.pathname.split('/').slice(-1)[0] const userId = (await getProcessedSystemVariablesFromUrlParams()).user_id const accessToken = localStorage.getItem('token') || JSON.stringify(getInitialTokenV2()) let accessTokenJson = getInitialTokenV2() @@ -23,8 +23,10 @@ export const checkOrSetAccessToken = async () => { catch { } + if (!accessTokenJson[sharedToken]?.[userId || 'DEFAULT']) { - const res = await fetchAccessToken(sharedToken, userId) + const webAppAccessToken = localStorage.getItem('webapp_access_token') + const res = await fetchAccessToken({ appCode: sharedToken, userId, webAppAccessToken }) accessTokenJson[sharedToken] = { ...accessTokenJson[sharedToken], [userId || 'DEFAULT']: res.access_token, @@ -33,7 +35,7 @@ export const checkOrSetAccessToken = async () => { } } -export const setAccessToken = async (sharedToken: string, token: string, user_id?: string) => { +export const setAccessToken = (sharedToken: string, token: string, user_id?: string) => { const accessToken = localStorage.getItem('token') || JSON.stringify(getInitialTokenV2()) let accessTokenJson = getInitialTokenV2() try { @@ -55,21 +57,6 @@ export const setAccessToken = async (sharedToken: string, token: string, user_id } export const removeAccessToken = () => { - const sharedToken = globalThis.location.pathname.split('/').slice(-1)[0] - - const accessToken = localStorage.getItem('token') || JSON.stringify(getInitialTokenV2()) - let accessTokenJson = getInitialTokenV2() - try { - accessTokenJson = JSON.parse(accessToken) - if (isTokenV1(accessTokenJson)) - accessTokenJson = getInitialTokenV2() - } - catch { - - } - - localStorage.removeItem(CONVERSATION_ID_INFO) - - delete accessTokenJson[sharedToken] - localStorage.setItem('token', JSON.stringify(accessTokenJson)) + localStorage.removeItem('token') + localStorage.removeItem('webapp_access_token') } diff --git a/web/app/components/tools/edit-custom-collection-modal/examples.ts b/web/app/components/tools/edit-custom-collection-modal/examples.ts index 0a3d023ca7..0d8369c14e 100644 --- a/web/app/components/tools/edit-custom-collection-modal/examples.ts +++ b/web/app/components/tools/edit-custom-collection-modal/examples.ts @@ -75,7 +75,7 @@ const examples = [ schema: type: string content: - application/json: + application/json: schema: $ref: "#/components/schemas/Pets" default: diff --git a/web/app/components/tools/marketplace/index.tsx b/web/app/components/tools/marketplace/index.tsx index f55bb9ecbd..8c805e1d5b 100644 --- a/web/app/components/tools/marketplace/index.tsx +++ b/web/app/components/tools/marketplace/index.tsx @@ -2,6 +2,7 @@ import { useEffect, useRef, } from 'react' +import { useTheme } from 'next-themes' import { RiArrowRightUpLine, RiArrowUpDoubleLine, @@ -25,7 +26,7 @@ const Marketplace = ({ }: MarketplaceProps) => { const locale = getLocaleOnClient() const { t } = useTranslation() - + const { theme } = useTheme() const { isLoading, marketplaceCollections, @@ -83,7 +84,7 @@ const Marketplace = ({ </span> {t('common.operation.in')} <a - href={`${MARKETPLACE_URL_PREFIX}?language=${locale}&q=${searchPluginText}&tags=${filterPluginTags.join(',')}`} + href={`${MARKETPLACE_URL_PREFIX}?language=${locale}&q=${searchPluginText}&tags=${filterPluginTags.join(',')}${theme ? `&theme=${theme}` : ''}`} className='system-sm-medium ml-1 flex items-center text-text-accent' target='_blank' > diff --git a/web/app/components/tools/provider-list.tsx b/web/app/components/tools/provider-list.tsx index d1144d7e69..0970daab9c 100644 --- a/web/app/components/tools/provider-list.tsx +++ b/web/app/components/tools/provider-list.tsx @@ -15,14 +15,14 @@ import WorkflowToolEmpty from '@/app/components/tools/add-tool-modal/empty' import Card from '@/app/components/plugins/card' import CardMoreInfo from '@/app/components/plugins/card/card-more-info' import PluginDetailPanel from '@/app/components/plugins/plugin-detail-panel' -import { useSelector as useAppContextSelector } from '@/context/app-context' import { useAllToolProviders } from '@/service/use-tools' import { useInstalledPluginList, useInvalidateInstalledPluginList } from '@/service/use-plugins' +import { useGlobalPublicStore } from '@/context/global-public-context' const ProviderList = () => { const { t } = useTranslation() + const { enable_marketplace } = useGlobalPublicStore(s => s.systemFeatures) const containerRef = useRef<HTMLDivElement>(null) - const { enable_marketplace } = useAppContextSelector(s => s.systemFeatures) const [activeTab, setActiveTab] = useTabSearchParams({ defaultTab: 'builtin', @@ -53,7 +53,10 @@ const ProviderList = () => { }) }, [activeTab, tagFilterValue, keywords, collectionList]) - const [currentProvider, setCurrentProvider] = useState<Collection | undefined>() + const [currentProviderId, setCurrentProviderId] = useState<string | undefined>() + const currentProvider = useMemo<Collection | undefined>(() => { + return filteredCollectionList.find(collection => collection.id === currentProviderId) + }, [currentProviderId, filteredCollectionList]) const { data: pluginList } = useInstalledPluginList() const invalidateInstalledPluginList = useInvalidateInstalledPluginList() const currentPluginDetail = useMemo(() => { @@ -70,14 +73,14 @@ const ProviderList = () => { > <div className={cn( 'sticky top-0 z-20 flex flex-wrap items-center justify-between gap-y-2 bg-background-body px-12 pb-2 pt-4 leading-[56px]', - currentProvider && 'pr-6', + currentProviderId && 'pr-6', )}> <TabSliderNew value={activeTab} onChange={(state) => { setActiveTab(state) if (state !== activeTab) - setCurrentProvider(undefined) + setCurrentProviderId(undefined) }} options={options} /> @@ -102,12 +105,12 @@ const ProviderList = () => { {filteredCollectionList.map(collection => ( <div key={collection.id} - onClick={() => setCurrentProvider(collection)} + onClick={() => setCurrentProviderId(collection.id)} > <Card className={cn( 'cursor-pointer border-[1.5px] border-transparent', - currentProvider?.id === collection.id && 'border-components-option-card-option-selected-border', + currentProviderId === collection.id && 'border-components-option-card-option-selected-border', )} hideCornerMark payload={{ @@ -141,19 +144,19 @@ const ProviderList = () => { /> ) } - </div> - </div> + </div > + </div > {currentProvider && !currentProvider.plugin_id && ( <ProviderDetail collection={currentProvider} - onHide={() => setCurrentProvider(undefined)} + onHide={() => setCurrentProviderId(undefined)} onRefreshData={refetch} /> )} <PluginDetailPanel detail={currentPluginDetail} onUpdate={() => invalidateInstalledPluginList()} - onHide={() => setCurrentProvider(undefined)} + onHide={() => setCurrentProviderId(undefined)} /> </> ) diff --git a/web/app/components/tools/provider/custom-create-card.tsx b/web/app/components/tools/provider/custom-create-card.tsx index 87c30b9ddb..f64daba8cd 100644 --- a/web/app/components/tools/provider/custom-create-card.tsx +++ b/web/app/components/tools/provider/custom-create-card.tsx @@ -14,6 +14,7 @@ import EditCustomToolModal from '@/app/components/tools/edit-custom-collection-m import { createCustomCollection } from '@/service/tools' import Toast from '@/app/components/base/toast' import { useAppContext } from '@/context/app-context' +import { useDocLink } from '@/context/i18n' type Props = { onRefreshData: () => void @@ -25,10 +26,11 @@ const Contribute = ({ onRefreshData }: Props) => { const language = getLanguage(locale) const { isCurrentWorkspaceManager } = useAppContext() + const docLink = useDocLink() const linkUrl = useMemo(() => { - if (language.startsWith('zh_')) - return 'https://docs.dify.ai/zh-hans/guides/tools#ru-he-chuang-jian-zi-ding-yi-gong-ju' - return 'https://docs.dify.ai/guides/tools#how-to-create-custom-tools' + return docLink('/guides/tools#how-to-create-custom-tools', { + 'zh-Hans': '/guides/tools#ru-he-chuang-jian-zi-ding-yi-gong-ju', + }) }, [language]) const [isShowEditCollectionToolModal, setIsShowEditCustomCollectionModal] = useState(false) diff --git a/web/app/components/workflow-app/components/workflow-header/chat-variable-trigger.tsx b/web/app/components/workflow-app/components/workflow-header/chat-variable-trigger.tsx index df93914285..0299d53ac9 100644 --- a/web/app/components/workflow-app/components/workflow-header/chat-variable-trigger.tsx +++ b/web/app/components/workflow-app/components/workflow-header/chat-variable-trigger.tsx @@ -3,9 +3,15 @@ import ChatVariableButton from '@/app/components/workflow/header/chat-variable-b import { useNodesReadOnly, } from '@/app/components/workflow/hooks' +import { useIsChatMode } from '../../hooks' const ChatVariableTrigger = () => { const { nodesReadOnly } = useNodesReadOnly() + const isChatMode = useIsChatMode() + + if (!isChatMode) + return null + return <ChatVariableButton disabled={nodesReadOnly} /> } export default memo(ChatVariableTrigger) diff --git a/web/app/components/workflow-app/components/workflow-header/features-trigger.tsx b/web/app/components/workflow-app/components/workflow-header/features-trigger.tsx index da64409090..f183dd9545 100644 --- a/web/app/components/workflow-app/components/workflow-header/features-trigger.tsx +++ b/web/app/components/workflow-app/components/workflow-header/features-trigger.tsx @@ -26,9 +26,8 @@ import type { StartNodeType } from '@/app/components/workflow/nodes/start/types' import { useToastContext } from '@/app/components/base/toast' import { usePublishWorkflow, useResetWorkflowVersionHistory } from '@/service/use-workflow' import type { PublishWorkflowParams } from '@/types/workflow' -import { fetchAppDetail, fetchAppSSO } from '@/service/apps' +import { fetchAppDetail } from '@/service/apps' import { useStore as useAppStore } from '@/app/components/app/store' -import { useSelector as useAppSelector } from '@/context/app-context' const FeaturesTrigger = () => { const { t } = useTranslation() @@ -36,7 +35,6 @@ const FeaturesTrigger = () => { const appDetail = useAppStore(s => s.appDetail) const appID = appDetail?.id const setAppDetail = useAppStore(s => s.setAppDetail) - const systemFeatures = useAppSelector(state => state.systemFeatures) const { nodesReadOnly, getNodesReadOnly, @@ -85,18 +83,12 @@ const FeaturesTrigger = () => { const updateAppDetail = useCallback(async () => { try { const res = await fetchAppDetail({ url: '/apps', id: appID! }) - if (systemFeatures.enable_web_sso_switch_component) { - const ssoRes = await fetchAppSSO({ appId: appID! }) - setAppDetail({ ...res, enable_sso: ssoRes.enabled }) - } - else { - setAppDetail({ ...res }) - } + setAppDetail({ ...res }) } catch (error) { console.error(error) } - }, [appID, setAppDetail, systemFeatures.enable_web_sso_switch_component]) + }, [appID, setAppDetail]) const { mutateAsync: publishWorkflow } = usePublishWorkflow(appID!) const onPublish = useCallback(async (params?: PublishWorkflowParams) => { if (await handleCheckBeforePublish()) { diff --git a/web/app/components/workflow-app/components/workflow-main.tsx b/web/app/components/workflow-app/components/workflow-main.tsx index 4ff1f4c624..2f2295cb59 100644 --- a/web/app/components/workflow-app/components/workflow-main.tsx +++ b/web/app/components/workflow-app/components/workflow-main.tsx @@ -8,6 +8,7 @@ import type { WorkflowProps } from '@/app/components/workflow' import WorkflowChildren from './workflow-children' import { useNodesSyncDraft, + useWorkflowRefreshDraft, useWorkflowRun, useWorkflowStartRun, } from '../hooks' @@ -32,6 +33,7 @@ const WorkflowMain = ({ doSyncWorkflowDraft, syncWorkflowDraftWhenPageClose, } = useNodesSyncDraft() + const { handleRefreshWorkflowDraft } = useWorkflowRefreshDraft() const { handleBackupDraft, handleLoadBackupDraft, @@ -49,6 +51,7 @@ const WorkflowMain = ({ return { syncWorkflowDraftWhenPageClose, doSyncWorkflowDraft, + handleRefreshWorkflowDraft, handleBackupDraft, handleLoadBackupDraft, handleRestoreFromPublishedWorkflow, @@ -61,6 +64,7 @@ const WorkflowMain = ({ }, [ syncWorkflowDraftWhenPageClose, doSyncWorkflowDraft, + handleRefreshWorkflowDraft, handleBackupDraft, handleLoadBackupDraft, handleRestoreFromPublishedWorkflow, diff --git a/web/app/components/workflow-app/components/workflow-panel.tsx b/web/app/components/workflow-app/components/workflow-panel.tsx index 3c1b5c8aac..dd368660ce 100644 --- a/web/app/components/workflow-app/components/workflow-panel.tsx +++ b/web/app/components/workflow-app/components/workflow-panel.tsx @@ -74,7 +74,7 @@ const WorkflowPanelOnRight = () => { ) } { - showChatVariablePanel && ( + showChatVariablePanel && isChatMode && ( <ChatVariablePanel /> ) } diff --git a/web/app/components/workflow-app/hooks/index.ts b/web/app/components/workflow-app/hooks/index.ts index 1517eb9a16..6373a8591c 100644 --- a/web/app/components/workflow-app/hooks/index.ts +++ b/web/app/components/workflow-app/hooks/index.ts @@ -4,3 +4,4 @@ export * from './use-nodes-sync-draft' export * from './use-workflow-run' export * from './use-workflow-start-run' export * from './use-is-chat-mode' +export * from './use-workflow-refresh-draft' diff --git a/web/app/components/workflow-app/hooks/use-nodes-sync-draft.ts b/web/app/components/workflow-app/hooks/use-nodes-sync-draft.ts index 7c6eb6a5be..db21cfb05e 100644 --- a/web/app/components/workflow-app/hooks/use-nodes-sync-draft.ts +++ b/web/app/components/workflow-app/hooks/use-nodes-sync-draft.ts @@ -6,20 +6,20 @@ import { useWorkflowStore, } from '@/app/components/workflow/store' import { BlockEnum } from '@/app/components/workflow/types' -import { useWorkflowUpdate } from '@/app/components/workflow/hooks' import { useNodesReadOnly, } from '@/app/components/workflow/hooks/use-workflow' import { syncWorkflowDraft } from '@/service/workflow' import { useFeaturesStore } from '@/app/components/base/features/hooks' import { API_PREFIX } from '@/config' +import { useWorkflowRefreshDraft } from '.' export const useNodesSyncDraft = () => { const store = useStoreApi() const workflowStore = useWorkflowStore() const featuresStore = useFeaturesStore() const { getNodesReadOnly } = useNodesReadOnly() - const { handleRefreshWorkflowDraft } = useWorkflowUpdate() + const { handleRefreshWorkflowDraft } = useWorkflowRefreshDraft() const params = useParams() const getPostParams = useCallback(() => { diff --git a/web/app/components/workflow-app/hooks/use-workflow-refresh-draft.ts b/web/app/components/workflow-app/hooks/use-workflow-refresh-draft.ts new file mode 100644 index 0000000000..c944e10c4c --- /dev/null +++ b/web/app/components/workflow-app/hooks/use-workflow-refresh-draft.ts @@ -0,0 +1,36 @@ +import { useCallback } from 'react' +import { useWorkflowStore } from '@/app/components/workflow/store' +import { fetchWorkflowDraft } from '@/service/workflow' +import type { WorkflowDataUpdater } from '@/app/components/workflow/types' +import { useWorkflowUpdate } from '@/app/components/workflow/hooks' + +export const useWorkflowRefreshDraft = () => { + const workflowStore = useWorkflowStore() + const { handleUpdateWorkflowCanvas } = useWorkflowUpdate() + + const handleRefreshWorkflowDraft = useCallback(() => { + const { + appId, + setSyncWorkflowDraftHash, + setIsSyncingWorkflowDraft, + setEnvironmentVariables, + setEnvSecrets, + setConversationVariables, + } = workflowStore.getState() + setIsSyncingWorkflowDraft(true) + fetchWorkflowDraft(`/apps/${appId}/workflows/draft`).then((response) => { + handleUpdateWorkflowCanvas(response.graph as WorkflowDataUpdater) + setSyncWorkflowDraftHash(response.hash) + setEnvSecrets((response.environment_variables || []).filter(env => env.value_type === 'secret').reduce((acc, env) => { + acc[env.id] = env.value + return acc + }, {} as Record<string, string>)) + setEnvironmentVariables(response.environment_variables?.map(env => env.value_type === 'secret' ? { ...env, value: '[__HIDDEN__]' } : env) || []) + setConversationVariables(response.conversation_variables || []) + }).finally(() => setIsSyncingWorkflowDraft(false)) + }, [handleUpdateWorkflowCanvas, workflowStore]) + + return { + handleRefreshWorkflowDraft, + } +} diff --git a/web/app/components/workflow/block-selector/all-tools.tsx b/web/app/components/workflow/block-selector/all-tools.tsx index 3ad0a41d54..36831aee3c 100644 --- a/web/app/components/workflow/block-selector/all-tools.tsx +++ b/web/app/components/workflow/block-selector/all-tools.tsx @@ -21,7 +21,7 @@ import ActionButton from '../../base/action-button' import { RiAddLine } from '@remixicon/react' import { PluginType } from '../../plugins/types' import { useMarketplacePlugins } from '../../plugins/marketplace/hooks' -import { useSelector as useAppContextSelector } from '@/context/app-context' +import { useGlobalPublicStore } from '@/context/global-public-context' type AllToolsProps = { className?: string @@ -87,7 +87,7 @@ const AllTools = ({ plugins: notInstalledPlugins = [], } = useMarketplacePlugins() - const { enable_marketplace } = useAppContextSelector(s => s.systemFeatures) + const { enable_marketplace } = useGlobalPublicStore(s => s.systemFeatures) useEffect(() => { if (enable_marketplace) return diff --git a/web/app/components/workflow/block-selector/index.tsx b/web/app/components/workflow/block-selector/index.tsx index f0f57adefe..9e55a24d9e 100644 --- a/web/app/components/workflow/block-selector/index.tsx +++ b/web/app/components/workflow/block-selector/index.tsx @@ -116,7 +116,7 @@ const NodeSelector: FC<NodeSelectorProps> = ({ : ( <div className={` - z-10 flex h-4 + z-10 flex h-4 w-4 cursor-pointer items-center justify-center rounded-full bg-components-button-primary-bg text-text-primary-on-surface hover:bg-components-button-primary-bg-hover ${triggerClassName?.(open)} `} diff --git a/web/app/components/workflow/block-selector/market-place-plugin/action.tsx b/web/app/components/workflow/block-selector/market-place-plugin/action.tsx index 4177fd40aa..9c3c69d601 100644 --- a/web/app/components/workflow/block-selector/market-place-plugin/action.tsx +++ b/web/app/components/workflow/block-selector/market-place-plugin/action.tsx @@ -1,6 +1,7 @@ 'use client' import type { FC } from 'react' import React, { useCallback, useEffect, useRef, useState } from 'react' +import { useTheme } from 'next-themes' import { useTranslation } from 'react-i18next' import { RiMoreFill } from '@remixicon/react' import ActionButton from '@/app/components/base/action-button' @@ -31,6 +32,7 @@ const OperationDropdown: FC<Props> = ({ version, }) => { const { t } = useTranslation() + const { theme } = useTheme() const openRef = useRef(open) const setOpen = useCallback((v: boolean) => { onOpenChange(v) @@ -78,7 +80,7 @@ const OperationDropdown: FC<Props> = ({ <PortalToFollowElemContent className='z-[9999]'> <div className='w-[112px] rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg-blur p-1 shadow-lg'> <div onClick={handleDownload} className='system-md-regular cursor-pointer rounded-lg px-3 py-1.5 text-text-secondary hover:bg-state-base-hover'>{t('common.operation.download')}</div> - <a href={`${MARKETPLACE_URL_PREFIX}/plugins/${author}/${name}`} target='_blank' className='system-md-regular block cursor-pointer rounded-lg px-3 py-1.5 text-text-secondary hover:bg-state-base-hover'>{t('common.operation.viewDetails')}</a> + <a href={`${MARKETPLACE_URL_PREFIX}/plugins/${author}/${name}${theme ? `?theme=${theme}` : ''}`} target='_blank' className='system-md-regular block cursor-pointer rounded-lg px-3 py-1.5 text-text-secondary hover:bg-state-base-hover'>{t('common.operation.viewDetails')}</a> </div> </PortalToFollowElemContent> </PortalToFollowElem> diff --git a/web/app/components/workflow/hooks-store/store.ts b/web/app/components/workflow/hooks-store/store.ts index 2e40cbfbc9..9f5e1a6650 100644 --- a/web/app/components/workflow/hooks-store/store.ts +++ b/web/app/components/workflow/hooks-store/store.ts @@ -18,6 +18,7 @@ type CommonHooksFnMap = { } ) => Promise<void> syncWorkflowDraftWhenPageClose: () => void + handleRefreshWorkflowDraft: () => void handleBackupDraft: () => void handleLoadBackupDraft: () => void handleRestoreFromPublishedWorkflow: (...args: any[]) => void @@ -35,6 +36,7 @@ export type Shape = { export const createHooksStore = ({ doSyncWorkflowDraft = async () => noop(), syncWorkflowDraftWhenPageClose = noop, + handleRefreshWorkflowDraft = noop, handleBackupDraft = noop, handleLoadBackupDraft = noop, handleRestoreFromPublishedWorkflow = noop, @@ -48,6 +50,7 @@ export const createHooksStore = ({ refreshAll: props => set(state => ({ ...state, ...props })), doSyncWorkflowDraft, syncWorkflowDraftWhenPageClose, + handleRefreshWorkflowDraft, handleBackupDraft, handleLoadBackupDraft, handleRestoreFromPublishedWorkflow, diff --git a/web/app/components/workflow/hooks/index.ts b/web/app/components/workflow/hooks/index.ts index 20a34c69e3..fda0f50aa6 100644 --- a/web/app/components/workflow/hooks/index.ts +++ b/web/app/components/workflow/hooks/index.ts @@ -16,3 +16,4 @@ export * from './use-shortcuts' export * from './use-workflow-interactions' export * from './use-workflow-mode' export * from './use-format-time-from-now' +export * from './use-workflow-refresh-draft' diff --git a/web/app/components/workflow/hooks/use-workflow-interactions.ts b/web/app/components/workflow/hooks/use-workflow-interactions.ts index 740868c594..636d3b94f9 100644 --- a/web/app/components/workflow/hooks/use-workflow-interactions.ts +++ b/web/app/components/workflow/hooks/use-workflow-interactions.ts @@ -313,7 +313,6 @@ export const useWorkflowZoom = () => { export const useWorkflowUpdate = () => { const reactflow = useReactFlow() - const workflowStore = useWorkflowStore() const { eventEmitter } = useEventEmitterContextContext() const handleUpdateWorkflowCanvas = useCallback((payload: WorkflowDataUpdater) => { @@ -333,32 +332,8 @@ export const useWorkflowUpdate = () => { setViewport(viewport) }, [eventEmitter, reactflow]) - const handleRefreshWorkflowDraft = useCallback(() => { - const { - appId, - setSyncWorkflowDraftHash, - setIsSyncingWorkflowDraft, - setEnvironmentVariables, - setEnvSecrets, - setConversationVariables, - } = workflowStore.getState() - setIsSyncingWorkflowDraft(true) - fetchWorkflowDraft(`/apps/${appId}/workflows/draft`).then((response) => { - handleUpdateWorkflowCanvas(response.graph as WorkflowDataUpdater) - setSyncWorkflowDraftHash(response.hash) - setEnvSecrets((response.environment_variables || []).filter(env => env.value_type === 'secret').reduce((acc, env) => { - acc[env.id] = env.value - return acc - }, {} as Record<string, string>)) - setEnvironmentVariables(response.environment_variables?.map(env => env.value_type === 'secret' ? { ...env, value: '[__HIDDEN__]' } : env) || []) - // #TODO chatVar sync# - setConversationVariables(response.conversation_variables || []) - }).finally(() => setIsSyncingWorkflowDraft(false)) - }, [handleUpdateWorkflowCanvas, workflowStore]) - return { handleUpdateWorkflowCanvas, - handleRefreshWorkflowDraft, } } diff --git a/web/app/components/workflow/hooks/use-workflow-refresh-draft.ts b/web/app/components/workflow/hooks/use-workflow-refresh-draft.ts new file mode 100644 index 0000000000..1948bd471d --- /dev/null +++ b/web/app/components/workflow/hooks/use-workflow-refresh-draft.ts @@ -0,0 +1,9 @@ +import { useHooksStore } from '@/app/components/workflow/hooks-store' + +export const useWorkflowRefreshDraft = () => { + const handleRefreshWorkflowDraft = useHooksStore(s => s.handleRefreshWorkflowDraft) + + return { + handleRefreshWorkflowDraft, + } +} diff --git a/web/app/components/workflow/hooks/use-workflow-run-event/use-workflow-node-started.ts b/web/app/components/workflow/hooks/use-workflow-run-event/use-workflow-node-started.ts index b537ccbb27..f1f184d4bb 100644 --- a/web/app/components/workflow/hooks/use-workflow-run-event/use-workflow-node-started.ts +++ b/web/app/components/workflow/hooks/use-workflow-run-event/use-workflow-node-started.ts @@ -67,6 +67,9 @@ export const useWorkflowNodeStarted = () => { incomeEdges.forEach((edge) => { const incomeNode = nodes.find(node => node.id === edge.source)! + if (!incomeNode || !('data' in incomeNode)) + return + if ( (!incomeNode.data._runningBranchId && edge.sourceHandle === 'source') || (incomeNode.data._runningBranchId && edge.sourceHandle === incomeNode.data._runningBranchId) diff --git a/web/app/components/workflow/index.tsx b/web/app/components/workflow/index.tsx index 9a3e13822a..549117faf7 100644 --- a/web/app/components/workflow/index.tsx +++ b/web/app/components/workflow/index.tsx @@ -44,7 +44,7 @@ import { useShortcuts, useWorkflow, useWorkflowReadOnly, - useWorkflowUpdate, + useWorkflowRefreshDraft, } from './hooks' import CustomNode from './nodes' import CustomNoteNode from './note-node' @@ -160,7 +160,7 @@ export const Workflow: FC<WorkflowProps> = memo(({ // eslint-disable-next-line react-hooks/exhaustive-deps }, []) - const { handleRefreshWorkflowDraft } = useWorkflowUpdate() + const { handleRefreshWorkflowDraft } = useWorkflowRefreshDraft() const handleSyncWorkflowDraftWhenPageClose = useCallback(() => { if (document.visibilityState === 'hidden') syncWorkflowDraftWhenPageClose() @@ -259,7 +259,7 @@ export const Workflow: FC<WorkflowProps> = memo(({ <div id='workflow-container' className={` - relative h-full w-full min-w-[960px] + relative h-full w-full min-w-[960px] ${workflowReadOnly && 'workflow-panel-animation'} ${nodeAnimation && 'workflow-node-animation'} `} @@ -316,6 +316,7 @@ export const Workflow: FC<WorkflowProps> = memo(({ nodesConnectable={!nodesReadOnly} nodesFocusable={!nodesReadOnly} edgesFocusable={!nodesReadOnly} + panOnScroll={false} panOnDrag={controlMode === ControlMode.Hand && !workflowReadOnly} zoomOnPinch={!workflowReadOnly} zoomOnScroll={!workflowReadOnly} diff --git a/web/app/components/workflow/nodes/_base/components/agent-strategy.tsx b/web/app/components/workflow/nodes/_base/components/agent-strategy.tsx index d67b7af1a4..674c768aa5 100644 --- a/web/app/components/workflow/nodes/_base/components/agent-strategy.tsx +++ b/web/app/components/workflow/nodes/_base/components/agent-strategy.tsx @@ -19,9 +19,7 @@ import { useWorkflowStore } from '../../../store' import { useRenderI18nObject } from '@/hooks/use-i18n' import type { NodeOutPutVar } from '../../../types' import type { Node } from 'reactflow' -import { useContext } from 'use-context-selector' -import I18n from '@/context/i18n' -import { LanguagesSupported } from '@/i18n/language' +import { useDocLink } from '@/context/i18n' export type Strategy = { agent_strategy_provider_name: string @@ -52,7 +50,7 @@ type CustomField = ToolSelectorSchema | MultipleToolSelectorSchema export const AgentStrategy = memo((props: AgentStrategyProps) => { const { strategy, onStrategyChange, formSchema, formValue, onFormValueChange, nodeOutputVars, availableNodes, nodeId } = props const { t } = useTranslation() - const { locale } = useContext(I18n) + const docLink = useDocLink() const defaultModel = useDefaultModel(ModelTypeEnum.textGeneration) const renderI18nObject = useRenderI18nObject() const workflowStore = useWorkflowStore() @@ -66,6 +64,7 @@ export const AgentStrategy = memo((props: AgentStrategyProps) => { case FormTypeEnum.textInput: { const def = schema as CredentialFormSchemaTextInput const value = props.value[schema.variable] || schema.default + const instanceId = schema.variable const onChange = (value: string) => { props.onChange({ ...props.value, [schema.variable]: value }) } @@ -77,6 +76,8 @@ export const AgentStrategy = memo((props: AgentStrategyProps) => { value={value} onChange={onChange} onGenerated={handleGenerated} + instanceId={instanceId} + key={instanceId} title={renderI18nObject(schema.label)} headerClassName='bg-transparent px-0 text-text-secondary system-sm-semibold-uppercase' containerBackgroundClassName='bg-transparent' @@ -220,11 +221,11 @@ export const AgentStrategy = memo((props: AgentStrategyProps) => { title={t('workflow.nodes.agent.strategy.configureTip')} description={<div className='text-xs text-text-tertiary'> {t('workflow.nodes.agent.strategy.configureTipDesc')} <br /> - <Link href={ - locale === LanguagesSupported[1] - ? 'https://docs.dify.ai/zh-hans/guides/workflow/node/agent#xuan-ze-agent-ce-le' - : 'https://docs.dify.ai/guides/workflow/node/agent#select-an-agent-strategy' - } className='text-text-accent-secondary' target='_blank'> + <Link href={docLink('/guides/workflow/node/agent#select-an-agent-strategy', { + 'zh-Hans': '/guides/workflow/node/agent#选择-agent-策略', + 'ja-JP': '/guides/workflow/node/agent#エージェント戦略の選択', + })} + className='text-text-accent-secondary' target='_blank'> {t('workflow.nodes.agent.learnMore')} </Link> </div>} diff --git a/web/app/components/workflow/nodes/_base/components/editor/code-editor/editor-support-vars.tsx b/web/app/components/workflow/nodes/_base/components/editor/code-editor/editor-support-vars.tsx index 7798ed3cb2..abc7d8dbc4 100644 --- a/web/app/components/workflow/nodes/_base/components/editor/code-editor/editor-support-vars.tsx +++ b/web/app/components/workflow/nodes/_base/components/editor/code-editor/editor-support-vars.tsx @@ -148,7 +148,7 @@ const CodeEditor: FC<Props> = ({ {isShowVarPicker && ( <div ref={popupRef} - className='w-[228px] space-y-1 rounded-lg border border-gray-200 bg-white p-1 shadow-lg' + className='w-[228px] space-y-1 rounded-lg border border-components-panel-border bg-components-panel-bg p-1 shadow-lg' style={{ position: 'fixed', top: popupPosition.y, diff --git a/web/app/components/workflow/nodes/_base/components/editor/code-editor/index.tsx b/web/app/components/workflow/nodes/_base/components/editor/code-editor/index.tsx index a185f16e2e..1e195c5d40 100644 --- a/web/app/components/workflow/nodes/_base/components/editor/code-editor/index.tsx +++ b/web/app/components/workflow/nodes/_base/components/editor/code-editor/index.tsx @@ -12,9 +12,10 @@ import { Theme } from '@/types/app' import useTheme from '@/hooks/use-theme' import './style.css' import { noop } from 'lodash-es' +import { basePath } from '@/utils/var' // load file from local instead of cdn https://github.com/suren-atoyan/monaco-react/issues/482 -loader.config({ paths: { vs: '/vs' } }) +loader.config({ paths: { vs: `${basePath}/vs` } }) const CODE_EDITOR_LINE_HEIGHT = 18 diff --git a/web/app/components/workflow/nodes/_base/components/editor/code-editor/style.css b/web/app/components/workflow/nodes/_base/components/editor/code-editor/style.css index 72e0087a3c..d364c1f15a 100644 --- a/web/app/components/workflow/nodes/_base/components/editor/code-editor/style.css +++ b/web/app/components/workflow/nodes/_base/components/editor/code-editor/style.css @@ -13,4 +13,4 @@ /* hide readonly tooltip */ .monaco-editor-overlaymessage { display: none !important; -} \ No newline at end of file +} diff --git a/web/app/components/workflow/nodes/_base/components/error-handle/default-value.tsx b/web/app/components/workflow/nodes/_base/components/error-handle/default-value.tsx index fa2d50f2c2..f9292be477 100644 --- a/web/app/components/workflow/nodes/_base/components/error-handle/default-value.tsx +++ b/web/app/components/workflow/nodes/_base/components/error-handle/default-value.tsx @@ -5,6 +5,7 @@ import Input from '@/app/components/base/input' import { VarType } from '@/app/components/workflow/types' import { CodeLanguage } from '@/app/components/workflow/nodes/code/types' import CodeEditor from '@/app/components/workflow/nodes/_base/components/editor/code-editor' +import { useDocLink } from '@/context/i18n' type DefaultValueProps = { forms: DefaultValueForm[] @@ -15,6 +16,7 @@ const DefaultValue = ({ onFormChange, }: DefaultValueProps) => { const { t } = useTranslation() + const docLink = useDocLink() const getFormChangeHandler = useCallback(({ key, type }: DefaultValueForm) => { return (payload: any) => { let value @@ -34,7 +36,9 @@ const DefaultValue = ({ {t('workflow.nodes.common.errorHandle.defaultValue.desc')}   <a - href='https://docs.dify.ai/guides/workflow/error-handling' + href={docLink('/guides/workflow/error-handling/README', { + 'zh-Hans': '/guides/workflow/error-handling/readme', + })} target='_blank' className='text-text-accent' > diff --git a/web/app/components/workflow/nodes/_base/components/error-handle/fail-branch-card.tsx b/web/app/components/workflow/nodes/_base/components/error-handle/fail-branch-card.tsx index 05a6cb96af..fa9cff3dc8 100644 --- a/web/app/components/workflow/nodes/_base/components/error-handle/fail-branch-card.tsx +++ b/web/app/components/workflow/nodes/_base/components/error-handle/fail-branch-card.tsx @@ -1,8 +1,10 @@ import { RiMindMap } from '@remixicon/react' import { useTranslation } from 'react-i18next' +import { useDocLink } from '@/context/i18n' const FailBranchCard = () => { const { t } = useTranslation() + const docLink = useDocLink() return ( <div className='px-4 pt-2'> @@ -17,7 +19,7 @@ const FailBranchCard = () => { {t('workflow.nodes.common.errorHandle.failBranch.customizeTip')}   <a - href='https://docs.dify.ai/guides/workflow/error-handling' + href={docLink('/guides/workflow/error-handling/error-type')} target='_blank' className='text-text-accent' > diff --git a/web/app/components/workflow/nodes/_base/components/field.tsx b/web/app/components/workflow/nodes/_base/components/field.tsx index 14e850b99a..aadcea1065 100644 --- a/web/app/components/workflow/nodes/_base/components/field.tsx +++ b/web/app/components/workflow/nodes/_base/components/field.tsx @@ -17,6 +17,7 @@ type Props = { children?: React.JSX.Element | string | null operations?: React.JSX.Element inline?: boolean + required?: boolean } const Field: FC<Props> = ({ @@ -28,6 +29,7 @@ const Field: FC<Props> = ({ operations, inline, supportFold, + required, }) => { const [fold, { toggle: toggleFold, @@ -38,7 +40,9 @@ const Field: FC<Props> = ({ onClick={() => supportFold && toggleFold()} className={cn('flex items-center justify-between', supportFold && 'cursor-pointer')}> <div className='flex h-6 items-center'> - <div className={cn(isSubTitle ? 'system-xs-medium-uppercase text-text-tertiary' : 'system-sm-semibold-uppercase text-text-secondary')}>{title}</div> + <div className={cn(isSubTitle ? 'system-xs-medium-uppercase text-text-tertiary' : 'system-sm-semibold-uppercase text-text-secondary')}> + {title} {required && <span className='text-text-destructive'>*</span>} + </div> {tooltip && ( <Tooltip popupContent={tooltip} diff --git a/web/app/components/workflow/nodes/_base/components/help-link.tsx b/web/app/components/workflow/nodes/_base/components/help-link.tsx index 2e7552001b..8bf74529f1 100644 --- a/web/app/components/workflow/nodes/_base/components/help-link.tsx +++ b/web/app/components/workflow/nodes/_base/components/help-link.tsx @@ -24,7 +24,7 @@ const HelpLink = ({ <a href={link} target='_blank' - className='mr-1 flex h-6 w-6 items-center justify-center' + className='mr-1 flex h-6 w-6 items-center justify-center rounded-md hover:bg-state-base-hover' > <RiBookOpenLine className='h-4 w-4 text-gray-500' /> </a> diff --git a/web/app/components/workflow/nodes/_base/components/input-support-select-var.tsx b/web/app/components/workflow/nodes/_base/components/input-support-select-var.tsx index aab14bb6f9..a741629da8 100644 --- a/web/app/components/workflow/nodes/_base/components/input-support-select-var.tsx +++ b/web/app/components/workflow/nodes/_base/components/input-support-select-var.tsx @@ -91,6 +91,9 @@ const Editor: FC<Props> = ({ acc[node.id] = { title: node.data.title, type: node.data.type, + width: node.width, + height: node.height, + position: node.position, } if (node.data.type === BlockEnum.Start) { acc.sys = { diff --git a/web/app/components/workflow/nodes/_base/components/node-handle.tsx b/web/app/components/workflow/nodes/_base/components/node-handle.tsx index 0dc099f5a0..d8754cd2ae 100644 --- a/web/app/components/workflow/nodes/_base/components/node-handle.tsx +++ b/web/app/components/workflow/nodes/_base/components/node-handle.tsx @@ -199,7 +199,7 @@ export const NodeSourceHandle = memo(({ onSelect={handleSelect} asChild triggerClassName={open => ` - hidden absolute top-0 left-0 pointer-events-none + hidden absolute top-0 left-0 pointer-events-none ${nodeSelectorClassName} group-hover:!flex ${data.selected && '!flex'} diff --git a/web/app/components/workflow/nodes/_base/components/node-position.tsx b/web/app/components/workflow/nodes/_base/components/node-position.tsx new file mode 100644 index 0000000000..404648dfa6 --- /dev/null +++ b/web/app/components/workflow/nodes/_base/components/node-position.tsx @@ -0,0 +1,54 @@ +import { memo } from 'react' +import { useTranslation } from 'react-i18next' +import { RiCrosshairLine } from '@remixicon/react' +import type { XYPosition } from 'reactflow' +import { useReactFlow, useStoreApi } from 'reactflow' +import TooltipPlus from '@/app/components/base/tooltip' +import { useNodesSyncDraft } from '@/app/components/workflow-app/hooks' + +type NodePositionProps = { + nodePosition: XYPosition, + nodeWidth?: number | null, + nodeHeight?: number | null, +} +const NodePosition = ({ + nodePosition, + nodeWidth, + nodeHeight, +}: NodePositionProps) => { + const { t } = useTranslation() + const reactflow = useReactFlow() + const store = useStoreApi() + const { doSyncWorkflowDraft } = useNodesSyncDraft() + + if (!nodePosition || !nodeWidth || !nodeHeight) return null + + const workflowContainer = document.getElementById('workflow-container') + const { transform } = store.getState() + const zoom = transform[2] + + const { clientWidth, clientHeight } = workflowContainer! + const { setViewport } = reactflow + + return ( + <TooltipPlus + popupContent={t('workflow.panel.moveToThisNode')} + > + <div + className='mr-1 flex h-6 w-6 cursor-pointer items-center justify-center rounded-md hover:bg-state-base-hover' + onClick={() => { + setViewport({ + x: (clientWidth - 400 - nodeWidth * zoom) / 2 - nodePosition.x * zoom, + y: (clientHeight - nodeHeight * zoom) / 2 - nodePosition.y * zoom, + zoom: transform[2], + }) + doSyncWorkflowDraft() + }} + > + <RiCrosshairLine className='h-4 w-4 text-text-tertiary' /> + </div> + </TooltipPlus> + ) +} + +export default memo(NodePosition) diff --git a/web/app/components/workflow/nodes/_base/components/prompt/editor.tsx b/web/app/components/workflow/nodes/_base/components/prompt/editor.tsx index c6233ff377..0a7ebc2a09 100644 --- a/web/app/components/workflow/nodes/_base/components/prompt/editor.tsx +++ b/web/app/components/workflow/nodes/_base/components/prompt/editor.tsx @@ -259,6 +259,9 @@ const Editor: FC<Props> = ({ acc[node.id] = { title: node.data.title, type: node.data.type, + width: node.width, + height: node.height, + position: node.position, } if (node.data.type === BlockEnum.Start) { acc.sys = { diff --git a/web/app/components/workflow/nodes/_base/components/retry/style.module.css b/web/app/components/workflow/nodes/_base/components/retry/style.module.css index 2ce8717af8..2ce4e7b400 100644 --- a/web/app/components/workflow/nodes/_base/components/retry/style.module.css +++ b/web/app/components/workflow/nodes/_base/components/retry/style.module.css @@ -2,4 +2,4 @@ .input::-webkit-outer-spin-button { -webkit-appearance: none; margin: 0; -} \ No newline at end of file +} diff --git a/web/app/components/workflow/nodes/_base/components/variable-tag.tsx b/web/app/components/workflow/nodes/_base/components/variable-tag.tsx index 83b07715fe..d73a3d4924 100644 --- a/web/app/components/workflow/nodes/_base/components/variable-tag.tsx +++ b/web/app/components/workflow/nodes/_base/components/variable-tag.tsx @@ -1,5 +1,5 @@ -import { useMemo } from 'react' -import { useNodes } from 'reactflow' +import { useCallback, useMemo } from 'react' +import { useNodes, useReactFlow, useStoreApi } from 'reactflow' import { capitalize } from 'lodash-es' import { useTranslation } from 'react-i18next' import { RiErrorWarningFill } from '@remixicon/react' @@ -48,12 +48,42 @@ const VariableTag = ({ const variableName = isSystemVar(valueSelector) ? valueSelector.slice(0).join('.') : valueSelector.slice(1).join('.') const isException = isExceptionVariable(variableName, node?.data.type) + const reactflow = useReactFlow() + const store = useStoreApi() + + const handleVariableJump = useCallback(() => { + const workflowContainer = document.getElementById('workflow-container') + const { + clientWidth, + clientHeight, + } = workflowContainer! + + const { + setViewport, + } = reactflow + const { transform } = store.getState() + const zoom = transform[2] + const position = node.position + setViewport({ + x: (clientWidth - 400 - node.width! * zoom) / 2 - position!.x * zoom, + y: (clientHeight - node.height! * zoom) / 2 - position!.y * zoom, + zoom: transform[2], + }) + }, [node, reactflow, store]) + const { t } = useTranslation() return ( <Tooltip popupContent={!isValid && t('workflow.errorMsg.invalidVariable')}> <div className={cn('border-[rgba(16, 2440,0.08)] inline-flex h-6 max-w-full items-center rounded-md border-[0.5px] border-divider-subtle bg-components-badge-white-to-dark px-1.5 text-xs shadow-xs', !isValid && 'border-red-400 !bg-[#FEF3F2]', - )}> + )} + onClick={(e) => { + if (e.metaKey || e.ctrlKey) { + e.stopPropagation() + handleVariableJump() + } + }} + > {(!isEnv && !isChatVar && <> {node && ( <> diff --git a/web/app/components/workflow/nodes/_base/components/variable/constant-field.tsx b/web/app/components/workflow/nodes/_base/components/variable/constant-field.tsx index 52d2675510..0adfa3c9fb 100644 --- a/web/app/components/workflow/nodes/_base/components/variable/constant-field.tsx +++ b/web/app/components/workflow/nodes/_base/components/variable/constant-field.tsx @@ -50,7 +50,7 @@ const ConstantField: FC<Props> = ({ {schema.type === FormTypeEnum.textNumber && ( <input type='number' - className='h-8 w-full overflow-hidden rounded-lg bg-gray-100 p-2 text-[13px] font-normal leading-8 text-gray-900 placeholder:text-gray-400 focus:outline-none' + className='h-8 w-full overflow-hidden rounded-lg bg-workflow-block-parma-bg p-2 text-[13px] font-normal leading-8 text-text-secondary placeholder:text-gray-400 focus:outline-none' value={value} onChange={handleStaticChange} readOnly={readonly} diff --git a/web/app/components/workflow/nodes/_base/components/variable/utils.ts b/web/app/components/workflow/nodes/_base/components/variable/utils.ts index 99faf77276..428c204dd3 100644 --- a/web/app/components/workflow/nodes/_base/components/variable/utils.ts +++ b/web/app/components/workflow/nodes/_base/components/variable/utils.ts @@ -134,18 +134,33 @@ const findExceptVarInObject = (obj: any, filterVar: (payload: Var, selector: Val const { children } = obj const isStructuredOutput = !!(children as StructuredOutput)?.schema?.properties + let childrenResult: Var[] | StructuredOutput | undefined + + if (isStructuredOutput) { + childrenResult = findExceptVarInStructuredOutput(children, filterVar) + } + else if (Array.isArray(children)) { + childrenResult = children.filter((item: Var) => { + const { children: itemChildren } = item + const currSelector = [...value_selector, item.variable] + + if (!itemChildren) + return filterVar(item, currSelector) + + const filteredObj = findExceptVarInObject(item, filterVar, currSelector, false) // File doesn't contain file children + return filteredObj.children && (filteredObj.children as Var[])?.length > 0 + }) + } + else { + childrenResult = [] + } + const res: Var = { variable: obj.variable, type: isFile ? VarType.file : VarType.object, - children: isStructuredOutput ? findExceptVarInStructuredOutput(children, filterVar) : children.filter((item: Var) => { - const { children } = item - const currSelector = [...value_selector, item.variable] - if (!children) - return filterVar(item, currSelector) - const obj = findExceptVarInObject(item, filterVar, currSelector, false) // File doesn't contains file children - return obj.children && (obj.children as Var[])?.length > 0 - }), + children: childrenResult, } + return res } @@ -562,8 +577,20 @@ export const toNodeOutputVars = ( chatVarList: conversationVariables, }, } + // Sort nodes in reverse chronological order (most recent first) + const sortedNodes = [...nodes].sort((a, b) => { + if (a.data.type === BlockEnum.Start) return 1 + if (b.data.type === BlockEnum.Start) return -1 + if (a.data.type === 'env') return 1 + if (b.data.type === 'env') return -1 + if (a.data.type === 'conversation') return 1 + if (b.data.type === 'conversation') return -1 + // sort nodes by x position + return (b.position?.x || 0) - (a.position?.x || 0) + }) + const res = [ - ...nodes.filter(node => SUPPORT_OUTPUT_VARS_NODE.includes(node?.data?.type)), + ...sortedNodes.filter(node => SUPPORT_OUTPUT_VARS_NODE.includes(node?.data?.type)), ...(environmentVariables.length > 0 ? [ENV_NODE] : []), ...((isChatMode && conversationVariables.length > 0) ? [CHAT_VAR_NODE] : []), ].map((node) => { @@ -586,6 +613,7 @@ const getIterationItemType = ({ const isSystem = isSystemVar(valueSelector) const targetVar = isSystem ? beforeNodesOutputVars.find(v => v.isStartNode) : beforeNodesOutputVars.find(v => v.nodeId === outputVarNodeId) + if (!targetVar) return VarType.string @@ -596,17 +624,16 @@ const getIterationItemType = ({ arrayType = curr.find((v: any) => v.variable === (valueSelector).join('.'))?.type } else { - (valueSelector).slice(1).forEach((key, i) => { - const isLast = i === valueSelector.length - 2 - curr = curr?.find((v: any) => v.variable === key) - if (isLast) { - arrayType = curr?.type - } - else { - if (curr?.type === VarType.object || curr?.type === VarType.file) - curr = curr.children - } - }) + for (let i = 1; i < valueSelector.length; i++) { + const key = valueSelector[i] + const isLast = i === valueSelector.length - 1 + curr = Array.isArray(curr) ? curr.find(v => v.variable === key) : [] + + if (isLast) + arrayType = curr?.type + else if (curr?.type === VarType.object || curr?.type === VarType.file) + curr = curr.children || [] + } } switch (arrayType as VarType) { @@ -631,7 +658,7 @@ const getLoopItemType = ({ }: { valueSelector: ValueSelector beforeNodesOutputVars: NodeOutPutVar[] - // eslint-disable-next-line sonarjs/no-identical-functions + }): VarType => { const outputVarNodeId = valueSelector[0] const isSystem = isSystemVar(valueSelector) diff --git a/web/app/components/workflow/nodes/_base/components/variable/var-reference-picker.tsx b/web/app/components/workflow/nodes/_base/components/variable/var-reference-picker.tsx index 789da34f9d..e9825cd44a 100644 --- a/web/app/components/workflow/nodes/_base/components/variable/var-reference-picker.tsx +++ b/web/app/components/workflow/nodes/_base/components/variable/var-reference-picker.tsx @@ -9,7 +9,7 @@ import { RiMoreLine, } from '@remixicon/react' import produce from 'immer' -import { useStoreApi } from 'reactflow' +import { useReactFlow, useStoreApi } from 'reactflow' import RemoveButton from '../remove-button' import useAvailableVarList from '../../hooks/use-available-var-list' import VarReferencePopup from './var-reference-popup' @@ -111,6 +111,9 @@ const VarReferencePicker: FC<Props> = ({ passedInAvailableNodes, filterVar, }) + + const reactflow = useReactFlow() + const startNode = availableNodes.find((node: any) => { return node.data.type === BlockEnum.Start }) @@ -172,7 +175,11 @@ const VarReferencePicker: FC<Props> = ({ if (isSystemVar(value as ValueSelector)) return startNode?.data - return getNodeInfoById(availableNodes, outputVarNodeId)?.data + const node = getNodeInfoById(availableNodes, outputVarNodeId)?.data + return { + ...node, + id: outputVarNodeId, + } }, [value, hasValue, isConstant, isIterationVar, iterationNode, availableNodes, outputVarNodeId, startNode, isLoopVar, loopNode]) const isShowAPart = (value as ValueSelector).length > 2 @@ -237,6 +244,28 @@ const VarReferencePicker: FC<Props> = ({ onChange([], varKindType) }, [onChange, varKindType]) + const handleVariableJump = useCallback((nodeId: string) => { + const currentNodeIndex = availableNodes.findIndex(node => node.id === nodeId) + const currentNode = availableNodes[currentNodeIndex] + + const workflowContainer = document.getElementById('workflow-container') + const { + clientWidth, + clientHeight, + } = workflowContainer! + const { + setViewport, + } = reactflow + const { transform } = store.getState() + const zoom = transform[2] + const position = currentNode.position + setViewport({ + x: (clientWidth - 400 - currentNode.width! * zoom) / 2 - position.x * zoom, + y: (clientHeight - currentNode.height! * zoom) / 2 - position.y * zoom, + zoom: transform[2], + }) + }, [availableNodes, reactflow, store]) + const type = getCurrentVariableType({ parentNode: isInIteration ? iterationNode : loopNode, valueSelector: value as ValueSelector, @@ -357,7 +386,12 @@ const VarReferencePicker: FC<Props> = ({ ? ( <> {isShowNodeName && !isEnv && !isChatVar && ( - <div className='flex items-center'> + <div className='flex items-center' onClick={(e) => { + if (e.metaKey || e.ctrlKey) { + e.stopPropagation() + handleVariableJump(outputVarNode?.id) + } + }}> <div className='h-3 px-[1px]'> {outputVarNode?.type && <VarBlockIcon className='!text-text-primary' diff --git a/web/app/components/workflow/nodes/_base/components/variable/var-reference-popup.tsx b/web/app/components/workflow/nodes/_base/components/variable/var-reference-popup.tsx index e35977ae31..9398ae7361 100644 --- a/web/app/components/workflow/nodes/_base/components/variable/var-reference-popup.tsx +++ b/web/app/components/workflow/nodes/_base/components/variable/var-reference-popup.tsx @@ -2,12 +2,10 @@ import type { FC } from 'react' import React from 'react' import { useTranslation } from 'react-i18next' -import { useContext } from 'use-context-selector' import VarReferenceVars from './var-reference-vars' import type { NodeOutPutVar, ValueSelector, Var } from '@/app/components/workflow/types' import ListEmpty from '@/app/components/base/list-empty' -import { LanguagesSupported } from '@/i18n/language' -import I18n from '@/context/i18n' +import { useDocLink } from '@/context/i18n' type Props = { vars: NodeOutPutVar[] @@ -24,7 +22,7 @@ const VarReferencePopup: FC<Props> = ({ isSupportFileVar = true, }) => { const { t } = useTranslation() - const { locale } = useContext(I18n) + const docLink = useDocLink() // max-h-[300px] overflow-y-auto todo: use portal to handle long list return ( <div className='space-y-1 rounded-lg border border-components-panel-border bg-components-panel-bg p-1 shadow-lg' style={{ @@ -47,7 +45,12 @@ const VarReferencePopup: FC<Props> = ({ {t('workflow.variableReference.assignedVarsDescription')} <a target='_blank' rel='noopener noreferrer' className='text-text-accent-secondary' - href={locale !== LanguagesSupported[1] ? 'https://docs.dify.ai/guides/workflow/variables#conversation-variables' : `https://docs.dify.ai/${locale.toLowerCase()}/guides/workflow/variables#hui-hua-bian-liang`}>{t('workflow.variableReference.conversationVars')}</a> + href={docLink('/guides/workflow/variables#conversation-variables', { + 'zh-Hans': '/guides/workflow/variables#会话变量', + 'ja-JP': '/guides/workflow/variables#会話変数', + })}> + {t('workflow.variableReference.conversationVars')} + </a> </div>} /> )) diff --git a/web/app/components/workflow/nodes/_base/components/variable/var-reference-vars.tsx b/web/app/components/workflow/nodes/_base/components/variable/var-reference-vars.tsx index 023916ec5b..9acbdf4ff7 100644 --- a/web/app/components/workflow/nodes/_base/components/variable/var-reference-vars.tsx +++ b/web/app/components/workflow/nodes/_base/components/variable/var-reference-vars.tsx @@ -141,7 +141,7 @@ const Item: FC<ItemProps> = ({ ref={itemRef} className={cn( (isObj || isStructureOutput) ? ' pr-1' : 'pr-[18px]', - isHovering && ((isObj || isStructureOutput) ? 'bg-primary-50' : 'bg-state-base-hover'), + isHovering && ((isObj || isStructureOutput) ? 'bg-components-panel-on-panel-item-bg-hover' : 'bg-state-base-hover'), 'relative flex h-6 w-full cursor-pointer items-center rounded-md pl-3') } onClick={handleChosen} diff --git a/web/app/components/workflow/nodes/_base/components/variable/var-type-picker.tsx b/web/app/components/workflow/nodes/_base/components/variable/var-type-picker.tsx index 926d7ac705..d0e42d5e47 100644 --- a/web/app/components/workflow/nodes/_base/components/variable/var-type-picker.tsx +++ b/web/app/components/workflow/nodes/_base/components/variable/var-type-picker.tsx @@ -43,7 +43,7 @@ const VarReferencePicker: FC<Props> = ({ offset={4} > <PortalToFollowElemTrigger onClick={() => setOpen(!open)} className='w-[120px] cursor-pointer'> - <div className='flex h-8 items-center justify-between rounded-lg border-0 bg-components-button-secondary-bg px-2.5 text-[13px] text-text-primary'> + <div className='flex h-8 items-center justify-between rounded-lg border-0 bg-components-input-bg-normal px-2.5 text-[13px] text-text-primary'> <div className='w-0 grow truncate capitalize' title={value}>{value}</div> <RiArrowDownSLine className='h-3.5 w-3.5 shrink-0 text-text-secondary' /> </div> diff --git a/web/app/components/workflow/nodes/_base/hooks/use-node-help-link.ts b/web/app/components/workflow/nodes/_base/hooks/use-node-help-link.ts index 3c68fbd1fd..2141abe7ad 100644 --- a/web/app/components/workflow/nodes/_base/hooks/use-node-help-link.ts +++ b/web/app/components/workflow/nodes/_base/hooks/use-node-help-link.ts @@ -1,14 +1,12 @@ import { useMemo } from 'react' -import { useGetLanguage } from '@/context/i18n' +import { useDocLink, useGetLanguage } from '@/context/i18n' import { BlockEnum } from '@/app/components/workflow/types' export const useNodeHelpLink = (nodeType: BlockEnum) => { const language = useGetLanguage() + const docLink = useDocLink() const prefixLink = useMemo(() => { - if (language === 'zh_Hans') - return 'https://docs.dify.ai/zh-hans/guides/workflow/node/' - - return 'https://docs.dify.ai/guides/workflow/node/' + return docLink('/guides/workflow/node/') }, [language]) const linkMap = useMemo(() => { if (language === 'zh_Hans') { diff --git a/web/app/components/workflow/nodes/_base/panel.tsx b/web/app/components/workflow/nodes/_base/panel.tsx index 2ee39a3b06..49c61b3416 100644 --- a/web/app/components/workflow/nodes/_base/panel.tsx +++ b/web/app/components/workflow/nodes/_base/panel.tsx @@ -16,6 +16,7 @@ import { useTranslation } from 'react-i18next' import NextStep from './components/next-step' import PanelOperator from './components/panel-operator' import HelpLink from './components/help-link' +import NodePosition from './components/node-position' import { DescriptionInput, TitleInput, @@ -55,6 +56,9 @@ const BasePanel: FC<BasePanelProps> = ({ id, data, children, + position, + width, + height, }) => { const { t } = useTranslation() const { showMessageLogModal } = useAppStore(useShallow(state => ({ @@ -150,6 +154,7 @@ const BasePanel: FC<BasePanelProps> = ({ </Tooltip> ) } + <NodePosition nodePosition={position} nodeWidth={width} nodeHeight={height}></NodePosition> <HelpLink nodeType={data.type} /> <PanelOperator id={id} data={data} showHelpLink={false} /> <div className='mx-3 h-3.5 w-[1px] bg-divider-regular' /> diff --git a/web/app/components/workflow/nodes/agent/default.ts b/web/app/components/workflow/nodes/agent/default.ts index 4e7b447de8..d80def7bd2 100644 --- a/web/app/components/workflow/nodes/agent/default.ts +++ b/web/app/components/workflow/nodes/agent/default.ts @@ -126,7 +126,7 @@ const nodeDefault: NodeDefault<AgentNodeType> = { } } // common params - if (param.required && !payload.agent_parameters?.[param.name]?.value) { + if (param.required && !(payload.agent_parameters?.[param.name]?.value || param.default)) { return { isValid: false, errorMessage: t('workflow.errorMsg.fieldRequired', { field: renderI18nObject(param.label, language) }), diff --git a/web/app/components/workflow/nodes/agent/panel.tsx b/web/app/components/workflow/nodes/agent/panel.tsx index 19be60cb51..f92e92dbcb 100644 --- a/web/app/components/workflow/nodes/agent/panel.tsx +++ b/web/app/components/workflow/nodes/agent/panel.tsx @@ -81,7 +81,11 @@ const AgentPanel: FC<NodePanelProps<AgentNodeType>> = (props) => { const resetEditor = useStore(s => s.setControlPromptEditorRerenderKey) return <div className='my-2'> - <Field title={t('workflow.nodes.agent.strategy.label')} className='px-4 py-2' tooltip={t('workflow.nodes.agent.strategy.tooltip')} > + <Field + required + title={t('workflow.nodes.agent.strategy.label')} + className='px-4 py-2' + tooltip={t('workflow.nodes.agent.strategy.tooltip')} > <AgentStrategy strategy={inputs.agent_strategy_name ? { agent_strategy_provider_name: inputs.agent_strategy_provider_name!, @@ -98,7 +102,6 @@ const AgentPanel: FC<NodePanelProps<AgentNodeType>> = (props) => { agent_strategy_label: strategy?.agent_strategy_label, output_schema: strategy!.agent_output_schema, plugin_unique_identifier: strategy!.plugin_unique_identifier, - agent_parameters: {}, }) resetEditor(Date.now()) }} diff --git a/web/app/components/workflow/nodes/agent/use-config.ts b/web/app/components/workflow/nodes/agent/use-config.ts index 32d7c6f9db..8196caa3f5 100644 --- a/web/app/components/workflow/nodes/agent/use-config.ts +++ b/web/app/components/workflow/nodes/agent/use-config.ts @@ -85,12 +85,13 @@ const useConfig = (id: string, payload: AgentNodeType) => { enabled: Boolean(pluginId), }) const formData = useMemo(() => { + const paramNameList = (currentStrategy?.parameters || []).map(item => item.name) return Object.fromEntries( - Object.entries(inputs.agent_parameters || {}).map(([key, value]) => { + Object.entries(inputs.agent_parameters || {}).filter(([name]) => paramNameList.includes(name)).map(([key, value]) => { return [key, value.value] }), ) - }, [inputs.agent_parameters]) + }, [inputs.agent_parameters, currentStrategy?.parameters]) const onFormChange = (value: Record<string, any>) => { const res: ToolVarInputs = {} Object.entries(value).forEach(([key, val]) => { diff --git a/web/app/components/workflow/nodes/assigner/components/var-list/index.tsx b/web/app/components/workflow/nodes/assigner/components/var-list/index.tsx index 170e6484c4..f34a1435ad 100644 --- a/web/app/components/workflow/nodes/assigner/components/var-list/index.tsx +++ b/web/app/components/workflow/nodes/assigner/components/var-list/index.tsx @@ -152,6 +152,7 @@ const VarList: FC<Props> = ({ /> </div> {item.operation !== WriteMode.clear && item.operation !== WriteMode.set + && item.operation !== WriteMode.removeFirst && item.operation !== WriteMode.removeLast && !writeModeTypesNum?.includes(item.operation) && ( <VarReferencePicker diff --git a/web/app/components/workflow/nodes/assigner/default.ts b/web/app/components/workflow/nodes/assigner/default.ts index f443ae1d3b..6341305576 100644 --- a/web/app/components/workflow/nodes/assigner/default.ts +++ b/web/app/components/workflow/nodes/assigner/default.ts @@ -29,7 +29,7 @@ const nodeDefault: NodeDefault<AssignerNodeType> = { if (!errorMessages && !value.variable_selector?.length) errorMessages = t(`${i18nPrefix}.fieldRequired`, { field: t('workflow.nodes.assigner.assignedVariable') }) - if (!errorMessages && value.operation !== WriteMode.clear) { + if (!errorMessages && value.operation !== WriteMode.clear && value.operation !== WriteMode.removeFirst && value.operation !== WriteMode.removeLast) { if (value.operation === WriteMode.set || value.operation === WriteMode.increment || value.operation === WriteMode.decrement || value.operation === WriteMode.multiply || value.operation === WriteMode.divide) { diff --git a/web/app/components/workflow/nodes/assigner/types.ts b/web/app/components/workflow/nodes/assigner/types.ts index 9f06f65dc3..85d2b2850f 100644 --- a/web/app/components/workflow/nodes/assigner/types.ts +++ b/web/app/components/workflow/nodes/assigner/types.ts @@ -10,6 +10,8 @@ export enum WriteMode { decrement = '-=', multiply = '*=', divide = '/=', + removeFirst = 'remove-first', + removeLast = 'remove-last', } export enum AssignerNodeInputType { diff --git a/web/app/components/workflow/nodes/assigner/use-config.ts b/web/app/components/workflow/nodes/assigner/use-config.ts index e7beb1f37a..cbd5475483 100644 --- a/web/app/components/workflow/nodes/assigner/use-config.ts +++ b/web/app/components/workflow/nodes/assigner/use-config.ts @@ -69,7 +69,7 @@ const useConfig = (id: string, rawPayload: AssignerNodeType) => { newSetInputs(newInputs) }, [inputs, newSetInputs]) - const writeModeTypesArr = [WriteMode.overwrite, WriteMode.clear, WriteMode.append, WriteMode.extend] + const writeModeTypesArr = [WriteMode.overwrite, WriteMode.clear, WriteMode.append, WriteMode.extend, WriteMode.removeFirst, WriteMode.removeLast] const writeModeTypes = [WriteMode.overwrite, WriteMode.clear, WriteMode.set] const writeModeTypesNum = [WriteMode.increment, WriteMode.decrement, WriteMode.multiply, WriteMode.divide] diff --git a/web/app/components/workflow/nodes/code/code-parser.spec.ts b/web/app/components/workflow/nodes/code/code-parser.spec.ts index b5d28dd136..67f2c218e1 100644 --- a/web/app/components/workflow/nodes/code/code-parser.spec.ts +++ b/web/app/components/workflow/nodes/code/code-parser.spec.ts @@ -57,7 +57,7 @@ describe('extractFunctionParams', () => { }) }) - // JavaScriptのテストケース + // JavaScript のテストケース describe('JavaScript', () => { test('handles no parameters', () => { const result = extractFunctionParams(SAMPLE_CODES.javascript.noParams, CodeLanguage.javascript) @@ -180,7 +180,7 @@ function main(name, age, city) { } describe('extractReturnType', () => { - // Python3のテスト + // Python3 のテスト describe('Python3', () => { test('extracts single return value', () => { const result = extractReturnType(RETURN_TYPE_SAMPLES.python3.singleReturn, CodeLanguage.python3) @@ -247,7 +247,7 @@ describe('extractReturnType', () => { }) }) - // JavaScriptのテスト + // JavaScript のテスト describe('JavaScript', () => { test('extracts single return value', () => { const result = extractReturnType(RETURN_TYPE_SAMPLES.javascript.singleReturn, CodeLanguage.javascript) diff --git a/web/app/components/workflow/nodes/code/code-parser.ts b/web/app/components/workflow/nodes/code/code-parser.ts index 0973a01bd0..216e13eaca 100644 --- a/web/app/components/workflow/nodes/code/code-parser.ts +++ b/web/app/components/workflow/nodes/code/code-parser.ts @@ -31,7 +31,7 @@ export const extractReturnType = (code: string, language: CodeLanguage): OutputV if (returnIndex === -1) return {} - // returnから始まる部分文字列を取得 + // return から始まる部分文字列を取得 const codeAfterReturn = codeWithoutComments.slice(returnIndex) let bracketCount = 0 diff --git a/web/app/components/workflow/nodes/code/panel.tsx b/web/app/components/workflow/nodes/code/panel.tsx index 4fd7a1a11c..a0b7535f89 100644 --- a/web/app/components/workflow/nodes/code/panel.tsx +++ b/web/app/components/workflow/nodes/code/panel.tsx @@ -117,8 +117,8 @@ const Panel: FC<NodePanelProps<CodeNodeType>> = ({ operations={ <AddButton onClick={handleAddOutputVariable} /> } + required > - <OutputVarList readonly={readOnly} outputs={inputs.outputs} diff --git a/web/app/components/workflow/nodes/document-extractor/panel.tsx b/web/app/components/workflow/nodes/document-extractor/panel.tsx index df144d56bf..5ed1425778 100644 --- a/web/app/components/workflow/nodes/document-extractor/panel.tsx +++ b/web/app/components/workflow/nodes/document-extractor/panel.tsx @@ -64,6 +64,7 @@ const Panel: FC<NodePanelProps<DocExtractorNodeType>> = ({ <div className='space-y-4 px-4 pb-4'> <Field title={t(`${i18nPrefix}.inputVar`)} + required > <> <VarReferencePicker diff --git a/web/app/components/workflow/nodes/end/node.tsx b/web/app/components/workflow/nodes/end/node.tsx index e0c5604391..6906e0f77c 100644 --- a/web/app/components/workflow/nodes/end/node.tsx +++ b/web/app/components/workflow/nodes/end/node.tsx @@ -52,7 +52,7 @@ const Node: FC<NodeProps<EndNodeType>> = ({ isChatMode, }) return ( - <div key={index} className='flex h-6 items-center justify-between space-x-1 rounded-md bg-components-badge-white-to-dark px-1 text-xs font-normal text-text-secondary'> + <div key={index} className='flex h-6 items-center justify-between space-x-1 rounded-md bg-workflow-block-parma-bg px-1 text-xs font-normal text-text-secondary'> <div className='flex items-center text-xs font-medium text-text-tertiary'> {!isEnv && !isChatVar && ( <> diff --git a/web/app/components/workflow/nodes/http/components/curl-panel.tsx b/web/app/components/workflow/nodes/http/components/curl-panel.tsx index 52e28d7336..a5339a1f39 100644 --- a/web/app/components/workflow/nodes/http/components/curl-panel.tsx +++ b/web/app/components/workflow/nodes/http/components/curl-panel.tsx @@ -2,7 +2,7 @@ import type { FC } from 'react' import React, { useCallback, useState } from 'react' import { useTranslation } from 'react-i18next' -import { BodyType, type HttpNodeType, Method } from '../types' +import { BodyPayloadValueType, BodyType, type HttpNodeType, Method } from '../types' import Modal from '@/app/components/base/modal' import Button from '@/app/components/base/button' import Textarea from '@/app/components/base/textarea' @@ -51,11 +51,16 @@ const parseCurl = (curlCommand: string): { node: HttpNodeType | null; error: str case '-d': case '--data': case '--data-raw': - case '--data-binary': + case '--data-binary': { if (i + 1 >= args.length) return { node: null, error: 'Missing data value after -d, --data, --data-raw, or --data-binary.' } - node.body = { type: BodyType.rawText, data: args[++i].replace(/^['"]|['"]$/g, '') } + const bodyPayload = [{ + type: BodyPayloadValueType.text, + value: args[++i].replace(/^['"]|['"]$/g, ''), + }] + node.body = { type: BodyType.rawText, data: bodyPayload } break + } case '-F': case '--form': { if (i + 1 >= args.length) diff --git a/web/app/components/workflow/nodes/http/panel.tsx b/web/app/components/workflow/nodes/http/panel.tsx index 4ba9b04a12..60f3de81c0 100644 --- a/web/app/components/workflow/nodes/http/panel.tsx +++ b/web/app/components/workflow/nodes/http/panel.tsx @@ -69,6 +69,7 @@ const Panel: FC<NodePanelProps<HttpNodeType>> = ({ <div className='space-y-4 px-4 pb-4'> <Field title={t(`${i18nPrefix}.api`)} + required operations={ <div className='flex'> <div @@ -126,6 +127,7 @@ const Panel: FC<NodePanelProps<HttpNodeType>> = ({ </Field> <Field title={t(`${i18nPrefix}.body`)} + required > <EditBody nodeId={id} diff --git a/web/app/components/workflow/nodes/http/use-config.ts b/web/app/components/workflow/nodes/http/use-config.ts index 68c0a89fe7..73c84c369f 100644 --- a/web/app/components/workflow/nodes/http/use-config.ts +++ b/web/app/components/workflow/nodes/http/use-config.ts @@ -42,6 +42,12 @@ const useConfig = (id: string, payload: HttpNodeType) => { data: transformToBodyPayload(bodyData, [BodyType.formData, BodyType.xWwwFormUrlencoded].includes(newInputs.body.type)), } } + else if (!bodyData) { + newInputs.body = { + ...newInputs.body, + data: [], + } + } setInputs(newInputs) setIsDataReady(true) @@ -151,7 +157,7 @@ const useConfig = (id: string, payload: HttpNodeType) => { inputs.url, inputs.headers, inputs.params, - typeof inputs.body.data === 'string' ? inputs.body.data : inputs.body.data.map(item => item.value).join(''), + typeof inputs.body.data === 'string' ? inputs.body.data : inputs.body.data?.map(item => item.value).join(''), fileVarInputs, ]) diff --git a/web/app/components/workflow/nodes/if-else/components/condition-list/condition-input.tsx b/web/app/components/workflow/nodes/if-else/components/condition-list/condition-input.tsx index c393aaaa58..e6f08184c8 100644 --- a/web/app/components/workflow/nodes/if-else/components/condition-list/condition-input.tsx +++ b/web/app/components/workflow/nodes/if-else/components/condition-list/condition-input.tsx @@ -37,6 +37,9 @@ const ConditionInput = ({ acc[node.id] = { title: node.data.title, type: node.data.type, + width: node.width, + height: node.height, + position: node.position, } if (node.data.type === BlockEnum.Start) { acc.sys = { diff --git a/web/app/components/workflow/nodes/iteration/panel.tsx b/web/app/components/workflow/nodes/iteration/panel.tsx index 67e67988b2..1f29a07946 100644 --- a/web/app/components/workflow/nodes/iteration/panel.tsx +++ b/web/app/components/workflow/nodes/iteration/panel.tsx @@ -73,6 +73,7 @@ const Panel: FC<NodePanelProps<IterationNodeType>> = ({ <div className='space-y-4 px-4 pb-4'> <Field title={t(`${i18nPrefix}.input`)} + required operations={( <div className='system-2xs-medium-uppercase flex h-[18px] items-center rounded-[5px] border border-divider-deep px-1 capitalize text-text-tertiary'>Array</div> )} @@ -91,6 +92,7 @@ const Panel: FC<NodePanelProps<IterationNodeType>> = ({ <div className='mt-2 space-y-4 px-4 pb-4'> <Field title={t(`${i18nPrefix}.output`)} + required operations={( <div className='system-2xs-medium-uppercase flex h-[18px] items-center rounded-[5px] border border-divider-deep px-1 capitalize text-text-tertiary'>Array</div> )} diff --git a/web/app/components/workflow/nodes/knowledge-retrieval/components/dataset-item.tsx b/web/app/components/workflow/nodes/knowledge-retrieval/components/dataset-item.tsx index f8d2dcfc75..bee387d549 100644 --- a/web/app/components/workflow/nodes/knowledge-retrieval/components/dataset-item.tsx +++ b/web/app/components/workflow/nodes/knowledge-retrieval/components/dataset-item.tsx @@ -55,8 +55,8 @@ const DatasetItem: FC<Props> = ({ }, [onRemove]) return ( - <div className={`group/dataset-item flex h-10 cursor-pointer items-center justify-between rounded-xl - border-[0.5px] border-components-panel-border-subtle px-2 + <div className={`group/dataset-item flex h-10 cursor-pointer items-center justify-between rounded-lg + border-[0.5px] border-components-panel-border-subtle px-2 ${isDeleteHovered ? 'border-state-destructive-border bg-state-destructive-hover' : 'bg-components-panel-on-panel-item-bg hover:bg-components-panel-on-panel-item-bg-hover' diff --git a/web/app/components/workflow/nodes/knowledge-retrieval/components/metadata/condition-list/condition-common-variable-selector.tsx.tsx b/web/app/components/workflow/nodes/knowledge-retrieval/components/metadata/condition-list/condition-common-variable-selector.tsx similarity index 100% rename from web/app/components/workflow/nodes/knowledge-retrieval/components/metadata/condition-list/condition-common-variable-selector.tsx.tsx rename to web/app/components/workflow/nodes/knowledge-retrieval/components/metadata/condition-list/condition-common-variable-selector.tsx diff --git a/web/app/components/workflow/nodes/knowledge-retrieval/components/metadata/condition-list/condition-item.tsx b/web/app/components/workflow/nodes/knowledge-retrieval/components/metadata/condition-list/condition-item.tsx index 398a2294e2..a93155113e 100644 --- a/web/app/components/workflow/nodes/knowledge-retrieval/components/metadata/condition-list/condition-item.tsx +++ b/web/app/components/workflow/nodes/knowledge-retrieval/components/metadata/condition-list/condition-item.tsx @@ -124,11 +124,11 @@ const ConditionItem = ({ )}> <div className='flex items-center p-1'> <div className='w-0 grow'> - <div className='inline-flex h-6 items-center rounded-md border-[0.5px] border-components-panel-border-subtle bg-components-badge-white-to-dark pl-1 pr-1.5 shadow-xs'> + <div className='flex h-6 min-w-0 items-center rounded-md border-[0.5px] border-components-panel-border-subtle bg-components-badge-white-to-dark pl-1 pr-1.5 shadow-xs'> <div className='mr-0.5 p-[1px]'> <MetadataIcon type={currentMetadata?.type} className='h-3 w-3' /> </div> - <div className='system-xs-medium mr-0.5 text-text-secondary'>{currentMetadata?.name}</div> + <div className='system-xs-medium mr-0.5 min-w-0 flex-1 truncate text-text-secondary'>{currentMetadata?.name}</div> <div className='system-xs-regular text-text-tertiary'>{currentMetadata?.type}</div> </div> </div> diff --git a/web/app/components/workflow/nodes/knowledge-retrieval/components/metadata/condition-list/condition-number.tsx b/web/app/components/workflow/nodes/knowledge-retrieval/components/metadata/condition-list/condition-number.tsx index 32fd64dec4..7016e8bd2a 100644 --- a/web/app/components/workflow/nodes/knowledge-retrieval/components/metadata/condition-list/condition-number.tsx +++ b/web/app/components/workflow/nodes/knowledge-retrieval/components/metadata/condition-list/condition-number.tsx @@ -3,7 +3,7 @@ import { useTranslation } from 'react-i18next' import ConditionValueMethod from './condition-value-method' import type { ConditionValueMethodProps } from './condition-value-method' import ConditionVariableSelector from './condition-variable-selector' -import ConditionCommonVariableSelector from './condition-common-variable-selector.tsx' +import ConditionCommonVariableSelector from './condition-common-variable-selector' import type { Node, NodeOutPutVar, diff --git a/web/app/components/workflow/nodes/knowledge-retrieval/components/metadata/condition-list/condition-string.tsx b/web/app/components/workflow/nodes/knowledge-retrieval/components/metadata/condition-list/condition-string.tsx index f376ee55ff..cf85f1259b 100644 --- a/web/app/components/workflow/nodes/knowledge-retrieval/components/metadata/condition-list/condition-string.tsx +++ b/web/app/components/workflow/nodes/knowledge-retrieval/components/metadata/condition-list/condition-string.tsx @@ -3,7 +3,7 @@ import { useTranslation } from 'react-i18next' import ConditionValueMethod from './condition-value-method' import type { ConditionValueMethodProps } from './condition-value-method' import ConditionVariableSelector from './condition-variable-selector' -import ConditionCommonVariableSelector from './condition-common-variable-selector.tsx' +import ConditionCommonVariableSelector from './condition-common-variable-selector' import type { Node, NodeOutPutVar, diff --git a/web/app/components/workflow/nodes/knowledge-retrieval/panel.tsx b/web/app/components/workflow/nodes/knowledge-retrieval/panel.tsx index 20fd24e50c..3b5eefd853 100644 --- a/web/app/components/workflow/nodes/knowledge-retrieval/panel.tsx +++ b/web/app/components/workflow/nodes/knowledge-retrieval/panel.tsx @@ -81,6 +81,7 @@ const Panel: FC<NodePanelProps<KnowledgeRetrievalNodeType>> = ({ {/* {JSON.stringify(inputs, null, 2)} */} <Field title={t(`${i18nPrefix}.queryVariable`)} + required > <VarReferencePicker nodeId={id} @@ -94,6 +95,7 @@ const Panel: FC<NodePanelProps<KnowledgeRetrievalNodeType>> = ({ <Field title={t(`${i18nPrefix}.knowledge`)} + required operations={ <div className='flex items-center space-x-1'> <RetrievalConfig diff --git a/web/app/components/workflow/nodes/list-operator/panel.tsx b/web/app/components/workflow/nodes/list-operator/panel.tsx index 42d5ff3bf1..d93a79397d 100644 --- a/web/app/components/workflow/nodes/list-operator/panel.tsx +++ b/web/app/components/workflow/nodes/list-operator/panel.tsx @@ -46,6 +46,7 @@ const Panel: FC<NodePanelProps<ListFilterNodeType>> = ({ <div className='space-y-4 px-4'> <Field title={t(`${i18nPrefix}.inputVar`)} + required > <VarReferencePicker readonly={readOnly} @@ -96,16 +97,14 @@ const Panel: FC<NodePanelProps<ListFilterNodeType>> = ({ {inputs.extract_by?.enabled ? ( <div className='flex items-center justify-between'> - {hasSubVariable && ( - <div className='mr-2 grow'> - <ExtractInput - value={inputs.extract_by.serial as string} - onChange={handleExtractsChange} - readOnly={readOnly} - nodeId={id} - /> - </div> - )} + <div className='mr-2 grow'> + <ExtractInput + value={inputs.extract_by.serial as string} + onChange={handleExtractsChange} + readOnly={readOnly} + nodeId={id} + /> + </div> </div> ) : null} diff --git a/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/code-editor.tsx b/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/code-editor.tsx index a3c2552b45..2ae7fec78d 100644 --- a/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/code-editor.tsx +++ b/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/code-editor.tsx @@ -28,6 +28,7 @@ const CodeEditor: FC<CodeEditorProps> = ({ const { theme } = useTheme() const monacoRef = useRef<any>(null) const editorRef = useRef<any>(null) + const containerRef = useRef<HTMLDivElement>(null) useEffect(() => { if (monacoRef.current) { @@ -74,6 +75,19 @@ const CodeEditor: FC<CodeEditorProps> = ({ onUpdate?.(value) }, [onUpdate]) + useEffect(() => { + const resizeObserver = new ResizeObserver(() => { + editorRef.current?.layout() + }) + + if (containerRef.current) + resizeObserver.observe(containerRef.current) + + return () => { + resizeObserver.disconnect() + } + }, []) + return ( <div className={classNames('flex flex-col h-full bg-components-input-bg-normal overflow-hidden', className)}> <div className='flex items-center justify-between pl-2 pr-1 pt-1'> @@ -102,9 +116,11 @@ const CodeEditor: FC<CodeEditorProps> = ({ </Tooltip> </div> </div> - <div className={classNames('relative', editorWrapperClassName)}> + <div + ref={containerRef} + className={classNames('relative overflow-hidden', editorWrapperClassName)} + > <Editor - height='100%' defaultLanguage='json' value={value} onChange={handleEditorChange} @@ -117,7 +133,6 @@ const CodeEditor: FC<CodeEditorProps> = ({ scrollBeyondLastLine: false, wordWrap: 'on', wrappingIndent: 'same', - // Add these options overviewRulerBorder: false, hideCursorInOverviewRuler: true, renderLineHighlightOnlyWhenFocus: false, diff --git a/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/json-schema-config.tsx b/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/json-schema-config.tsx index d125e31dae..fecd1093d9 100644 --- a/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/json-schema-config.tsx +++ b/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/json-schema-config.tsx @@ -21,8 +21,8 @@ import { MittProvider, VisualEditorContextProvider, useMittContext } from './vis import ErrorMessage from './error-message' import { useVisualEditorStore } from './visual-editor/store' import Toast from '@/app/components/base/toast' -import { useGetLanguage } from '@/context/i18n' import { JSON_SCHEMA_MAX_DEPTH } from '@/config' +import { useDocLink } from '@/context/i18n' type JsonSchemaConfigProps = { defaultSchema?: SchemaRoot @@ -47,21 +47,13 @@ const DEFAULT_SCHEMA: SchemaRoot = { additionalProperties: false, } -const HELP_DOC_URL = { - zh_Hans: 'https://docs.dify.ai/zh-hans/guides/workflow/structured-outputs', - en_US: 'https://docs.dify.ai/guides/workflow/structured-outputs', - ja_JP: 'https://docs.dify.ai/ja-jp/guides/workflow/structured-outputs', -} - -type LocaleKey = keyof typeof HELP_DOC_URL - const JsonSchemaConfig: FC<JsonSchemaConfigProps> = ({ defaultSchema, onSave, onClose, }) => { const { t } = useTranslation() - const locale = useGetLanguage() as LocaleKey + const docLink = useDocLink() const [currentTab, setCurrentTab] = useState(SchemaView.VisualEditor) const [jsonSchema, setJsonSchema] = useState(defaultSchema || DEFAULT_SCHEMA) const [json, setJson] = useState(JSON.stringify(jsonSchema, null, 2)) @@ -260,7 +252,7 @@ const JsonSchemaConfig: FC<JsonSchemaConfigProps> = ({ <div className='flex items-center gap-x-2 p-6 pt-5'> <a className='flex grow items-center gap-x-1 text-text-accent' - href={HELP_DOC_URL[locale]} + href={docLink('/guides/workflow/structured-outputs')} target='_blank' rel='noopener noreferrer' > diff --git a/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/schema-editor.tsx b/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/schema-editor.tsx index e78b9224b2..05a429ff72 100644 --- a/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/schema-editor.tsx +++ b/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/schema-editor.tsx @@ -12,7 +12,7 @@ const SchemaEditor: FC<SchemaEditorProps> = ({ }) => { return ( <CodeEditor - className='rounded-xl' + className='grow rounded-xl' editorWrapperClassName='grow' value={schema} onUpdate={onUpdate} diff --git a/web/app/components/workflow/nodes/llm/panel.tsx b/web/app/components/workflow/nodes/llm/panel.tsx index 2335fa0c80..29fb4fb2c3 100644 --- a/web/app/components/workflow/nodes/llm/panel.tsx +++ b/web/app/components/workflow/nodes/llm/panel.tsx @@ -147,6 +147,7 @@ const Panel: FC<NodePanelProps<LLMNodeType>> = ({ <div className='space-y-4 px-4 pb-4'> <Field title={t(`${i18nPrefix}.model`)} + required > <ModelParameterModal popupClassName='!w-[387px]' @@ -295,7 +296,7 @@ const Panel: FC<NodePanelProps<LLMNodeType>> = ({ onCollapse={setStructuredOutputCollapsed} operations={ <div className='mr-4 flex shrink-0 items-center'> - {!isModelSupportStructuredOutput && ( + {(!isModelSupportStructuredOutput && !!inputs.structured_output_enabled) && ( <Tooltip noDecoration popupContent={ <div className='w-[232px] rounded-xl border-[0.5px] border-components-panel-border bg-components-tooltip-bg px-4 py-3.5 shadow-lg backdrop-blur-[5px]'> <div className='title-xs-semi-bold text-text-primary'>{t('app.structOutput.modelNotSupported')}</div> diff --git a/web/app/components/workflow/nodes/llm/use-config.ts b/web/app/components/workflow/nodes/llm/use-config.ts index 13db9e4031..cd2c552257 100644 --- a/web/app/components/workflow/nodes/llm/use-config.ts +++ b/web/app/components/workflow/nodes/llm/use-config.ts @@ -247,11 +247,11 @@ const useConfig = (id: string, payload: LLMNodeType) => { }, [inputs, setInputs]) const handlePromptChange = useCallback((newPrompt: PromptItem[] | PromptItem) => { - const newInputs = produce(inputRef.current, (draft) => { + const newInputs = produce(inputs, (draft) => { draft.prompt_template = newPrompt }) setInputs(newInputs) - }, [setInputs]) + }, [inputs, setInputs]) const handleMemoryChange = useCallback((newMemory?: Memory) => { const newInputs = produce(inputs, (draft) => { diff --git a/web/app/components/workflow/nodes/parameter-extractor/panel.tsx b/web/app/components/workflow/nodes/parameter-extractor/panel.tsx index ba6aed13ea..d03f1d9ff3 100644 --- a/web/app/components/workflow/nodes/parameter-extractor/panel.tsx +++ b/web/app/components/workflow/nodes/parameter-extractor/panel.tsx @@ -115,6 +115,7 @@ const Panel: FC<NodePanelProps<ParameterExtractorNodeType>> = ({ <div className='space-y-4 px-4'> <Field title={t(`${i18nCommonPrefix}.model`)} + required > <ModelParameterModal popupClassName='!w-[387px]' @@ -133,6 +134,7 @@ const Panel: FC<NodePanelProps<ParameterExtractorNodeType>> = ({ </Field> <Field title={t(`${i18nPrefix}.inputVar`)} + required > <> <VarReferencePicker @@ -157,6 +159,7 @@ const Panel: FC<NodePanelProps<ParameterExtractorNodeType>> = ({ /> <Field title={t(`${i18nPrefix}.extractParameters`)} + required operations={ !readOnly ? ( diff --git a/web/app/components/workflow/nodes/question-classifier/components/class-list.tsx b/web/app/components/workflow/nodes/question-classifier/components/class-list.tsx index 4dc0ff759e..f152917ed4 100644 --- a/web/app/components/workflow/nodes/question-classifier/components/class-list.tsx +++ b/web/app/components/workflow/nodes/question-classifier/components/class-list.tsx @@ -63,7 +63,7 @@ const ClassList: FC<Props> = ({ return ( <Item nodeId={nodeId} - key={index} + key={list[index].id} payload={item} onChange={handleClassChange(index)} onRemove={handleRemoveClass(index)} diff --git a/web/app/components/workflow/nodes/question-classifier/panel.tsx b/web/app/components/workflow/nodes/question-classifier/panel.tsx index 96e8fabd47..d2e0fb060a 100644 --- a/web/app/components/workflow/nodes/question-classifier/panel.tsx +++ b/web/app/components/workflow/nodes/question-classifier/panel.tsx @@ -103,6 +103,7 @@ const Panel: FC<NodePanelProps<QuestionClassifierNodeType>> = ({ <div className='space-y-4 px-4'> <Field title={t(`${i18nPrefix}.model`)} + required > <ModelParameterModal popupClassName='!w-[387px]' @@ -121,6 +122,7 @@ const Panel: FC<NodePanelProps<QuestionClassifierNodeType>> = ({ </Field> <Field title={t(`${i18nPrefix}.inputVars`)} + required > <VarReferencePicker readonly={readOnly} @@ -143,6 +145,7 @@ const Panel: FC<NodePanelProps<QuestionClassifierNodeType>> = ({ /> <Field title={t(`${i18nPrefix}.class`)} + required > <ClassList nodeId={id} diff --git a/web/app/components/workflow/nodes/tool/components/input-var-list.tsx b/web/app/components/workflow/nodes/tool/components/input-var-list.tsx index dc25184f5a..1a609c58f5 100644 --- a/web/app/components/workflow/nodes/tool/components/input-var-list.tsx +++ b/web/app/components/workflow/nodes/tool/components/input-var-list.tsx @@ -179,7 +179,7 @@ const InputVarList: FC<Props> = ({ </div> {isString && ( <Input - className={cn(inputsIsFocus[variable] ? 'border-gray-300 bg-gray-50 shadow-xs' : 'border-gray-100 bg-gray-100', 'rounded-lg border px-3 py-[6px]')} + className={cn(inputsIsFocus[variable] ? 'border-components-input-border-active bg-components-input-bg-active shadow-xs' : 'border-components-input-border-hover bg-components-input-bg-normal', 'rounded-lg border px-3 py-[6px]')} value={varInput?.value as string || ''} onChange={handleMixedTypeChange(variable)} readOnly={readOnly} diff --git a/web/app/components/workflow/nodes/tool/panel.tsx b/web/app/components/workflow/nodes/tool/panel.tsx index 85966443d5..393a11c1e8 100644 --- a/web/app/components/workflow/nodes/tool/panel.tsx +++ b/web/app/components/workflow/nodes/tool/panel.tsx @@ -79,7 +79,7 @@ const Panel: FC<NodePanelProps<ToolNodeType>> = ({ className='w-full' onClick={showSetAuthModal} > - {t(`${i18nPrefix}.toAuthorize`)} + {t(`${i18nPrefix}.authorize`)} </Button> </div> </> diff --git a/web/app/components/workflow/note-node/note-editor/theme/theme.css b/web/app/components/workflow/note-node/note-editor/theme/theme.css index 25c0cdeaad..77b745ca4a 100644 --- a/web/app/components/workflow/note-node/note-editor/theme/theme.css +++ b/web/app/components/workflow/note-node/note-editor/theme/theme.css @@ -29,4 +29,4 @@ .note-editor-theme_text-italic { font-style: italic; -} \ No newline at end of file +} diff --git a/web/app/components/workflow/operator/export-image.tsx b/web/app/components/workflow/operator/export-image.tsx index d128a6b6ff..f59f0cd92b 100644 --- a/web/app/components/workflow/operator/export-image.tsx +++ b/web/app/components/workflow/operator/export-image.tsx @@ -1,131 +1,131 @@ -import type { FC } from 'react' -import { - memo, - useCallback, - useState, -} from 'react' -import { useTranslation } from 'react-i18next' -import { toJpeg, toPng, toSvg } from 'html-to-image' -import { useNodesReadOnly } from '../hooks' -import TipPopup from './tip-popup' -import { RiExportLine } from '@remixicon/react' -import cn from '@/utils/classnames' -import { useStore as useAppStore } from '@/app/components/app/store' -import { - PortalToFollowElem, - PortalToFollowElemContent, - PortalToFollowElemTrigger, -} from '@/app/components/base/portal-to-follow-elem' - -const ExportImage: FC = () => { - const { t } = useTranslation() - const { getNodesReadOnly } = useNodesReadOnly() - - const appDetail = useAppStore(s => s.appDetail) - const [open, setOpen] = useState(false) - - const handleExportImage = useCallback(async (type: 'png' | 'jpeg' | 'svg') => { - if (!appDetail) - return - - if (getNodesReadOnly()) - return - - setOpen(false) - const flowElement = document.querySelector('.react-flow__viewport') as HTMLElement - if (!flowElement) return - - try { - const filter = (node: HTMLElement) => { - if (node instanceof HTMLImageElement) - return node.complete && node.naturalHeight !== 0 - - return true - } - - let dataUrl - switch (type) { - case 'png': - dataUrl = await toPng(flowElement, { filter }) - break - case 'jpeg': - dataUrl = await toJpeg(flowElement, { filter }) - break - case 'svg': - dataUrl = await toSvg(flowElement, { filter }) - break - default: - dataUrl = await toPng(flowElement, { filter }) - } - - const link = document.createElement('a') - link.href = dataUrl - link.download = `${appDetail.name}.${type}` - document.body.appendChild(link) - link.click() - document.body.removeChild(link) - } - catch (error) { - console.error('Export image failed:', error) - } - }, [getNodesReadOnly, appDetail]) - - const handleTrigger = useCallback(() => { - if (getNodesReadOnly()) - return - - setOpen(v => !v) - }, [getNodesReadOnly]) - - return ( - <PortalToFollowElem - open={open} - onOpenChange={setOpen} - placement="top-start" - offset={{ - mainAxis: 4, - crossAxis: -8, - }} - > - <PortalToFollowElemTrigger> - <TipPopup title={t('workflow.common.exportImage')}> - <div - className={cn( - 'flex h-8 w-8 cursor-pointer items-center justify-center rounded-lg hover:bg-state-base-hover hover:text-text-secondary', - `${getNodesReadOnly() && 'cursor-not-allowed text-text-disabled hover:bg-transparent hover:text-text-disabled'}`, - )} - onClick={handleTrigger} - > - <RiExportLine className='h-4 w-4' /> - </div> - </TipPopup> - </PortalToFollowElemTrigger> - <PortalToFollowElemContent className='z-10'> - <div className='min-w-[120px] rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg-blur text-text-secondary shadow-lg'> - <div className='p-1'> - <div - className='system-md-regular flex h-8 cursor-pointer items-center rounded-lg px-2 hover:bg-state-base-hover' - onClick={() => handleExportImage('png')} - > - {t('workflow.common.exportPNG')} - </div> - <div - className='system-md-regular flex h-8 cursor-pointer items-center rounded-lg px-2 hover:bg-state-base-hover' - onClick={() => handleExportImage('jpeg')} - > - {t('workflow.common.exportJPEG')} - </div> - <div - className='system-md-regular flex h-8 cursor-pointer items-center rounded-lg px-2 hover:bg-state-base-hover' - onClick={() => handleExportImage('svg')} - > - {t('workflow.common.exportSVG')} - </div> - </div> - </div> - </PortalToFollowElemContent> - </PortalToFollowElem> - ) -} - -export default memo(ExportImage) +import type { FC } from 'react' +import { + memo, + useCallback, + useState, +} from 'react' +import { useTranslation } from 'react-i18next' +import { toJpeg, toPng, toSvg } from 'html-to-image' +import { useNodesReadOnly } from '../hooks' +import TipPopup from './tip-popup' +import { RiExportLine } from '@remixicon/react' +import cn from '@/utils/classnames' +import { useStore as useAppStore } from '@/app/components/app/store' +import { + PortalToFollowElem, + PortalToFollowElemContent, + PortalToFollowElemTrigger, +} from '@/app/components/base/portal-to-follow-elem' + +const ExportImage: FC = () => { + const { t } = useTranslation() + const { getNodesReadOnly } = useNodesReadOnly() + + const appDetail = useAppStore(s => s.appDetail) + const [open, setOpen] = useState(false) + + const handleExportImage = useCallback(async (type: 'png' | 'jpeg' | 'svg') => { + if (!appDetail) + return + + if (getNodesReadOnly()) + return + + setOpen(false) + const flowElement = document.querySelector('.react-flow__viewport') as HTMLElement + if (!flowElement) return + + try { + const filter = (node: HTMLElement) => { + if (node instanceof HTMLImageElement) + return node.complete && node.naturalHeight !== 0 + + return true + } + + let dataUrl + switch (type) { + case 'png': + dataUrl = await toPng(flowElement, { filter }) + break + case 'jpeg': + dataUrl = await toJpeg(flowElement, { filter }) + break + case 'svg': + dataUrl = await toSvg(flowElement, { filter }) + break + default: + dataUrl = await toPng(flowElement, { filter }) + } + + const link = document.createElement('a') + link.href = dataUrl + link.download = `${appDetail.name}.${type}` + document.body.appendChild(link) + link.click() + document.body.removeChild(link) + } + catch (error) { + console.error('Export image failed:', error) + } + }, [getNodesReadOnly, appDetail]) + + const handleTrigger = useCallback(() => { + if (getNodesReadOnly()) + return + + setOpen(v => !v) + }, [getNodesReadOnly]) + + return ( + <PortalToFollowElem + open={open} + onOpenChange={setOpen} + placement="top-start" + offset={{ + mainAxis: 4, + crossAxis: -8, + }} + > + <PortalToFollowElemTrigger> + <TipPopup title={t('workflow.common.exportImage')}> + <div + className={cn( + 'flex h-8 w-8 cursor-pointer items-center justify-center rounded-lg hover:bg-state-base-hover hover:text-text-secondary', + `${getNodesReadOnly() && 'cursor-not-allowed text-text-disabled hover:bg-transparent hover:text-text-disabled'}`, + )} + onClick={handleTrigger} + > + <RiExportLine className='h-4 w-4' /> + </div> + </TipPopup> + </PortalToFollowElemTrigger> + <PortalToFollowElemContent className='z-10'> + <div className='min-w-[120px] rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg-blur text-text-secondary shadow-lg'> + <div className='p-1'> + <div + className='system-md-regular flex h-8 cursor-pointer items-center rounded-lg px-2 hover:bg-state-base-hover' + onClick={() => handleExportImage('png')} + > + {t('workflow.common.exportPNG')} + </div> + <div + className='system-md-regular flex h-8 cursor-pointer items-center rounded-lg px-2 hover:bg-state-base-hover' + onClick={() => handleExportImage('jpeg')} + > + {t('workflow.common.exportJPEG')} + </div> + <div + className='system-md-regular flex h-8 cursor-pointer items-center rounded-lg px-2 hover:bg-state-base-hover' + onClick={() => handleExportImage('svg')} + > + {t('workflow.common.exportSVG')} + </div> + </div> + </div> + </PortalToFollowElemContent> + </PortalToFollowElem> + ) +} + +export default memo(ExportImage) diff --git a/web/app/components/workflow/operator/zoom-in-out.tsx b/web/app/components/workflow/operator/zoom-in-out.tsx index 35aa183351..19d8eef463 100644 --- a/web/app/components/workflow/operator/zoom-in-out.tsx +++ b/web/app/components/workflow/operator/zoom-in-out.tsx @@ -132,7 +132,7 @@ const ZoomInOut: FC = () => { <PortalToFollowElemTrigger asChild> <div className={` h-9 cursor-pointer rounded-lg border-[0.5px] border-components-actionbar-border bg-components-actionbar-bg - p-0.5 text-[13px] shadow-lg backdrop-blur-[5px] + p-0.5 text-[13px] shadow-lg backdrop-blur-[5px] hover:bg-state-base-hover ${workflowReadOnly && '!cursor-not-allowed opacity-50'} `}> diff --git a/web/app/components/workflow/panel/chat-variable-panel/index.tsx b/web/app/components/workflow/panel/chat-variable-panel/index.tsx index ad00bddd0c..bbf39489dd 100644 --- a/web/app/components/workflow/panel/chat-variable-panel/index.tsx +++ b/web/app/components/workflow/panel/chat-variable-panel/index.tsx @@ -3,7 +3,6 @@ import { useCallback, useState, } from 'react' -import { useContext } from 'use-context-selector' import { useStoreApi, } from 'reactflow' @@ -22,13 +21,12 @@ import type { import { findUsedVarNodes, updateNodeVars } from '@/app/components/workflow/nodes/_base/components/variable/utils' import { useNodesSyncDraft } from '@/app/components/workflow/hooks/use-nodes-sync-draft' import { BlockEnum } from '@/app/components/workflow/types' -import I18n from '@/context/i18n' -import { LanguagesSupported } from '@/i18n/language' +import { useDocLink } from '@/context/i18n' import cn from '@/utils/classnames' const ChatVariablePanel = () => { const { t } = useTranslation() - const { locale } = useContext(I18n) + const docLink = useDocLink() const store = useStoreApi() const setShowChatVariablePanel = useStore(s => s.setShowChatVariablePanel) const varList = useStore(s => s.conversationVariables) as ConversationVariable[] @@ -139,7 +137,13 @@ const ChatVariablePanel = () => { <div className='system-2xs-medium-uppercase inline-block rounded-[5px] border border-divider-deep px-[5px] py-[3px] text-text-tertiary'>TIPS</div> <div className='system-sm-regular mb-4 mt-1 text-text-secondary'> {t('workflow.chatVariable.panelDescription')} - <a target='_blank' rel='noopener noreferrer' className='text-text-accent' href={locale !== LanguagesSupported[1] ? 'https://docs.dify.ai/guides/workflow/variables#conversation-variables' : `https://docs.dify.ai/${locale.toLowerCase()}/guides/workflow/variables#hui-hua-bian-liang`}>{t('workflow.chatVariable.docLink')}</a> + <a target='_blank' rel='noopener noreferrer' className='text-text-accent' + href={docLink('/guides/workflow/variables#conversation-variables', { + 'zh-Hans': '/guides/workflow/variables#会话变量', + 'ja-JP': '/guides/workflow/variables#会話変数', + })}> + {t('workflow.chatVariable.docLink')} + </a> </div> <div className='flex items-center gap-2'> <div className='radius-lg flex flex-col border border-workflow-block-border bg-workflow-block-bg p-3 pb-4 shadow-md'> @@ -166,7 +170,7 @@ const ChatVariablePanel = () => { </div> </div> </div> - <div className='absolute right-[38px] top-[-4px] z-10 h-3 w-3 rotate-45 bg-background-section-burn'/> + <div className='absolute right-[38px] top-[-4px] z-10 h-3 w-3 rotate-45 bg-background-section-burn' /> </div> </div> )} diff --git a/web/app/components/workflow/panel/debug-and-preview/index.tsx b/web/app/components/workflow/panel/debug-and-preview/index.tsx index b63b7af16c..563be25a99 100644 --- a/web/app/components/workflow/panel/debug-and-preview/index.tsx +++ b/web/app/components/workflow/panel/debug-and-preview/index.tsx @@ -39,6 +39,7 @@ const DebugAndPreview = () => { const nodes = useNodes<StartNodeType>() const startNode = nodes.find(node => node.data.type === BlockEnum.Start) const variables = startNode?.data.variables || [] + const visibleVariables = variables.filter(v => v.hide !== true) const [showConversationVariableModal, setShowConversationVariableModal] = useState(false) @@ -107,7 +108,7 @@ const DebugAndPreview = () => { </ActionButton> </Tooltip> )} - {variables.length > 0 && ( + {visibleVariables.length > 0 && ( <div className='relative'> <Tooltip popupContent={t('workflow.panel.userInputField')} diff --git a/web/app/components/workflow/panel/debug-and-preview/user-input.tsx b/web/app/components/workflow/panel/debug-and-preview/user-input.tsx index 0b1b4512fc..0670d9a52b 100644 --- a/web/app/components/workflow/panel/debug-and-preview/user-input.tsx +++ b/web/app/components/workflow/panel/debug-and-preview/user-input.tsx @@ -17,6 +17,7 @@ const UserInput = () => { const nodes = useNodes<StartNodeType>() const startNode = nodes.find(node => node.data.type === BlockEnum.Start) const variables = startNode?.data.variables || [] + const visibleVariables = variables.filter(v => v.hide !== true) const handleValueChange = (variable: string, v: string) => { const { @@ -29,13 +30,13 @@ const UserInput = () => { }) } - if (!variables.length) + if (!visibleVariables.length) return null return ( - <div className={cn('sticky top-0 z-[1] rounded-xl border-[0.5px] border-components-panel-border-subtle bg-components-panel-on-panel-item-bg shadow-xs')}> + <div className={cn('relative z-[1] rounded-xl border-[0.5px] border-components-panel-border-subtle bg-components-panel-on-panel-item-bg shadow-xs')}> <div className='px-4 pb-4 pt-3'> - {variables.map((variable, index) => ( + {visibleVariables.map((variable, index) => ( <div key={variable.variable} className='mb-4 last-of-type:mb-0' diff --git a/web/app/components/workflow/panel/version-history-panel/index.tsx b/web/app/components/workflow/panel/version-history-panel/index.tsx index 3ca800bbf6..a42eb0f215 100644 --- a/web/app/components/workflow/panel/version-history-panel/index.tsx +++ b/web/app/components/workflow/panel/version-history-panel/index.tsx @@ -191,7 +191,7 @@ const VersionHistoryPanel = () => { }, [t, updateWorkflow, resetWorkflowVersionHistory]) return ( - <div className='flex w-[268px] flex-col rounded-l-2xl border-y-[0.5px] border-l-[0.5px] border-components-panel-border bg-components-panel-bg shadow-xl shadow-shadow-shadow-5'> + <div className='flex h-full w-[268px] flex-col rounded-l-2xl border-y-[0.5px] border-l-[0.5px] border-components-panel-border bg-components-panel-bg shadow-xl shadow-shadow-shadow-5'> <div className='flex items-center gap-x-2 px-4 pt-3'> <div className='system-xl-semibold flex-1 py-1 text-text-primary'>{t('workflow.versionHistory.title')}</div> <Filter @@ -208,50 +208,51 @@ const VersionHistoryPanel = () => { <RiCloseLine className='h-4 w-4 text-text-tertiary' /> </div> </div> - <div className="relative flex-1 overflow-y-auto px-3 py-2"> - {(isFetching && !versionHistory?.pages?.length) - ? ( - <Loading /> - ) - : ( - <> - {versionHistory?.pages?.map((page, pageNumber) => ( - page.items?.map((item, idx) => { - const isLast = pageNumber === versionHistory.pages.length - 1 && idx === page.items.length - 1 - return <VersionHistoryItem - key={item.id} - item={item} - currentVersion={currentVersion} - latestVersionId={appDetail!.workflow!.id} - onClick={handleVersionClick} - handleClickMenuItem={handleClickMenuItem.bind(null, item)} - isLast={isLast} - /> - }) - ))} - {hasNextPage && ( - <div className='absolute bottom-2 left-2 flex p-2'> - <div - className='flex cursor-pointer items-center gap-x-1' - onClick={handleNextPage} - > - <div className='item-center flex justify-center p-0.5'> - { - isFetching - ? <RiLoader2Line className='h-3.5 w-3.5 animate-spin text-text-accent' /> - : <RiArrowDownDoubleLine className='h-3.5 w-3.5 text-text-accent' />} - </div> - <div className='system-xs-medium-uppercase py-[1px] text-text-accent'> - {t('workflow.common.loadMore')} - </div> - </div> - </div> - )} - {!isFetching && (!versionHistory?.pages?.length || !versionHistory.pages[0].items.length) && ( - <Empty onResetFilter={handleResetFilter} /> - )} - </> - )} + <div className="flex h-0 flex-1 flex-col"> + <div className="flex-1 overflow-y-auto px-3 py-2"> + {(isFetching && !versionHistory?.pages?.length) + ? ( + <Loading /> + ) + : ( + <> + {versionHistory?.pages?.map((page, pageNumber) => ( + page.items?.map((item, idx) => { + const isLast = pageNumber === versionHistory.pages.length - 1 && idx === page.items.length - 1 + return <VersionHistoryItem + key={item.id} + item={item} + currentVersion={currentVersion} + latestVersionId={appDetail!.workflow!.id} + onClick={handleVersionClick} + handleClickMenuItem={handleClickMenuItem.bind(null, item)} + isLast={isLast} + /> + }) + ))} + {!isFetching && (!versionHistory?.pages?.length || !versionHistory.pages[0].items.length) && ( + <Empty onResetFilter={handleResetFilter} /> + )} + </> + )} + </div> + {hasNextPage && ( + <div className='p-2'> + <div + className='flex cursor-pointer items-center gap-x-1' + onClick={handleNextPage} + > + <div className='item-center flex justify-center p-0.5'> + {isFetching + ? <RiLoader2Line className='h-3.5 w-3.5 animate-spin text-text-accent' /> + : <RiArrowDownDoubleLine className='h-3.5 w-3.5 text-text-accent' />} + </div> + <div className='system-xs-medium-uppercase py-[1px] text-text-accent'> + {t('workflow.common.loadMore')} + </div> + </div> + </div> + )} </div> {restoreConfirmOpen && (<RestoreConfirmModal isOpen={restoreConfirmOpen} diff --git a/web/app/components/workflow/run/iteration-log/iteration-log-trigger.tsx b/web/app/components/workflow/run/iteration-log/iteration-log-trigger.tsx index ee051883f7..8c9b43916b 100644 --- a/web/app/components/workflow/run/iteration-log/iteration-log-trigger.tsx +++ b/web/app/components/workflow/run/iteration-log/iteration-log-trigger.tsx @@ -9,44 +9,89 @@ import { Iteration } from '@/app/components/base/icons/src/vender/workflow' type IterationLogTriggerProps = { nodeInfo: NodeTracing + allExecutions?: NodeTracing[] onShowIterationResultList: (iterationResultList: NodeTracing[][], iterationResultDurationMap: IterationDurationMap) => void } const IterationLogTrigger = ({ nodeInfo, + allExecutions, onShowIterationResultList, }: IterationLogTriggerProps) => { const { t } = useTranslation() + + const filterNodesForInstance = (key: string): NodeTracing[] => { + if (!allExecutions) return [] + + const parallelNodes = allExecutions.filter(exec => + exec.execution_metadata?.parallel_mode_run_id === key, + ) + if (parallelNodes.length > 0) + return parallelNodes + + const serialIndex = Number.parseInt(key, 10) + if (!isNaN(serialIndex)) { + const serialNodes = allExecutions.filter(exec => + exec.execution_metadata?.iteration_id === nodeInfo.node_id + && exec.execution_metadata?.iteration_index === serialIndex, + ) + if (serialNodes.length > 0) + return serialNodes + } + + return [] + } + + const handleOnShowIterationDetail = (e: React.MouseEvent<HTMLButtonElement>) => { + e.stopPropagation() + e.nativeEvent.stopImmediatePropagation() + + const iterationNodeMeta = nodeInfo.execution_metadata + const iterDurationMap = nodeInfo?.iterDurationMap || iterationNodeMeta?.iteration_duration_map || {} + + let structuredList: NodeTracing[][] = [] + if (iterationNodeMeta?.iteration_duration_map) { + const instanceKeys = Object.keys(iterationNodeMeta.iteration_duration_map) + structuredList = instanceKeys + .map(key => filterNodesForInstance(key)) + .filter(branchNodes => branchNodes.length > 0) + } + else if (nodeInfo.details?.length) { + structuredList = nodeInfo.details + } + + onShowIterationResultList(structuredList, iterDurationMap) + } + + let displayIterationCount = 0 + const iterMap = nodeInfo.execution_metadata?.iteration_duration_map + if (iterMap) + displayIterationCount = Object.keys(iterMap).length + else if (nodeInfo.details?.length) + displayIterationCount = nodeInfo.details.length + else if (nodeInfo.metadata?.iterator_length) + displayIterationCount = nodeInfo.metadata.iterator_length + const getErrorCount = (details: NodeTracing[][] | undefined) => { if (!details || details.length === 0) return 0 - return details.reduce((acc, iteration) => { if (iteration.some(item => item.status === 'failed')) acc++ return acc }, 0) } - const getCount = (iteration_curr_length: number | undefined, iteration_length: number) => { - if ((iteration_curr_length && iteration_curr_length < iteration_length) || !iteration_length) - return iteration_curr_length + const errorCount = getErrorCount(nodeInfo.details) - return iteration_length - } - const handleOnShowIterationDetail = (e: React.MouseEvent<HTMLButtonElement>) => { - e.stopPropagation() - e.nativeEvent.stopImmediatePropagation() - onShowIterationResultList(nodeInfo.details || [], nodeInfo?.iterDurationMap || nodeInfo.execution_metadata?.iteration_duration_map || {}) - } return ( <Button className='flex w-full cursor-pointer items-center gap-2 self-stretch rounded-lg border-none bg-components-button-tertiary-bg-hover px-3 py-2 hover:bg-components-button-tertiary-bg-hover' onClick={handleOnShowIterationDetail} > <Iteration className='h-4 w-4 shrink-0 text-components-button-tertiary-text' /> - <div className='system-sm-medium flex-1 text-left text-components-button-tertiary-text'>{t('workflow.nodes.iteration.iteration', { count: getCount(nodeInfo.details?.length, nodeInfo.metadata?.iterator_length) })}{getErrorCount(nodeInfo.details) > 0 && ( + <div className='system-sm-medium flex-1 text-left text-components-button-tertiary-text'>{t('workflow.nodes.iteration.iteration', { count: displayIterationCount })}{errorCount > 0 && ( <> {t('workflow.nodes.iteration.comma')} - {t('workflow.nodes.iteration.error', { count: getErrorCount(nodeInfo.details) })} + {t('workflow.nodes.iteration.error', { count: errorCount })} </> )}</div> <RiArrowRightSLine className='h-4 w-4 shrink-0 text-components-button-tertiary-text' /> diff --git a/web/app/components/workflow/run/iteration-log/iteration-result-panel.tsx b/web/app/components/workflow/run/iteration-log/iteration-result-panel.tsx index ac80d7e39e..3d9ad87890 100644 --- a/web/app/components/workflow/run/iteration-log/iteration-result-panel.tsx +++ b/web/app/components/workflow/run/iteration-log/iteration-result-panel.tsx @@ -109,8 +109,10 @@ const IterationResultPanel: FC<Props> = ({ className="h-px grow bg-divider-subtle" ></div>} <div className={cn( - 'overflow-hidden transition-all duration-200', - expandedIterations[index] ? 'max-h-[1000px] opacity-100' : 'max-h-0 opacity-0', + 'transition-all duration-200', + expandedIterations[index] + ? 'opacity-100' + : 'max-h-0 overflow-hidden opacity-0', )}> <TracingPanel list={iteration} diff --git a/web/app/components/workflow/run/loop-log/loop-log-trigger.tsx b/web/app/components/workflow/run/loop-log/loop-log-trigger.tsx index fca549b2a1..b086312baf 100644 --- a/web/app/components/workflow/run/loop-log/loop-log-trigger.tsx +++ b/web/app/components/workflow/run/loop-log/loop-log-trigger.tsx @@ -10,48 +10,94 @@ import { Loop } from '@/app/components/base/icons/src/vender/workflow' type LoopLogTriggerProps = { nodeInfo: NodeTracing + allExecutions?: NodeTracing[] onShowLoopResultList: (loopResultList: NodeTracing[][], loopResultDurationMap: LoopDurationMap, loopVariableMap: LoopVariableMap) => void } const LoopLogTrigger = ({ nodeInfo, + allExecutions, onShowLoopResultList, }: LoopLogTriggerProps) => { const { t } = useTranslation() + + const filterNodesForInstance = (key: string): NodeTracing[] => { + if (!allExecutions) return [] + + const parallelNodes = allExecutions.filter(exec => + exec.execution_metadata?.parallel_mode_run_id === key, + ) + if (parallelNodes.length > 0) + return parallelNodes + + const serialIndex = Number.parseInt(key, 10) + if (!isNaN(serialIndex)) { + const serialNodes = allExecutions.filter(exec => + exec.execution_metadata?.loop_id === nodeInfo.node_id + && exec.execution_metadata?.loop_index === serialIndex, + ) + if (serialNodes.length > 0) + return serialNodes + } + + return [] + } + + const handleOnShowLoopDetail = (e: React.MouseEvent<HTMLButtonElement>) => { + e.stopPropagation() + e.nativeEvent.stopImmediatePropagation() + + const loopNodeMeta = nodeInfo.execution_metadata + const loopDurMap = nodeInfo?.loopDurationMap || loopNodeMeta?.loop_duration_map || {} + const loopVarMap = loopNodeMeta?.loop_variable_map || {} + + let structuredList: NodeTracing[][] = [] + if (nodeInfo.details?.length) { + structuredList = nodeInfo.details + } + else if (loopNodeMeta?.loop_duration_map) { + const instanceKeys = Object.keys(loopNodeMeta.loop_duration_map) + structuredList = instanceKeys + .map(key => filterNodesForInstance(key)) + .filter(branchNodes => branchNodes.length > 0) + } + + onShowLoopResultList( + structuredList, + loopDurMap, + loopVarMap, + ) + } + + let displayLoopCount = 0 + const loopMap = nodeInfo.execution_metadata?.loop_duration_map + if (loopMap) + displayLoopCount = Object.keys(loopMap).length + else if (nodeInfo.details?.length) + displayLoopCount = nodeInfo.details.length + else if (nodeInfo.metadata?.loop_length) + displayLoopCount = nodeInfo.metadata.loop_length + const getErrorCount = (details: NodeTracing[][] | undefined) => { if (!details || details.length === 0) return 0 - return details.reduce((acc, loop) => { if (loop.some(item => item.status === 'failed')) acc++ return acc }, 0) } - const getCount = (loop_curr_length: number | undefined, loop_length: number) => { - if ((loop_curr_length && loop_curr_length < loop_length) || !loop_length) - return loop_curr_length + const errorCount = getErrorCount(nodeInfo.details) - return loop_length - } - const handleOnShowLoopDetail = (e: React.MouseEvent<HTMLButtonElement>) => { - e.stopPropagation() - e.nativeEvent.stopImmediatePropagation() - onShowLoopResultList( - nodeInfo.details || [], - nodeInfo?.loopDurationMap || nodeInfo.execution_metadata?.loop_duration_map || {}, - nodeInfo.execution_metadata?.loop_variable_map || {}, - ) - } return ( <Button className='flex w-full cursor-pointer items-center gap-2 self-stretch rounded-lg border-none bg-components-button-tertiary-bg-hover px-3 py-2 hover:bg-components-button-tertiary-bg-hover' onClick={handleOnShowLoopDetail} > <Loop className='h-4 w-4 shrink-0 text-components-button-tertiary-text' /> - <div className='system-sm-medium flex-1 text-left text-components-button-tertiary-text'>{t('workflow.nodes.loop.loop', { count: getCount(nodeInfo.details?.length, nodeInfo.metadata?.loop_length) })}{getErrorCount(nodeInfo.details) > 0 && ( + <div className='system-sm-medium flex-1 text-left text-components-button-tertiary-text'>{t('workflow.nodes.loop.loop', { count: displayLoopCount })}{errorCount > 0 && ( <> {t('workflow.nodes.loop.comma')} - {t('workflow.nodes.loop.error', { count: getErrorCount(nodeInfo.details) })} + {t('workflow.nodes.loop.error', { count: errorCount })} </> )}</div> <RiArrowRightSLine className='h-4 w-4 shrink-0 text-components-button-tertiary-text' /> diff --git a/web/app/components/workflow/run/loop-log/loop-result-panel.tsx b/web/app/components/workflow/run/loop-log/loop-result-panel.tsx index 3282c17e35..18871537e9 100644 --- a/web/app/components/workflow/run/loop-log/loop-result-panel.tsx +++ b/web/app/components/workflow/run/loop-log/loop-result-panel.tsx @@ -115,8 +115,10 @@ const LoopResultPanel: FC<Props> = ({ className="h-px grow bg-divider-subtle" ></div>} <div className={cn( - 'overflow-hidden transition-all duration-200', - expandedLoops[index] ? 'max-h-[1000px] opacity-100' : 'max-h-0 opacity-0', + 'transition-all duration-200', + expandedLoops[index] + ? 'opacity-100' + : 'max-h-0 overflow-hidden opacity-0', )}> { loopVariableMap?.[index] && ( diff --git a/web/app/components/workflow/run/loop-result-panel.tsx b/web/app/components/workflow/run/loop-result-panel.tsx index c66e8cb4b5..836bef8819 100644 --- a/web/app/components/workflow/run/loop-result-panel.tsx +++ b/web/app/components/workflow/run/loop-result-panel.tsx @@ -82,8 +82,10 @@ const LoopResultPanel: FC<Props> = ({ className="h-px grow bg-divider-subtle" ></div>} <div className={cn( - 'overflow-hidden transition-all duration-200', - expandedLoops[index] ? 'max-h-[1000px] opacity-100' : 'max-h-0 opacity-0', + 'transition-all duration-200', + expandedLoops[index] + ? 'opacity-100' + : 'max-h-0 overflow-hidden opacity-0', )}> <TracingPanel list={loop} diff --git a/web/app/components/workflow/run/node.tsx b/web/app/components/workflow/run/node.tsx index a5706186c7..9555cbd087 100644 --- a/web/app/components/workflow/run/node.tsx +++ b/web/app/components/workflow/run/node.tsx @@ -28,10 +28,12 @@ import type { } from '@/types/workflow' import ErrorHandleTip from '@/app/components/workflow/nodes/_base/components/error-handle/error-handle-tip' import { hasRetryNode } from '@/app/components/workflow/utils' +import { useDocLink } from '@/context/i18n' type Props = { className?: string nodeInfo: NodeTracing + allExecutions?: NodeTracing[] inMessage?: boolean hideInfo?: boolean hideProcessDetail?: boolean @@ -46,6 +48,7 @@ type Props = { const NodePanel: FC<Props> = ({ className, nodeInfo, + allExecutions, inMessage = false, hideInfo = false, hideProcessDetail, @@ -63,6 +66,7 @@ const NodePanel: FC<Props> = ({ doSetCollapseState(state) }, [hideProcessDetail]) const { t } = useTranslation() + const docLink = useDocLink() const getTime = (time: number) => { if (time < 1) @@ -157,6 +161,7 @@ const NodePanel: FC<Props> = ({ {isIterationNode && !notShowIterationNav && onShowIterationDetail && ( <IterationLogTrigger nodeInfo={nodeInfo} + allExecutions={allExecutions} onShowIterationResultList={onShowIterationDetail} /> )} @@ -164,6 +169,7 @@ const NodePanel: FC<Props> = ({ {isLoopNode && !notShowLoopNav && onShowLoopDetail && ( <LoopLogTrigger nodeInfo={nodeInfo} + allExecutions={allExecutions} onShowLoopResultList={onShowLoopDetail} /> )} @@ -191,7 +197,7 @@ const NodePanel: FC<Props> = ({ <StatusContainer status='stopped'> {nodeInfo.error} <a - href='https://docs.dify.ai/guides/workflow/error-handling/error-type' + href={docLink('/guides/workflow/error-handling/error-type')} target='_blank' className='text-text-accent' > diff --git a/web/app/components/workflow/run/status.tsx b/web/app/components/workflow/run/status.tsx index 2f870f2e06..253aaab3a1 100644 --- a/web/app/components/workflow/run/status.tsx +++ b/web/app/components/workflow/run/status.tsx @@ -4,6 +4,7 @@ import { useTranslation } from 'react-i18next' import cn from '@/utils/classnames' import Indicator from '@/app/components/header/indicator' import StatusContainer from '@/app/components/workflow/run/status-container' +import { useDocLink } from '@/context/i18n' type ResultProps = { status: string @@ -21,6 +22,7 @@ const StatusPanel: FC<ResultProps> = ({ exceptionCounts, }) => { const { t } = useTranslation() + const docLink = useDocLink() return ( <StatusContainer status={status}> @@ -134,7 +136,7 @@ const StatusPanel: FC<ResultProps> = ({ <div className='system-xs-medium text-text-warning'> {error} <a - href='https://docs.dify.ai/guides/workflow/error-handling/error-type' + href={docLink('/guides/workflow/error-handling/error-type')} target='_blank' className='text-text-accent' > diff --git a/web/app/components/workflow/run/tracing-panel.tsx b/web/app/components/workflow/run/tracing-panel.tsx index 27815799e2..a6e9bf9dd4 100644 --- a/web/app/components/workflow/run/tracing-panel.tsx +++ b/web/app/components/workflow/run/tracing-panel.tsx @@ -145,6 +145,7 @@ const TracingPanel: FC<TracingPanelProps> = ({ </div> <NodePanel nodeInfo={node!} + allExecutions={list} onShowIterationDetail={handleShowIterationResultList} onShowLoopDetail={handleShowLoopResultList} onShowRetryDetail={handleShowRetryResultList} diff --git a/web/app/components/workflow/style.css b/web/app/components/workflow/style.css index 253d6b7dd0..9d88ac2644 100644 --- a/web/app/components/workflow/style.css +++ b/web/app/components/workflow/style.css @@ -21,4 +21,6 @@ z-index: -1000 !important; } -#workflow-container .react-flow {} \ No newline at end of file +#workflow-container .react-flow__attribution { + background: none !important; +} diff --git a/web/app/components/workflow/types.ts b/web/app/components/workflow/types.ts index 884bdfbd10..2a0a10629b 100644 --- a/web/app/components/workflow/types.ts +++ b/web/app/components/workflow/types.ts @@ -2,6 +2,7 @@ import type { Edge as ReactFlowEdge, Node as ReactFlowNode, Viewport, + XYPosition, } from 'reactflow' import type { Resolution, TransferMethod } from '@/types/app' import type { ToolDefaultValue } from '@/app/components/workflow/block-selector/types' @@ -83,6 +84,7 @@ export type CommonNodeType<T = {}> = { type: BlockEnum width?: number height?: number + position?: XYPosition _loopLength?: number _loopIndex?: number isInLoop?: boolean @@ -196,6 +198,7 @@ export type InputVar = { hint?: string options?: string[] value_selector?: ValueSelector + hide?: boolean } & Partial<UploadFileSetting> export type ModelConfig = { diff --git a/web/app/education-apply/education-apply-page.tsx b/web/app/education-apply/education-apply-page.tsx index f99cca2670..3925695895 100644 --- a/web/app/education-apply/education-apply-page.tsx +++ b/web/app/education-apply/education-apply-page.tsx @@ -1,7 +1,6 @@ 'use client' import { - useMemo, useState, } from 'react' import { useTranslation } from 'react-i18next' @@ -23,12 +22,11 @@ import { import { useProviderContext } from '@/context/provider-context' import { useToastContext } from '@/app/components/base/toast' import { EDUCATION_VERIFYING_LOCALSTORAGE_ITEM } from '@/app/education-apply/constants' -import { getLocaleOnClient } from '@/i18n' import { noop } from 'lodash-es' - +import DifyLogo from '../components/base/logo/dify-logo' +import { useDocLink } from '@/context/i18n' const EducationApplyAge = () => { const { t } = useTranslation() - const locale = getLocaleOnClient() const [schoolName, setSchoolName] = useState('') const [role, setRole] = useState('Student') const [ageChecked, setAgeChecked] = useState(false) @@ -42,14 +40,7 @@ const EducationApplyAge = () => { const updateEducationStatus = useInvalidateEducationStatus() const { notify } = useToastContext() const router = useRouter() - - const docLink = useMemo(() => { - if (locale === 'zh-Hans') - return 'https://docs.dify.ai/zh-hans/getting-started/dify-for-education' - if (locale === 'ja-JP') - return 'https://docs.dify.ai/ja-jp/getting-started/dify-for-education' - return 'https://docs.dify.ai/getting-started/dify-for-education' - }, [locale]) + const docLink = useDocLink() const handleModalConfirm = () => { setShowModal(undefined) @@ -93,12 +84,8 @@ const EducationApplyAge = () => { }} > </div> - <div className='mt-[-349px] flex h-[88px] items-center justify-between px-8 py-6'> - <img - src='/logo/logo-site-dark.png' - alt='dify logo' - className='h-10' - /> + <div className='mt-[-349px] box-content flex h-7 items-center justify-between p-6'> + <DifyLogo size='large' style='monochromeWhite' /> </div> <div className='mx-auto max-w-[720px] px-8 pb-[180px]'> <div className='mb-2 flex h-[192px] flex-col justify-end pb-4 pt-3 text-text-primary-on-surface'> @@ -170,7 +157,7 @@ const EducationApplyAge = () => { <div className='mb-4 mt-5 h-[1px] bg-gradient-to-r from-[rgba(16,24,40,0.08)]'></div> <a className='system-xs-regular flex items-center text-text-accent' - href={docLink} + href={docLink('/getting-started/dify-for-education')} target='_blank' > {t('education.learn')} diff --git a/web/app/education-apply/verify-state-modal.tsx b/web/app/education-apply/verify-state-modal.tsx index aace6a3bb1..2ea2fe5bae 100644 --- a/web/app/education-apply/verify-state-modal.tsx +++ b/web/app/education-apply/verify-state-modal.tsx @@ -1,11 +1,11 @@ -import React, { useEffect, useMemo, useRef, useState } from 'react' +import React, { useEffect, useRef, useState } from 'react' import { createPortal } from 'react-dom' import { useTranslation } from 'react-i18next' import { RiExternalLinkLine, } from '@remixicon/react' import Button from '@/app/components/base/button' -import { getLocaleOnClient } from '@/i18n' +import { useDocLink } from '@/context/i18n' export type IConfirm = { className?: string @@ -30,20 +30,13 @@ function Confirm({ email, }: IConfirm) { const { t } = useTranslation() - const locale = getLocaleOnClient() + const docLink = useDocLink() const dialogRef = useRef<HTMLDivElement>(null) const [isVisible, setIsVisible] = useState(isShow) - - const docLink = useMemo(() => { - if (locale === 'zh-Hans') - return 'https://docs.dify.ai/zh-hans/getting-started/dify-for-education' - if (locale === 'ja-JP') - return 'https://docs.dify.ai/ja-jp/getting-started/dify-for-education' - return 'https://docs.dify.ai/getting-started/dify-for-education' - }, [locale]) + const eduDocLink = docLink('/getting-started/dify-for-education') const handleClick = () => { - window.open(docLink, '_blank', 'noopener,noreferrer') + window.open(eduDocLink, '_blank', 'noopener,noreferrer') } useEffect(() => { @@ -106,7 +99,7 @@ function Confirm({ <div className='flex items-center gap-1'> {showLink && ( <> - <a onClick={handleClick} href={docLink} target='_blank' className='system-xs-regular cursor-pointer text-text-accent'>{t('education.learn')}</a> + <a onClick={handleClick} href={eduDocLink} target='_blank' className='system-xs-regular cursor-pointer text-text-accent'>{t('education.learn')}</a> <RiExternalLinkLine className='h-3 w-3 text-text-accent' /> </> )} diff --git a/web/app/forgot-password/page.tsx b/web/app/forgot-password/page.tsx index ed3d0d529d..3b92b7b013 100644 --- a/web/app/forgot-password/page.tsx +++ b/web/app/forgot-password/page.tsx @@ -5,19 +5,23 @@ import { useSearchParams } from 'next/navigation' import Header from '../signin/_header' import ForgotPasswordForm from './ForgotPasswordForm' import ChangePasswordForm from '@/app/forgot-password/ChangePasswordForm' +import useDocumentTitle from '@/hooks/use-document-title' +import { useGlobalPublicStore } from '@/context/global-public-context' const ForgotPassword = () => { + useDocumentTitle('') const searchParams = useSearchParams() const token = searchParams.get('token') + const { systemFeatures } = useGlobalPublicStore() return ( <div className={cn('flex min-h-screen w-full justify-center bg-background-default-burn p-6')}> <div className={cn('flex w-full shrink-0 flex-col rounded-2xl border border-effects-highlight bg-background-default-subtle')}> <Header /> {token ? <ChangePasswordForm /> : <ForgotPasswordForm />} - <div className='px-8 py-6 text-sm font-normal text-text-tertiary'> + {!systemFeatures.branding.enabled && <div className='px-8 py-6 text-sm font-normal text-text-tertiary'> © {new Date().getFullYear()} LangGenius, Inc. All rights reserved. - </div> + </div>} </div> </div> ) diff --git a/web/app/init/InitPasswordPopup.tsx b/web/app/init/InitPasswordPopup.tsx index 464da9edea..6c0e9e3078 100644 --- a/web/app/init/InitPasswordPopup.tsx +++ b/web/app/init/InitPasswordPopup.tsx @@ -8,8 +8,10 @@ import Button from '@/app/components/base/button' import { basePath } from '@/utils/var' import { fetchInitValidateStatus, initValidate } from '@/service/common' import type { InitValidateStatusResponse } from '@/models/common' +import useDocumentTitle from '@/hooks/use-document-title' const InitPasswordPopup = () => { + useDocumentTitle('') const [password, setPassword] = useState('') const [loading, setLoading] = useState(true) const [validated, setValidated] = useState(false) diff --git a/web/app/install/installForm.tsx b/web/app/install/installForm.tsx index c01be722c0..c4f4cae626 100644 --- a/web/app/install/installForm.tsx +++ b/web/app/install/installForm.tsx @@ -16,6 +16,8 @@ import Button from '@/app/components/base/button' import { fetchInitValidateStatus, fetchSetupStatus, setup } from '@/service/common' import type { InitValidateStatusResponse, SetupStatusResponse } from '@/models/common' +import useDocumentTitle from '@/hooks/use-document-title' +import { useDocLink } from '@/context/i18n' const validPassword = /^(?=.*[a-zA-Z])(?=.*\d).{8,}$/ @@ -33,7 +35,9 @@ const accountFormSchema = z.object({ type AccountFormValues = z.infer<typeof accountFormSchema> const InstallForm = () => { + useDocumentTitle('') const { t } = useTranslation() + const docLink = useDocLink() const router = useRouter() const [showPassword, setShowPassword] = React.useState(false) const [loading, setLoading] = React.useState(true) @@ -172,7 +176,7 @@ const InstallForm = () => { <Link className='text-text-accent' target='_blank' rel='noopener noreferrer' - href={'https://docs.dify.ai/user-agreement/open-source'} + href={docLink('/policies/open-source')} >{t('login.license.link')}</Link> </div> </div> diff --git a/web/app/install/page.tsx b/web/app/install/page.tsx index f63fdf8443..304f44b35c 100644 --- a/web/app/install/page.tsx +++ b/web/app/install/page.tsx @@ -1,17 +1,20 @@ +'use client' import React from 'react' import Header from '../signin/_header' import InstallForm from './installForm' import cn from '@/utils/classnames' +import { useGlobalPublicStore } from '@/context/global-public-context' const Install = () => { + const { systemFeatures } = useGlobalPublicStore() return ( <div className={cn('flex min-h-screen w-full justify-center bg-background-default-burn p-6')}> <div className={cn('flex w-full shrink-0 flex-col rounded-2xl border border-effects-highlight bg-background-default-subtle')}> <Header /> <InstallForm /> - <div className='px-8 py-6 text-sm font-normal text-text-tertiary'> + {!systemFeatures.branding.enabled && <div className='px-8 py-6 text-sm font-normal text-text-tertiary'> © {new Date().getFullYear()} LangGenius, Inc. All rights reserved. - </div> + </div>} </div> </div> ) diff --git a/web/app/layout.tsx b/web/app/layout.tsx index 438f56d363..e39b4f3a1b 100644 --- a/web/app/layout.tsx +++ b/web/app/layout.tsx @@ -1,5 +1,5 @@ -import type { Viewport } from 'next' import RoutePrefixHandle from './routePrefixHandle' +import type { Viewport } from 'next' import I18nServer from './components/i18n-server' import BrowserInitor from './components/browser-initor' import SentryInitor from './components/sentry-initor' @@ -8,10 +8,7 @@ import { TanstackQueryIniter } from '@/context/query-client' import { ThemeProvider } from 'next-themes' import './styles/globals.css' import './styles/markdown.scss' - -export const metadata = { - title: 'Dify', -} +import GlobalPublicStoreProvider from '@/context/global-public-context' export const viewport: Viewport = { width: 'device-width', @@ -63,13 +60,14 @@ const LocaleLayout = async ({ <TanstackQueryIniter> <ThemeProvider attribute='data-theme' - forcedTheme='light' - defaultTheme='light' // TODO: change to 'system' when dark mode ready + defaultTheme='system' enableSystem disableTransitionOnChange > <I18nServer> - {children} + <GlobalPublicStoreProvider> + {children} + </GlobalPublicStoreProvider> </I18nServer> </ThemeProvider> </TanstackQueryIniter> diff --git a/web/app/page.module.css b/web/app/page.module.css index 90fa1554c0..b51afee7b1 100644 --- a/web/app/page.module.css +++ b/web/app/page.module.css @@ -263,4 +263,4 @@ to { transform: rotate(0deg); } -} \ No newline at end of file +} diff --git a/web/app/reset-password/layout.tsx b/web/app/reset-password/layout.tsx index 3d053e4d34..979c64e7c9 100644 --- a/web/app/reset-password/layout.tsx +++ b/web/app/reset-password/layout.tsx @@ -1,8 +1,11 @@ +'use client' import Header from '../signin/_header' import cn from '@/utils/classnames' +import { useGlobalPublicStore } from '@/context/global-public-context' -export default async function SignInLayout({ children }: any) { +export default function SignInLayout({ children }: any) { + const { systemFeatures } = useGlobalPublicStore() return <> <div className={cn('flex min-h-screen w-full justify-center bg-background-default-burn p-6')}> <div className={cn('flex w-full shrink-0 flex-col rounded-2xl border border-effects-highlight bg-background-default-subtle')}> @@ -18,9 +21,9 @@ export default async function SignInLayout({ children }: any) { {children} </div> </div> - <div className='system-xs-regular px-8 py-6 text-text-tertiary'> + {!systemFeatures.branding.enabled && <div className='system-xs-regular px-8 py-6 text-text-tertiary'> © {new Date().getFullYear()} LangGenius, Inc. All rights reserved. - </div> + </div>} </div> </div> </> diff --git a/web/app/reset-password/page.tsx b/web/app/reset-password/page.tsx index dd8cdbc69e..8a9bf78eb9 100644 --- a/web/app/reset-password/page.tsx +++ b/web/app/reset-password/page.tsx @@ -13,9 +13,11 @@ import Toast from '@/app/components/base/toast' import { sendResetPasswordCode } from '@/service/common' import I18NContext from '@/context/i18n' import { noop } from 'lodash-es' +import useDocumentTitle from '@/hooks/use-document-title' export default function CheckCode() { const { t } = useTranslation() + useDocumentTitle('') const searchParams = useSearchParams() const router = useRouter() const [email, setEmail] = useState('') diff --git a/web/app/reset-password/set-password/page.tsx b/web/app/reset-password/set-password/page.tsx index dd1c4ef1f4..ee4c114a77 100644 --- a/web/app/reset-password/set-password/page.tsx +++ b/web/app/reset-password/set-password/page.tsx @@ -105,7 +105,7 @@ const ChangePasswordForm = () => { </div> <div className="mx-auto mt-6 w-full"> - <div className="bg-white"> + <div> {/* Password */} <div className='mb-5'> <label htmlFor="password" className="system-md-semibold my-2 text-text-secondary"> diff --git a/web/app/routePrefixHandle.tsx b/web/app/routePrefixHandle.tsx index 16ed480000..58b861b014 100644 --- a/web/app/routePrefixHandle.tsx +++ b/web/app/routePrefixHandle.tsx @@ -10,7 +10,7 @@ export default function RoutePrefixHandle() { const addPrefixToImg = (e: HTMLImageElement) => { const url = new URL(e.src) const prefix = url.pathname.substr(0, basePath.length) - if (prefix !== basePath) { + if (prefix !== basePath && !url.href.startsWith('blob:') && !url.href.startsWith('data:')) { url.pathname = basePath + url.pathname e.src = url.toString() } diff --git a/web/app/signin/LoginLogo.tsx b/web/app/signin/LoginLogo.tsx new file mode 100644 index 0000000000..73dfb88205 --- /dev/null +++ b/web/app/signin/LoginLogo.tsx @@ -0,0 +1,30 @@ +'use client' +import type { FC } from 'react' +import classNames from '@/utils/classnames' +import { useGlobalPublicStore } from '@/context/global-public-context' +import { useTheme } from 'next-themes' + +type LoginLogoProps = { + className?: string +} + +const LoginLogo: FC<LoginLogoProps> = ({ + className, +}) => { + const { systemFeatures } = useGlobalPublicStore() + const { theme } = useTheme() + + let src = theme === 'light' ? '/logo/logo-site.png' : `/logo/logo-site-${theme}.png` + if (systemFeatures.branding.enabled) + src = systemFeatures.branding.login_page_logo + + return ( + <img + src={src} + className={classNames('block w-auto h-10', className)} + alt='logo' + /> + ) +} + +export default LoginLogo diff --git a/web/app/signin/_header.tsx b/web/app/signin/_header.tsx index c7b1e67092..0efd30b6cc 100644 --- a/web/app/signin/_header.tsx +++ b/web/app/signin/_header.tsx @@ -2,25 +2,49 @@ import React from 'react' import { useContext } from 'use-context-selector' import Select from '@/app/components/base/select/locale' +import Divider from '@/app/components/base/divider' import { languages } from '@/i18n/language' import type { Locale } from '@/i18n' import I18n from '@/context/i18n' -import LogoSite from '@/app/components/base/logo/logo-site' +import dynamic from 'next/dynamic' +import { useGlobalPublicStore } from '@/context/global-public-context' + +// Avoid rendering the logo and theme selector on the server +const DifyLogo = dynamic(() => import('@/app/components/base/logo/dify-logo'), { + ssr: false, + loading: () => <div className='h-7 w-16 bg-transparent' />, +}) +const ThemeSelector = dynamic(() => import('@/app/components/base/theme-selector'), { + ssr: false, + loading: () => <div className='size-8 bg-transparent' />, +}) const Header = () => { const { locale, setLocaleOnClient } = useContext(I18n) + const systemFeatures = useGlobalPublicStore(s => s.systemFeatures) - return <div className='flex w-full items-center justify-between p-6'> - <LogoSite /> - <Select - value={locale} - items={languages.filter(item => item.supported)} - onChange={(value) => { - setLocaleOnClient(value as Locale) - }} - /> - - </div> + return ( + <div className='flex w-full items-center justify-between p-6'> + {systemFeatures.branding.enabled && systemFeatures.branding.login_page_logo + ? <img + src={systemFeatures.branding.login_page_logo} + className='block h-7 w-auto object-contain' + alt='logo' + /> + : <DifyLogo size='large' />} + <div className='flex items-center gap-1'> + <Select + value={locale} + items={languages.filter(item => item.supported)} + onChange={(value) => { + setLocaleOnClient(value as Locale) + }} + /> + <Divider type='vertical' className='mx-0 ml-2 h-4' /> + <ThemeSelector /> + </div> + </div> + ) } export default Header diff --git a/web/app/signin/invite-settings/page.tsx b/web/app/signin/invite-settings/page.tsx index 925bc51f16..0480c2acb8 100644 --- a/web/app/signin/invite-settings/page.tsx +++ b/web/app/signin/invite-settings/page.tsx @@ -1,5 +1,6 @@ 'use client' import { useTranslation } from 'react-i18next' +import { useDocLink } from '@/context/i18n' import { useCallback, useState } from 'react' import Link from 'next/link' import { useContext } from 'use-context-selector' @@ -18,10 +19,11 @@ import Toast from '@/app/components/base/toast' export default function InviteSettingsPage() { const { t } = useTranslation() + const docLink = useDocLink() const router = useRouter() const searchParams = useSearchParams() const token = decodeURIComponent(searchParams.get('invite_token') as string) - const { locale, setLocaleOnClient } = useContext(I18n) + const { setLocaleOnClient } = useContext(I18n) const [name, setName] = useState('') const [language, setLanguage] = useState(LanguagesSupported[0]) const [timezone, setTimezone] = useState(Intl.DateTimeFormat().resolvedOptions().timeZone || 'America/Los_Angeles') @@ -147,7 +149,7 @@ export default function InviteSettingsPage() { <Link className='system-xs-medium text-text-accent-secondary' target='_blank' rel='noopener noreferrer' - href={`https://docs.dify.ai/${language !== LanguagesSupported[1] ? 'user-agreement' : `v/${locale.toLowerCase()}/policies`}/open-source`} + href={docLink('/policies/open-source')} >{t('login.license.link')}</Link> </div> </div> diff --git a/web/app/signin/layout.tsx b/web/app/signin/layout.tsx index 1af4082c73..4e9ac7ebf9 100644 --- a/web/app/signin/layout.tsx +++ b/web/app/signin/layout.tsx @@ -1,8 +1,13 @@ +'use client' import Header from './_header' import cn from '@/utils/classnames' +import { useGlobalPublicStore } from '@/context/global-public-context' +import useDocumentTitle from '@/hooks/use-document-title' -export default async function SignInLayout({ children }: any) { +export default function SignInLayout({ children }: any) { + const { systemFeatures } = useGlobalPublicStore() + useDocumentTitle('') return <> <div className={cn('flex min-h-screen w-full justify-center bg-background-default-burn p-6')}> <div className={cn('flex w-full shrink-0 flex-col rounded-2xl border border-effects-highlight bg-background-default-subtle')}> @@ -12,9 +17,9 @@ export default async function SignInLayout({ children }: any) { {children} </div> </div> - <div className='system-xs-regular px-8 py-6 text-text-tertiary'> + {systemFeatures.branding.enabled === false && <div className='system-xs-regular px-8 py-6 text-text-tertiary'> © {new Date().getFullYear()} LangGenius, Inc. All rights reserved. - </div> + </div>} </div> </div> </> diff --git a/web/app/signin/normalForm.tsx b/web/app/signin/normalForm.tsx index c76e088e9c..f8c973a13f 100644 --- a/web/app/signin/normalForm.tsx +++ b/web/app/signin/normalForm.tsx @@ -9,10 +9,11 @@ import MailAndPasswordAuth from './components/mail-and-password-auth' import SocialAuth from './components/social-auth' import SSOAuth from './components/sso-auth' import cn from '@/utils/classnames' -import { getSystemFeatures, invitationCheck } from '@/service/common' -import { LicenseStatus, defaultSystemFeatures } from '@/types/feature' +import { invitationCheck } from '@/service/common' +import { LicenseStatus } from '@/types/feature' import Toast from '@/app/components/base/toast' import { IS_CE_EDITION } from '@/config' +import { useGlobalPublicStore } from '@/context/global-public-context' const NormalForm = () => { const { t } = useTranslation() @@ -23,7 +24,7 @@ const NormalForm = () => { const message = decodeURIComponent(searchParams.get('message') || '') const invite_token = decodeURIComponent(searchParams.get('invite_token') || '') const [isLoading, setIsLoading] = useState(true) - const [systemFeatures, setSystemFeatures] = useState(defaultSystemFeatures) + const { systemFeatures } = useGlobalPublicStore() const [authType, updateAuthType] = useState<'code' | 'password'>('password') const [showORLine, setShowORLine] = useState(false) const [allMethodsAreDisabled, setAllMethodsAreDisabled] = useState(false) @@ -46,12 +47,9 @@ const NormalForm = () => { message, }) } - const features = await getSystemFeatures() - const allFeatures = { ...defaultSystemFeatures, ...features } - setSystemFeatures(allFeatures) - setAllMethodsAreDisabled(!allFeatures.enable_social_oauth_login && !allFeatures.enable_email_code_login && !allFeatures.enable_email_password_login && !allFeatures.sso_enforced_for_signin) - setShowORLine((allFeatures.enable_social_oauth_login || allFeatures.sso_enforced_for_signin) && (allFeatures.enable_email_code_login || allFeatures.enable_email_password_login)) - updateAuthType(allFeatures.enable_email_password_login ? 'password' : 'code') + setAllMethodsAreDisabled(!systemFeatures.enable_social_oauth_login && !systemFeatures.enable_email_code_login && !systemFeatures.enable_email_password_login && !systemFeatures.sso_enforced_for_signin) + setShowORLine((systemFeatures.enable_social_oauth_login || systemFeatures.sso_enforced_for_signin) && (systemFeatures.enable_email_code_login || systemFeatures.enable_email_password_login)) + updateAuthType(systemFeatures.enable_email_password_login ? 'password' : 'code') if (isInviteLink) { const checkRes = await invitationCheck({ url: '/activate/check', @@ -65,10 +63,9 @@ const NormalForm = () => { catch (error) { console.error(error) setAllMethodsAreDisabled(true) - setSystemFeatures(defaultSystemFeatures) } finally { setIsLoading(false) } - }, [consoleToken, refreshToken, message, router, invite_token, isInviteLink]) + }, [consoleToken, refreshToken, message, router, invite_token, isInviteLink, systemFeatures]) useEffect(() => { init() }, [init]) @@ -132,11 +129,11 @@ const NormalForm = () => { {isInviteLink ? <div className="mx-auto w-full"> <h2 className="title-4xl-semi-bold text-text-primary">{t('login.join')}{workspaceName}</h2> - <p className='body-md-regular mt-2 text-text-tertiary'>{t('login.joinTipStart')}{workspaceName}{t('login.joinTipEnd')}</p> + {!systemFeatures.branding.enabled && <p className='body-md-regular mt-2 text-text-tertiary'>{t('login.joinTipStart')}{workspaceName}{t('login.joinTipEnd')}</p>} </div> : <div className="mx-auto w-full"> <h2 className="title-4xl-semi-bold text-text-primary">{t('login.pageTitle')}</h2> - <p className='body-md-regular mt-2 text-text-tertiary'>{t('login.welcome')}</p> + {!systemFeatures.branding.enabled && <p className='body-md-regular mt-2 text-text-tertiary'>{t('login.welcome')}</p>} </div>} <div className="relative"> <div className="mt-6 flex flex-col gap-3"> @@ -184,29 +181,31 @@ const NormalForm = () => { </div> </div> </>} - <div className="system-xs-regular mt-2 block w-full text-text-tertiary"> - {t('login.tosDesc')} -   - <Link - className='system-xs-medium text-text-secondary hover:underline' - target='_blank' rel='noopener noreferrer' - href='https://dify.ai/terms' - >{t('login.tos')}</Link> -  &  - <Link - className='system-xs-medium text-text-secondary hover:underline' - target='_blank' rel='noopener noreferrer' - href='https://dify.ai/privacy' - >{t('login.pp')}</Link> - </div> - {IS_CE_EDITION && <div className="w-hull system-xs-regular mt-2 block text-text-tertiary"> - {t('login.goToInit')} -   - <Link - className='system-xs-medium text-text-secondary hover:underline' - href='/install' - >{t('login.setAdminAccount')}</Link> - </div>} + {!systemFeatures.branding.enabled && <> + <div className="system-xs-regular mt-2 block w-full text-text-tertiary"> + {t('login.tosDesc')} +   + <Link + className='system-xs-medium text-text-secondary hover:underline' + target='_blank' rel='noopener noreferrer' + href='https://dify.ai/terms' + >{t('login.tos')}</Link> +  &  + <Link + className='system-xs-medium text-text-secondary hover:underline' + target='_blank' rel='noopener noreferrer' + href='https://dify.ai/privacy' + >{t('login.pp')}</Link> + </div> + {IS_CE_EDITION && <div className="w-hull system-xs-regular mt-2 block text-text-tertiary"> + {t('login.goToInit')} +   + <Link + className='system-xs-medium text-text-secondary hover:underline' + href='/install' + >{t('login.setAdminAccount')}</Link> + </div>} + </>} </div> </div> diff --git a/web/app/signin/oneMoreStep.tsx b/web/app/signin/oneMoreStep.tsx index a78a3ab3b1..06a9ddba90 100644 --- a/web/app/signin/oneMoreStep.tsx +++ b/web/app/signin/oneMoreStep.tsx @@ -12,6 +12,7 @@ import { timezones } from '@/utils/timezone' import { LanguagesSupported, languages } from '@/i18n/language' import { oneMoreStep } from '@/service/common' import Toast from '@/app/components/base/toast' +import { useDocLink } from '@/context/i18n' type IState = { formState: 'processing' | 'error' | 'success' | 'initial' @@ -51,6 +52,7 @@ const reducer: Reducer<IState, IAction> = (state: IState, action: IAction) => { const OneMoreStep = () => { const { t } = useTranslation() + const docLink = useDocLink() const router = useRouter() const searchParams = useSearchParams() @@ -164,7 +166,7 @@ const OneMoreStep = () => { <Link className='system-xs-medium text-text-accent-secondary' target='_blank' rel='noopener noreferrer' - href={'https://docs.dify.ai/user-agreement/open-source'} + href={docLink('/policies/agreement/README')} >{t('login.license.link')}</Link> </div> </div> diff --git a/web/app/signin/page.module.css b/web/app/signin/page.module.css index 5d12925980..eda396f763 100644 --- a/web/app/signin/page.module.css +++ b/web/app/signin/page.module.css @@ -4,4 +4,4 @@ .googleIcon { background: center/contain url('./assets/google.svg'); -} \ No newline at end of file +} diff --git a/web/app/styles/globals.css b/web/app/styles/globals.css index 93ef2ce166..52e36a2767 100644 --- a/web/app/styles/globals.css +++ b/web/app/styles/globals.css @@ -697,4 +697,4 @@ button:focus-within { -ms-overflow-style: none; scrollbar-width: none; } -} \ No newline at end of file +} diff --git a/web/app/styles/preflight.css b/web/app/styles/preflight.css index 71da96aa1d..35d3b6e876 100644 --- a/web/app/styles/preflight.css +++ b/web/app/styles/preflight.css @@ -376,4 +376,4 @@ video { /* Make elements with the HTML hidden attribute stay hidden by default */ [hidden] { display: none; -} \ No newline at end of file +} diff --git a/web/config/index.ts b/web/config/index.ts index af4da76835..66ccde2aad 100644 --- a/web/config/index.ts +++ b/web/config/index.ts @@ -173,7 +173,7 @@ export const MAX_TOOLS_NUM = maxToolsNum export const DEFAULT_AGENT_SETTING = { enabled: false, - max_iteration: 5, + max_iteration: 10, strategy: AgentStrategy.functionCall, tools: [], } @@ -295,7 +295,7 @@ else if (globalThis.document?.body?.getAttribute('data-public-loop-node-max-coun export const LOOP_NODE_MAX_COUNT = loopNodeMaxCount -let maxIterationsNum = 5 +let maxIterationsNum = 99 if (process.env.NEXT_PUBLIC_MAX_ITERATIONS_NUM && process.env.NEXT_PUBLIC_MAX_ITERATIONS_NUM !== '') maxIterationsNum = Number.parseInt(process.env.NEXT_PUBLIC_MAX_ITERATIONS_NUM) diff --git a/web/context/access-control-store.ts b/web/context/access-control-store.ts new file mode 100644 index 0000000000..3a80d7c865 --- /dev/null +++ b/web/context/access-control-store.ts @@ -0,0 +1,34 @@ +import { create } from 'zustand' +import type { AccessControlAccount, AccessControlGroup } from '@/models/access-control' +import { AccessMode } from '@/models/access-control' +import type { App } from '@/types/app' + +type AccessControlStore = { + appId: App['id'] + setAppId: (appId: App['id']) => void + specificGroups: AccessControlGroup[] + setSpecificGroups: (specificGroups: AccessControlGroup[]) => void + specificMembers: AccessControlAccount[] + setSpecificMembers: (specificMembers: AccessControlAccount[]) => void + currentMenu: AccessMode + setCurrentMenu: (currentMenu: AccessMode) => void + selectedGroupsForBreadcrumb: AccessControlGroup[] + setSelectedGroupsForBreadcrumb: (selectedGroupsForBreadcrumb: AccessControlGroup[]) => void +} + +const useAccessControlStore = create<AccessControlStore>((set) => { + return { + appId: '', + setAppId: appId => set({ appId }), + specificGroups: [], + setSpecificGroups: specificGroups => set({ specificGroups }), + specificMembers: [], + setSpecificMembers: specificMembers => set({ specificMembers }), + currentMenu: AccessMode.SPECIFIC_GROUPS_MEMBERS, + setCurrentMenu: currentMenu => set({ currentMenu }), + selectedGroupsForBreadcrumb: [], + setSelectedGroupsForBreadcrumb: selectedGroupsForBreadcrumb => set({ selectedGroupsForBreadcrumb }), + } +}) + +export default useAccessControlStore diff --git a/web/context/app-context.tsx b/web/context/app-context.tsx index 79cc246a93..9b95b0f1eb 100644 --- a/web/context/app-context.tsx +++ b/web/context/app-context.tsx @@ -6,17 +6,14 @@ import { createContext, useContext, useContextSelector } from 'use-context-selec import type { FC, ReactNode } from 'react' import { fetchAppList } from '@/service/apps' import Loading from '@/app/components/base/loading' -import { fetchCurrentWorkspace, fetchLanggeniusVersion, fetchUserProfile, getSystemFeatures } from '@/service/common' +import { fetchCurrentWorkspace, fetchLanggeniusVersion, fetchUserProfile } from '@/service/common' import type { App } from '@/types/app' import type { ICurrentWorkspace, LangGeniusVersionResponse, UserProfileResponse } from '@/models/common' import MaintenanceNotice from '@/app/components/header/maintenance-notice' -import type { SystemFeatures } from '@/types/feature' -import { defaultSystemFeatures } from '@/types/feature' import { noop } from 'lodash-es' export type AppContextValue = { apps: App[] - systemFeatures: SystemFeatures mutateApps: VoidFunction userProfile: UserProfileResponse mutateUserProfile: VoidFunction @@ -53,7 +50,6 @@ const initialWorkspaceInfo: ICurrentWorkspace = { } const AppContext = createContext<AppContextValue>({ - systemFeatures: defaultSystemFeatures, apps: [], mutateApps: noop, userProfile: { @@ -92,10 +88,6 @@ export const AppContextProvider: FC<AppContextProviderProps> = ({ children }) => const { data: userProfileResponse, mutate: mutateUserProfile } = useSWR({ url: '/account/profile', params: {} }, fetchUserProfile) const { data: currentWorkspaceResponse, mutate: mutateCurrentWorkspace, isLoading: isLoadingCurrentWorkspace } = useSWR({ url: '/workspaces/current', params: {} }, fetchCurrentWorkspace) - const { data: systemFeatures } = useSWR({ url: '/console/system-features' }, getSystemFeatures, { - fallbackData: defaultSystemFeatures, - }) - const [userProfile, setUserProfile] = useState<UserProfileResponse>() const [langeniusVersionInfo, setLangeniusVersionInfo] = useState<LangGeniusVersionResponse>(initialLangeniusVersionInfo) const [currentWorkspace, setCurrentWorkspace] = useState<ICurrentWorkspace>(initialWorkspaceInfo) @@ -129,7 +121,6 @@ export const AppContextProvider: FC<AppContextProviderProps> = ({ children }) => return ( <AppContext.Provider value={{ apps: appList.data, - systemFeatures: { ...defaultSystemFeatures, ...systemFeatures }, mutateApps, userProfile, mutateUserProfile, diff --git a/web/context/global-public-context.tsx b/web/context/global-public-context.tsx new file mode 100644 index 0000000000..26ad84be65 --- /dev/null +++ b/web/context/global-public-context.tsx @@ -0,0 +1,51 @@ +'use client' +import { create } from 'zustand' +import { useQuery } from '@tanstack/react-query' +import type { FC, PropsWithChildren } from 'react' +import { useEffect } from 'react' +import type { SystemFeatures } from '@/types/feature' +import { defaultSystemFeatures } from '@/types/feature' +import { getSystemFeatures } from '@/service/common' +import Loading from '@/app/components/base/loading' +import { AccessMode } from '@/models/access-control' + +type GlobalPublicStore = { + isGlobalPending: boolean + setIsGlobalPending: (isPending: boolean) => void + systemFeatures: SystemFeatures + setSystemFeatures: (systemFeatures: SystemFeatures) => void + webAppAccessMode: AccessMode, + setWebAppAccessMode: (webAppAccessMode: AccessMode) => void +} + +export const useGlobalPublicStore = create<GlobalPublicStore>(set => ({ + isGlobalPending: true, + setIsGlobalPending: (isPending: boolean) => set(() => ({ isGlobalPending: isPending })), + systemFeatures: defaultSystemFeatures, + setSystemFeatures: (systemFeatures: SystemFeatures) => set(() => ({ systemFeatures })), + webAppAccessMode: AccessMode.PUBLIC, + setWebAppAccessMode: (webAppAccessMode: AccessMode) => set(() => ({ webAppAccessMode })), +})) + +const GlobalPublicStoreProvider: FC<PropsWithChildren> = ({ + children, +}) => { + const { isPending, data } = useQuery({ + queryKey: ['systemFeatures'], + queryFn: getSystemFeatures, + }) + const { setSystemFeatures, setIsGlobalPending: setIsPending } = useGlobalPublicStore() + useEffect(() => { + if (data) + setSystemFeatures({ ...defaultSystemFeatures, ...data }) + }, [data, setSystemFeatures]) + + useEffect(() => { + setIsPending(isPending) + }, [isPending, setIsPending]) + + if (isPending) + return <div className='flex h-screen w-screen items-center justify-center'><Loading /></div> + return <>{children}</> +} +export default GlobalPublicStoreProvider diff --git a/web/context/i18n.ts b/web/context/i18n.ts index 6db211dd5d..ef53a4b481 100644 --- a/web/context/i18n.ts +++ b/web/context/i18n.ts @@ -3,7 +3,7 @@ import { useContext, } from 'use-context-selector' import type { Locale } from '@/i18n' -import { getLanguage } from '@/i18n/language' +import { getDocLanguage, getLanguage, getPricingPageLanguage } from '@/i18n/language' import { noop } from 'lodash-es' type II18NContext = { @@ -24,5 +24,23 @@ export const useGetLanguage = () => { return getLanguage(locale) } +export const useGetPricingPageLanguage = () => { + const { locale } = useI18N() + + return getPricingPageLanguage(locale) +} +const defaultDocBaseUrl = 'https://docs.dify.ai' +export const useDocLink = (baseUrl?: string): ((path?: string, pathMap?: { [index: string]: string }) => string) => { + let baseDocUrl = baseUrl || defaultDocBaseUrl + baseDocUrl = (baseDocUrl.endsWith('/')) ? baseDocUrl.slice(0, -1) : baseDocUrl + const { locale } = useI18N() + const docLanguage = getDocLanguage(locale) + return (path?: string, pathMap?: { [index: string]: string }): string => { + const pathUrl = path || '' + let targetPath = (pathMap) ? pathMap[locale] || pathUrl : pathUrl + targetPath = (targetPath.startsWith('/')) ? targetPath.slice(1) : targetPath + return `${baseDocUrl}/${docLanguage}/${targetPath}` + } +} export default I18NContext diff --git a/web/context/mitt-context.tsx b/web/context/mitt-context.tsx new file mode 100644 index 0000000000..2b437b0c30 --- /dev/null +++ b/web/context/mitt-context.tsx @@ -0,0 +1,27 @@ +import { createContext, useContext, useContextSelector } from 'use-context-selector' +import { useMitt } from '@/hooks/use-mitt' +import { noop } from 'lodash-es' + +type ContextValueType = ReturnType<typeof useMitt> +export const MittContext = createContext<ContextValueType>({ + emit: noop, + useSubscribe: noop, +}) + +export const MittProvider = ({ children }: { children: React.ReactNode }) => { + const mitt = useMitt() + + return ( + <MittContext.Provider value={mitt}> + {children} + </MittContext.Provider> + ) +} + +export const useMittContext = () => { + return useContext(MittContext) +} + +export function useMittContextSelector<T>(selector: (value: ContextValueType) => T): T { + return useContextSelector(MittContext, selector) +} diff --git a/web/context/provider-context.tsx b/web/context/provider-context.tsx index bd997380e7..70c9019aca 100644 --- a/web/context/provider-context.tsx +++ b/web/context/provider-context.tsx @@ -17,6 +17,7 @@ import { } from '@/app/components/header/account-setting/model-provider-page/declarations' import type { Model, ModelProvider } from '@/app/components/header/account-setting/model-provider-page/declarations' import type { RETRIEVE_METHOD } from '@/types/app' +import type { BasicPlan } from '@/app/components/billing/type' import { Plan, type UsagePlanInfo } from '@/app/components/billing/type' import { fetchCurrentPlanInfo } from '@/service/billing' import { parseCurrentPlan } from '@/app/components/billing/utils' @@ -34,7 +35,7 @@ type ProviderContextState = { supportRetrievalMethods: RETRIEVE_METHOD[] isAPIKeySet: boolean plan: { - type: Plan + type: BasicPlan usage: UsagePlanInfo total: UsagePlanInfo } @@ -47,6 +48,14 @@ type ProviderContextState = { enableEducationPlan: boolean isEducationWorkspace: boolean isEducationAccount: boolean + webappCopyrightEnabled: boolean + licenseLimit: { + workspace_members: { + size: number + limit: number + } + }, + refreshLicenseLimit: () => void } const ProviderContext = createContext<ProviderContextState>({ modelProviders: [], @@ -80,6 +89,14 @@ const ProviderContext = createContext<ProviderContextState>({ enableEducationPlan: false, isEducationWorkspace: false, isEducationAccount: false, + webappCopyrightEnabled: false, + licenseLimit: { + workspace_members: { + size: 0, + limit: 0, + }, + }, + refreshLicenseLimit: noop, }) export const useProviderContext = () => useContext(ProviderContext) @@ -106,6 +123,13 @@ export const ProviderContextProvider = ({ const [enableReplaceWebAppLogo, setEnableReplaceWebAppLogo] = useState(false) const [modelLoadBalancingEnabled, setModelLoadBalancingEnabled] = useState(false) const [datasetOperatorEnabled, setDatasetOperatorEnabled] = useState(false) + const [webappCopyrightEnabled, setWebappCopyrightEnabled] = useState(false) + const [licenseLimit, setLicenseLimit] = useState({ + workspace_members: { + size: 0, + limit: 0, + }, + }) const [enableEducationPlan, setEnableEducationPlan] = useState(false) const [isEducationWorkspace, setIsEducationWorkspace] = useState(false) @@ -134,6 +158,14 @@ export const ProviderContextProvider = ({ setModelLoadBalancingEnabled(true) if (data.dataset_operator_enabled) setDatasetOperatorEnabled(true) + if (data.model_load_balancing_enabled) + setModelLoadBalancingEnabled(true) + if (data.dataset_operator_enabled) + setDatasetOperatorEnabled(true) + if (data.webapp_copyright_enabled) + setWebappCopyrightEnabled(true) + if (data.workspace_members) + setLicenseLimit({ workspace_members: data.workspace_members }) } catch (error) { console.error('Failed to fetch plan info:', error) @@ -191,6 +223,9 @@ export const ProviderContextProvider = ({ enableEducationPlan, isEducationWorkspace, isEducationAccount: isEducationAccount?.result || false, + webappCopyrightEnabled, + licenseLimit, + refreshLicenseLimit: fetchPlan, }}> {children} </ProviderContext.Provider> diff --git a/web/docker/entrypoint.sh b/web/docker/entrypoint.sh index adb1d7d3af..59e9c472b3 100755 --- a/web/docker/entrypoint.sh +++ b/web/docker/entrypoint.sh @@ -32,4 +32,7 @@ export NEXT_PUBLIC_MAX_TOOLS_NUM=${MAX_TOOLS_NUM} export NEXT_PUBLIC_ENABLE_WEBSITE_JINAREADER=${ENABLE_WEBSITE_JINAREADER:-true} export NEXT_PUBLIC_ENABLE_WEBSITE_FIRECRAWL=${ENABLE_WEBSITE_FIRECRAWL:-true} export NEXT_PUBLIC_ENABLE_WEBSITE_WATERCRAWL=${ENABLE_WEBSITE_WATERCRAWL:-true} +export NEXT_PUBLIC_LOOP_NODE_MAX_COUNT=${LOOP_NODE_MAX_COUNT} +export NEXT_PUBLIC_MAX_PARALLEL_LIMIT=${MAX_PARALLEL_LIMIT} +export NEXT_PUBLIC_MAX_ITERATIONS_NUM=${MAX_ITERATIONS_NUM} pm2 start /app/web/server.js --name dify-web --cwd /app/web -i ${PM2_INSTANCES} --no-daemon diff --git a/web/eslint.config.mjs b/web/eslint.config.mjs index d40d96356b..b2696f7561 100644 --- a/web/eslint.config.mjs +++ b/web/eslint.config.mjs @@ -167,7 +167,7 @@ export default combine( 'sonarjs/max-lines': 'warn', // max 1000 lines 'sonarjs/no-variable-usage-before-declaration': 'error', // security - // eslint-disable-next-line sonarjs/no-hardcoded-passwords + 'sonarjs/no-hardcoded-passwords': 'off', // detect the wrong code that is not password. 'sonarjs/no-hardcoded-secrets': 'off', 'sonarjs/pseudo-random': 'off', diff --git a/web/hooks/use-document-title.spec.ts b/web/hooks/use-document-title.spec.ts new file mode 100644 index 0000000000..a8d3d56cff --- /dev/null +++ b/web/hooks/use-document-title.spec.ts @@ -0,0 +1,65 @@ +import { defaultSystemFeatures } from '@/types/feature' +import { act, renderHook } from '@testing-library/react' +import useDocumentTitle from './use-document-title' +import { useGlobalPublicStore } from '@/context/global-public-context' + +jest.mock('@/service/common', () => ({ + getSystemFeatures: jest.fn(() => ({ ...defaultSystemFeatures })), +})) + +describe('title should be empty if systemFeatures is pending', () => { + act(() => { + useGlobalPublicStore.setState({ + systemFeatures: { ...defaultSystemFeatures, branding: { ...defaultSystemFeatures.branding, enabled: false } }, + isGlobalPending: true, + }) + }) + it('document title should be empty if set title', () => { + renderHook(() => useDocumentTitle('test')) + expect(document.title).toBe('') + }) + it('document title should be empty if not set title', () => { + renderHook(() => useDocumentTitle('')) + expect(document.title).toBe('') + }) +}) + +describe('use default branding', () => { + beforeEach(() => { + act(() => { + useGlobalPublicStore.setState({ + isGlobalPending: false, + systemFeatures: { ...defaultSystemFeatures, branding: { ...defaultSystemFeatures.branding, enabled: false } }, + }) + }) + }) + it('document title should be test-Dify if set title', () => { + renderHook(() => useDocumentTitle('test')) + expect(document.title).toBe('test - Dify') + }) + + it('document title should be Dify if not set title', () => { + renderHook(() => useDocumentTitle('')) + expect(document.title).toBe('Dify') + }) +}) + +describe('use specific branding', () => { + beforeEach(() => { + act(() => { + useGlobalPublicStore.setState({ + isGlobalPending: false, + systemFeatures: { ...defaultSystemFeatures, branding: { ...defaultSystemFeatures.branding, enabled: true, application_title: 'Test' } }, + }) + }) + }) + it('document title should be test-Test if set title', () => { + renderHook(() => useDocumentTitle('test')) + expect(document.title).toBe('test - Test') + }) + + it('document title should be Test if not set title', () => { + renderHook(() => useDocumentTitle('')) + expect(document.title).toBe('Test') + }) +}) diff --git a/web/hooks/use-document-title.ts b/web/hooks/use-document-title.ts new file mode 100644 index 0000000000..2c848a1f56 --- /dev/null +++ b/web/hooks/use-document-title.ts @@ -0,0 +1,23 @@ +'use client' +import { useGlobalPublicStore } from '@/context/global-public-context' +import { useFavicon, useTitle } from 'ahooks' + +export default function useDocumentTitle(title: string) { + const isPending = useGlobalPublicStore(s => s.isGlobalPending) + const systemFeatures = useGlobalPublicStore(s => s.systemFeatures) + const prefix = title ? `${title} - ` : '' + let titleStr = '' + let favicon = '' + if (isPending === false) { + if (systemFeatures.branding.enabled) { + titleStr = `${prefix}${systemFeatures.branding.application_title}` + favicon = systemFeatures.branding.favicon + } + else { + titleStr = `${prefix}Dify` + favicon = '/favicon.ico' + } + } + useTitle(titleStr) + useFavicon(favicon) +} diff --git a/web/hooks/use-import-dsl.ts b/web/hooks/use-import-dsl.ts new file mode 100644 index 0000000000..e5fafb1e75 --- /dev/null +++ b/web/hooks/use-import-dsl.ts @@ -0,0 +1,163 @@ +import { + useCallback, + useRef, + useState, +} from 'react' +import { useTranslation } from 'react-i18next' +import { useRouter } from 'next/navigation' +import type { + DSLImportMode, + DSLImportResponse, +} from '@/models/app' +import { DSLImportStatus } from '@/models/app' +import { + importDSL, + importDSLConfirm, +} from '@/service/apps' +import type { AppIconType } from '@/types/app' +import { useToastContext } from '@/app/components/base/toast' +import { usePluginDependencies } from '@/app/components/workflow/plugin-dependency/hooks' +import { getRedirection } from '@/utils/app-redirection' +import { useSelector } from '@/context/app-context' +import { NEED_REFRESH_APP_LIST_KEY } from '@/config' + +type DSLPayload = { + mode: DSLImportMode + yaml_content?: string + yaml_url?: string + name?: string + icon_type?: AppIconType + icon?: string + icon_background?: string + description?: string +} +type ResponseCallback = { + onSuccess?: () => void + onPending?: (payload: DSLImportResponse) => void + onFailed?: () => void +} +export const useImportDSL = () => { + const { t } = useTranslation() + const { notify } = useToastContext() + const [isFetching, setIsFetching] = useState(false) + const { handleCheckPluginDependencies } = usePluginDependencies() + const isCurrentWorkspaceEditor = useSelector(s => s.isCurrentWorkspaceEditor) + const { push } = useRouter() + const [versions, setVersions] = useState<{ importedVersion: string; systemVersion: string }>() + const importIdRef = useRef<string>('') + + const handleImportDSL = useCallback(async ( + payload: DSLPayload, + { + onSuccess, + onPending, + onFailed, + }: ResponseCallback, + ) => { + if (isFetching) + return + setIsFetching(true) + + try { + const response = await importDSL(payload) + + if (!response) + return + + const { + id, + status, + app_id, + app_mode, + imported_dsl_version, + current_dsl_version, + } = response + + if (status === DSLImportStatus.COMPLETED || status === DSLImportStatus.COMPLETED_WITH_WARNINGS) { + if (!app_id) + return + + notify({ + type: status === DSLImportStatus.COMPLETED ? 'success' : 'warning', + message: t(status === DSLImportStatus.COMPLETED ? 'app.newApp.appCreated' : 'app.newApp.caution'), + children: status === DSLImportStatus.COMPLETED_WITH_WARNINGS && t('app.newApp.appCreateDSLWarning'), + }) + onSuccess?.() + localStorage.setItem(NEED_REFRESH_APP_LIST_KEY, '1') + await handleCheckPluginDependencies(app_id) + getRedirection(isCurrentWorkspaceEditor, { id: app_id, mode: app_mode }, push) + } + else if (status === DSLImportStatus.PENDING) { + setVersions({ + importedVersion: imported_dsl_version ?? '', + systemVersion: current_dsl_version ?? '', + }) + importIdRef.current = id + onPending?.(response) + } + else { + notify({ type: 'error', message: t('app.newApp.appCreateFailed') }) + onFailed?.() + } + } + catch { + notify({ type: 'error', message: t('app.newApp.appCreateFailed') }) + onFailed?.() + } + finally { + setIsFetching(false) + } + }, [t, notify, handleCheckPluginDependencies, isCurrentWorkspaceEditor, push, isFetching]) + + const handleImportDSLConfirm = useCallback(async ( + { + onSuccess, + onFailed, + }: Pick<ResponseCallback, 'onSuccess' | 'onFailed'>, + ) => { + if (isFetching) + return + setIsFetching(true) + if (!importIdRef.current) + return + + try { + const response = await importDSLConfirm({ + import_id: importIdRef.current, + }) + + const { status, app_id, app_mode } = response + if (!app_id) + return + + if (status === DSLImportStatus.COMPLETED) { + onSuccess?.() + notify({ + type: 'success', + message: t('app.newApp.appCreated'), + }) + await handleCheckPluginDependencies(app_id) + localStorage.setItem(NEED_REFRESH_APP_LIST_KEY, '1') + getRedirection(isCurrentWorkspaceEditor, { id: app_id!, mode: app_mode }, push) + } + else if (status === DSLImportStatus.FAILED) { + notify({ type: 'error', message: t('app.newApp.appCreateFailed') }) + onFailed?.() + } + } + catch { + notify({ type: 'error', message: t('app.newApp.appCreateFailed') }) + onFailed?.() + } + finally { + setIsFetching(false) + } + }, [t, notify, handleCheckPluginDependencies, isCurrentWorkspaceEditor, push, isFetching]) + + return { + handleImportDSL, + handleImportDSLConfirm, + versions, + isFetching, + } +} diff --git a/web/hooks/use-tab-searchparams.ts b/web/hooks/use-tab-searchparams.ts index bbeb1ea8be..0c0e3b7773 100644 --- a/web/hooks/use-tab-searchparams.ts +++ b/web/hooks/use-tab-searchparams.ts @@ -1,4 +1,5 @@ -import { usePathname, useSearchParams } from 'next/navigation' +'use client' +import { usePathname, useRouter, useSearchParams } from 'next/navigation' import { useState } from 'react' type UseTabSearchParamsOptions = { @@ -25,7 +26,8 @@ export const useTabSearchParams = ({ disableSearchParams = false, }: UseTabSearchParamsOptions) => { const pathnameFromHook = usePathname() - const pathName = window?.location?.pathname || pathnameFromHook + const router = useRouter() + const pathName = pathnameFromHook || window?.location?.pathname const searchParams = useSearchParams() const [activeTab, setTab] = useState<string>( !disableSearchParams @@ -37,7 +39,7 @@ export const useTabSearchParams = ({ setTab(newActiveTab) if (disableSearchParams) return - history[`${routingBehavior}State`](null, '', `${pathName}?${searchParamName}=${newActiveTab}`) + router[`${routingBehavior}`](`${pathName}?${searchParamName}=${newActiveTab}`) } return [activeTab, setActiveTab] as const diff --git a/web/i18n/README.md b/web/i18n/README.md index 9384ffc519..b81ffbf4c3 100644 --- a/web/i18n/README.md +++ b/web/i18n/README.md @@ -115,13 +115,13 @@ export const languages = [ }, { value: 'ja-JP', - name: '日本語(日本)', + name: '日本語 (日本)', example: 'こんにちは、Dify!', supported: false, }, { value: 'ko-KR', - name: '한국어(대한민국)', + name: '한국어 (대한민국)', example: '안녕, Dify!', supported: true, }, diff --git a/web/i18n/auto-gen-i18n.js b/web/i18n/auto-gen-i18n.js index 7599a87f3e..a03b3aac24 100644 --- a/web/i18n/auto-gen-i18n.js +++ b/web/i18n/auto-gen-i18n.js @@ -43,7 +43,7 @@ async function translateMissingKeyDeeply(sourceObj, targetObject, toLanguage) { targetObject[key] = translation } catch { - console.error(`Error translating ${sourceObj[key]}(${key}) to ${toLanguage}`) + console.error(`Error translating "${sourceObj[key]}" to ${toLanguage}. Key: ${key}`) } } } @@ -59,6 +59,14 @@ async function autoGenTrans(fileName, toGenLanguage) { const toGenLanguageFilePath = path.join(__dirname, toGenLanguage, `${fileName}.ts`) // eslint-disable-next-line sonarjs/code-eval const fullKeyContent = eval(transpile(fs.readFileSync(fullKeyFilePath, 'utf8'))) + // if toGenLanguageFilePath is not exist, create it + if (!fs.existsSync(toGenLanguageFilePath)) { + fs.writeFileSync(toGenLanguageFilePath, `const translation = { +} + +export default translation +`) + } // To keep object format and format it for magicast to work: const translation = { ... } => export default {...} const readContent = await loadFile(toGenLanguageFilePath) const { code: toGenContent } = generateCode(readContent) diff --git a/web/i18n/check-i18n.js b/web/i18n/check-i18n.js index ccd9741a60..55a2301ed8 100644 --- a/web/i18n/check-i18n.js +++ b/web/i18n/check-i18n.js @@ -27,9 +27,14 @@ async function getKeysFromLanuage(language) { // console.log(camelCaseFileName) const content = fs.readFileSync(filePath, 'utf8') // eslint-disable-next-line sonarjs/code-eval - const translation = eval(transpile(content)) + const translationObj = eval(transpile(content)) // console.log(translation) - const keys = Object.keys(translation) + if(!translationObj || typeof translationObj !== 'object') { + console.error(`Error parsing file: ${filePath}`) + reject(new Error(`Error parsing file: ${filePath}`)) + return + } + const keys = Object.keys(translationObj) const nestedKeys = [] const iterateKeys = (obj, prefix = '') => { for (const key in obj) { @@ -39,7 +44,7 @@ async function getKeysFromLanuage(language) { iterateKeys(obj[key], nestedKey) } } - iterateKeys(translation) + iterateKeys(translationObj) allKeys = [...keys, ...nestedKeys].map( key => `${camelCaseFileName}.${key}`, diff --git a/web/i18n/de-DE/app-debug.ts b/web/i18n/de-DE/app-debug.ts index c00c799519..4022e755e9 100644 --- a/web/i18n/de-DE/app-debug.ts +++ b/web/i18n/de-DE/app-debug.ts @@ -1,392 +1,392 @@ -const translation = { - pageTitle: { - line1: 'PROMPT', - line2: 'Engineering', - }, - orchestrate: 'Orchestrieren', - promptMode: { - simple: 'Wechseln Sie in den Expertenmodus, um das gesamte PROMPT zu bearbeiten', - advanced: 'Expertenmodus', - switchBack: 'Zurückwechseln', - advancedWarning: { - title: 'Sie haben in den Expertenmodus gewechselt, und sobald Sie das PROMPT ändern, können Sie NICHT zum Basis-Modus zurückkehren.', - description: 'Im Expertenmodus können Sie das gesamte PROMPT bearbeiten.', - learnMore: 'Mehr erfahren', - ok: 'OK', - }, - operation: { - addMessage: 'Nachricht hinzufügen', - }, - contextMissing: 'Komponente fehlt, die Wirksamkeit des Prompts könnte schlecht sein.', - }, - operation: { - applyConfig: 'Veröffentlichen', - resetConfig: 'Zurücksetzen', - debugConfig: 'Debuggen', - addFeature: 'Funktion hinzufügen', - automatic: 'Generieren', - stopResponding: 'Antworten stoppen', - agree: 'gefällt mir', - disagree: 'gefällt mir nicht', - cancelAgree: 'Gefällt mir zurücknehmen', - cancelDisagree: 'Gefällt mir nicht zurücknehmen', - userAction: 'Benutzer ', - }, - notSetAPIKey: { - title: 'LLM-Anbieterschlüssel wurde nicht festgelegt', - trailFinished: 'Testversion beendet', - description: 'Der LLM-Anbieterschlüssel wurde nicht festgelegt und muss vor dem Debuggen festgelegt werden.', - settingBtn: 'Zu den Einstellungen gehen', - }, - trailUseGPT4Info: { - title: 'Unterstützt derzeit kein gpt-4', - description: 'Um gpt-4 zu verwenden, bitte API-Schlüssel festlegen.', - }, - feature: { - groupChat: { - title: 'Chatverbesserung', - description: 'Voreinstellungen für Konversationen zu Apps hinzufügen kann die Benutzererfahrung verbessern.', - }, - groupExperience: { - title: 'Erfahrungsverbesserung', - }, - conversationOpener: { - title: 'Gesprächseröffnungen', - description: 'In einer Chat-App wird der erste Satz, den die KI aktiv an den Benutzer richtet, üblicherweise als Begrüßung verwendet.', - }, - suggestedQuestionsAfterAnswer: { - title: 'Nachfolgefragen', - description: 'Das Einrichten von Vorschlägen für nächste Fragen kann den Chat für Benutzer verbessern.', - resDes: '3 Vorschläge für die nächste Benutzerfrage.', - tryToAsk: 'Versuchen Sie zu fragen', - }, - moreLikeThis: { - title: 'Mehr davon', - description: 'Mehrere Texte gleichzeitig generieren und dann bearbeiten und weiter generieren', - generateNumTip: 'Anzahl der generierten Texte pro Durchgang', - tip: 'Die Verwendung dieser Funktion verursacht zusätzliche Token-Kosten', - }, - speechToText: { - title: 'Sprache zu Text', - description: 'Einmal aktiviert, können Sie Spracheingabe verwenden.', - resDes: 'Spracheingabe ist aktiviert', - }, - textToSpeech: { - title: 'Text zu Sprache', - description: 'Einmal aktiviert, kann Text in Sprache umgewandelt werden.', - resDes: 'Text zu Audio ist aktiviert', - }, - citation: { - title: 'Zitate und Urheberangaben', - description: 'Einmal aktiviert, zeigen Sie das Quelldokument und den zugeordneten Abschnitt des generierten Inhalts an.', - resDes: 'Zitate und Urheberangaben sind aktiviert', - }, - annotation: { - title: 'Annotation Antwort', - description: 'Sie können manuell hochwertige Antworten zum Cache hinzufügen für bevorzugte Übereinstimmung mit ähnlichen Benutzerfragen.', - resDes: 'Annotationsantwort ist aktiviert', - scoreThreshold: { - title: 'Schwellenwert', - description: 'Wird verwendet, um den Ähnlichkeitsschwellenwert für die Annotation Antwort einzustellen.', - easyMatch: 'Einfache Übereinstimmung', - accurateMatch: 'Genaue Übereinstimmung', - }, - matchVariable: { - title: 'Übereinstimmungsvariable', - choosePlaceholder: 'Wählen Sie Übereinstimmungsvariable', - }, - cacheManagement: 'Annotationen', - cached: 'Annotiert', - remove: 'Entfernen', - removeConfirm: 'Diese Annotation löschen?', - add: 'Annotation hinzufügen', - edit: 'Annotation bearbeiten', - }, - dataSet: { - title: 'Kontext', - noData: 'Sie können Wissen als Kontext importieren', - words: 'Wörter', - textBlocks: 'Textblöcke', - selectTitle: 'Wählen Sie Referenzwissen', - selected: 'Wissen ausgewählt', - noDataSet: 'Kein Wissen gefunden', - toCreate: 'Erstellen gehen', - notSupportSelectMulti: 'Unterstützt derzeit nur ein Wissen', - queryVariable: { - title: 'Abfragevariable', - tip: 'Diese Variable wird als Eingabe für die Kontextabfrage verwendet, um kontextbezogene Informationen in Bezug auf die Eingabe dieser Variable zu erhalten.', - choosePlaceholder: 'Wählen Sie Abfragevariable', - noVar: 'Keine Variablen', - noVarTip: 'Bitte erstellen Sie eine Variable im Variablenbereich', - unableToQueryDataSet: 'Konnte das Wissen nicht abfragen', - unableToQueryDataSetTip: 'Konnte das Wissen nicht erfolgreich abfragen, bitte wählen Sie eine Kontextabfragevariable im Kontextbereich.', - ok: 'OK', - contextVarNotEmpty: 'Kontextabfragevariable darf nicht leer sein', - deleteContextVarTitle: 'Variable „{{varName}}“ löschen?', - deleteContextVarTip: 'Diese Variable wurde als Kontextabfragevariable festgelegt und deren Entfernung wird die normale Verwendung des Wissens beeinträchtigen. Wenn Sie sie trotzdem löschen müssen, wählen Sie sie bitte im Kontextbereich erneut.', - }, - }, - tools: { - title: 'Werkzeuge', - tips: 'Werkzeuge bieten eine standardisierte API-Aufrufmethode, die Benutzereingaben oder Variablen als Anfrageparameter für die Abfrage externer Daten als Kontext verwendet.', - toolsInUse: '{{count}} Werkzeuge in Verwendung', - modal: { - title: 'Werkzeug', - toolType: { - title: 'Werkzeugtyp', - placeholder: 'Bitte wählen Sie den Werkzeugtyp', - }, - name: { - title: 'Name', - placeholder: 'Bitte geben Sie den Namen ein', - }, - variableName: { - title: 'Variablenname', - placeholder: 'Bitte geben Sie den Variablennamen ein', - }, - }, - }, - conversationHistory: { - title: 'Konversationsverlauf', - description: 'Präfixnamen für Konversationsrollen festlegen', - tip: 'Der Konversationsverlauf ist nicht aktiviert, bitte fügen Sie <histories> im Prompt oben ein.', - learnMore: 'Mehr erfahren', - editModal: { - title: 'Konversationsrollennamen bearbeiten', - userPrefix: 'Benutzerpräfix', - assistantPrefix: 'Assistentenpräfix', - }, - }, - toolbox: { - title: 'WERKZEUGKASTEN', - }, - moderation: { - title: 'Inhaltsmoderation', - description: 'Sichern Sie die Ausgabe des Modells durch Verwendung der Moderations-API oder durch Pflege einer Liste sensibler Wörter.', - allEnabled: 'INHALT von EINGABE/AUSGABE aktiviert', - inputEnabled: 'INHALT von EINGABE aktiviert', - outputEnabled: 'INHALT von AUSGABE aktiviert', - modal: { - title: 'Einstellungen zur Inhaltsmoderation', - provider: { - title: 'Anbieter', - openai: 'OpenAI-Moderation', - openaiTip: { - prefix: 'OpenAI-Moderation erfordert einen konfigurierten OpenAI-API-Schlüssel in den ', - suffix: '.', - }, - keywords: 'Schlüsselwörter', - }, - keywords: { - tip: 'Jeweils eine pro Zeile, getrennt durch Zeilenumbrüche. Bis zu 100 Zeichen pro Zeile.', - placeholder: 'Jeweils eine pro Zeile, getrennt durch Zeilenumbrüche', - line: 'Zeile', - }, - content: { - input: 'INHALT der EINGABE moderieren', - output: 'INHALT der AUSGABE moderieren', - preset: 'Voreingestellte Antworten', - placeholder: 'Inhalt der voreingestellten Antworten hier', - condition: 'Moderation von INHALT der EINGABE und AUSGABE mindestens eine aktiviert', - fromApi: 'Voreingestellte Antworten werden durch API zurückgegeben', - errorMessage: 'Voreingestellte Antworten dürfen nicht leer sein', - supportMarkdown: 'Markdown unterstützt', - }, - openaiNotConfig: { - before: 'OpenAI-Moderation erfordert einen konfigurierten OpenAI-API-Schlüssel in den', - after: '', - }, - }, - }, - }, - resetConfig: { - title: 'Zurücksetzen bestätigen?', - message: - 'Zurücksetzen verwirft Änderungen und stellt die zuletzt veröffentlichte Konfiguration wieder her.', - }, - errorMessage: { - nameOfKeyRequired: 'Name des Schlüssels: {{key}} erforderlich', - valueOfVarRequired: '{{key}} Wert darf nicht leer sein', - queryRequired: 'Anfragetext ist erforderlich.', - waitForResponse: - 'Bitte warten Sie auf die Antwort auf die vorherige Nachricht, um abzuschließen.', - waitForBatchResponse: - 'Bitte warten Sie auf die Antwort auf die Stapelaufgabe, um abzuschließen.', - notSelectModel: 'Bitte wählen Sie ein Modell', - waitForImgUpload: 'Bitte warten Sie, bis das Bild hochgeladen ist', - }, - chatSubTitle: 'Anweisungen', - completionSubTitle: 'Vor-Prompt', - promptTip: - 'Prompts leiten KI-Antworten mit Anweisungen und Einschränkungen. Fügen Sie Variablen wie {{input}} ein. Dieses Prompt wird den Benutzern nicht angezeigt.', - formattingChangedTitle: 'Formatierung geändert', - formattingChangedText: - 'Die Änderung der Formatierung wird den Debug-Bereich zurücksetzen, sind Sie sicher?', - variableTitle: 'Variablen', - variableTip: - 'Benutzer füllen Variablen in einem Formular aus, automatisches Ersetzen von Variablen im Prompt.', - notSetVar: 'Variablen ermöglichen es Benutzern, Aufforderungswörter oder Eröffnungsbemerkungen einzuführen, wenn sie Formulare ausfüllen. Sie könnten versuchen, "{{input}}" im Prompt einzugeben.', - autoAddVar: 'Im Vor-Prompt referenzierte undefinierte Variablen, möchten Sie sie im Benutzereingabeformular hinzufügen?', - variableTable: { - key: 'Variablenschlüssel', - name: 'Name des Benutzereingabefelds', - optional: 'Optional', - type: 'Eingabetyp', - action: 'Aktionen', - typeString: 'String', - typeSelect: 'Auswählen', - }, - varKeyError: { - canNoBeEmpty: '{{key}} ist erforderlich', - tooLong: '{{key}} zu lang. Darf nicht länger als 30 Zeichen sein', - notValid: '{{key}} ist ungültig. Darf nur Buchstaben, Zahlen und Unterstriche enthalten', - notStartWithNumber: '{{key}} darf nicht mit einer Zahl beginnen', - keyAlreadyExists: '{{key}} existiert bereits', - }, - otherError: { - promptNoBeEmpty: 'Prompt darf nicht leer sein', - historyNoBeEmpty: 'Konversationsverlauf muss im Prompt gesetzt sein', - queryNoBeEmpty: 'Anfrage muss im Prompt gesetzt sein', - }, - variableConfig: { - modalTitle: 'Feldeinstellungen', - description: 'Einstellung für Variable {{varName}}', - fieldType: 'Feldtyp', - string: 'Kurztext', - paragraph: 'Absatz', - select: 'Auswählen', - notSet: 'Nicht gesetzt, versuchen Sie, {{input}} im Vor-Prompt zu tippen', - stringTitle: 'Formular-Textfeldoptionen', - maxLength: 'Maximale Länge', - options: 'Optionen', - addOption: 'Option hinzufügen', - apiBasedVar: 'API-basierte Variable', - }, - vision: { - name: 'Vision', - description: 'Vision zu aktivieren ermöglicht es dem Modell, Bilder aufzunehmen und Fragen dazu zu beantworten.', - settings: 'Einstellungen', - visionSettings: { - title: 'Vision-Einstellungen', - resolution: 'Auflösung', - resolutionTooltip: `Niedrige Auflösung ermöglicht es dem Modell, eine Bildversion mit niedriger Auflösung von 512 x 512 zu erhalten und das Bild mit einem Budget von 65 Tokens darzustellen. Dies ermöglicht schnellere Antworten des API und verbraucht weniger Eingabetokens für Anwendungsfälle, die kein hohes Detail benötigen. - \n - Hohe Auflösung ermöglicht zunächst, dass das Modell das Bild mit niedriger Auflösung sieht und dann detaillierte Ausschnitte von Eingabebildern als 512px Quadrate basierend auf der Größe des Eingabebildes erstellt. Jeder der detaillierten Ausschnitte verwendet das doppelte Token-Budget für insgesamt 129 Tokens.`, - high: 'Hoch', - low: 'Niedrig', - uploadMethod: 'Upload-Methode', - both: 'Beides', - localUpload: 'Lokaler Upload', - url: 'URL', - uploadLimit: 'Upload-Limit', - }, - }, - voice: { - name: 'Stimme', - defaultDisplay: 'Standardstimme', - description: 'Text-zu-Sprache-Stimmeinstellungen', - settings: 'Einstellungen', - voiceSettings: { - title: 'Stimmeinstellungen', - language: 'Sprache', - resolutionTooltip: 'Text-zu-Sprache unterstützte Sprache.', - voice: 'Stimme', - }, - }, - openingStatement: { - title: 'Gesprächseröffner', - add: 'Hinzufügen', - writeOpener: 'Eröffnung schreiben', - placeholder: 'Schreiben Sie hier Ihre Eröffnungsnachricht, Sie können Variablen verwenden, versuchen Sie {{Variable}} zu tippen.', - openingQuestion: 'Eröffnungsfragen', - noDataPlaceHolder: - 'Den Dialog mit dem Benutzer zu beginnen, kann helfen, in konversationellen Anwendungen eine engere Verbindung mit ihnen herzustellen.', - varTip: 'Sie können Variablen verwenden, versuchen Sie {{Variable}} zu tippen', - tooShort: 'Für die Erzeugung von Eröffnungsbemerkungen für das Gespräch werden mindestens 20 Wörter des Anfangsprompts benötigt.', - notIncludeKey: 'Das Anfangsprompt enthält nicht die Variable: {{key}}. Bitte fügen Sie sie dem Anfangsprompt hinzu.', - }, - modelConfig: { - model: 'Modell', - setTone: 'Ton der Antworten festlegen', - title: 'Modell und Parameter', - modeType: { - chat: 'Chat', - completion: 'Vollständig', - }, - }, - inputs: { - title: 'Debug und Vorschau', - noPrompt: 'Versuchen Sie, etwas Prompt im Vor-Prompt-Eingabefeld zu schreiben', - userInputField: 'Benutzereingabefeld', - noVar: 'Füllen Sie den Wert der Variable aus, der bei jedem Start einer neuen Sitzung automatisch im Prompt ersetzt wird.', - chatVarTip: - 'Füllen Sie den Wert der Variable aus, der bei jedem Start einer neuen Sitzung automatisch im Prompt ersetzt wird', - completionVarTip: - 'Füllen Sie den Wert der Variable aus, der bei jeder Einreichung einer Frage automatisch in den Prompt-Wörtern ersetzt wird.', - previewTitle: 'Prompt-Vorschau', - queryTitle: 'Anfrageinhalt', - queryPlaceholder: 'Bitte geben Sie den Anfragetext ein.', - run: 'AUSFÜHREN', - }, - result: 'Ausgabetext', - datasetConfig: { - settingTitle: 'Abfragen-Einstellungen', - retrieveOneWay: { - title: 'N-zu-1-Abfrage', - description: 'Basierend auf Benutzerabsicht und Beschreibungen des Wissens wählt der Agent autonom das beste Wissen für die Abfrage aus. Am besten für Anwendungen mit deutlichen, begrenzten Wissensgebieten.', - }, - retrieveMultiWay: { - title: 'Mehrwegabfrage', - description: 'Basierend auf Benutzerabsicht werden Abfragen über alle Wissensbereiche hinweg durchgeführt, relevante Texte aus Mehrfachquellen abgerufen und die besten Ergebnisse, die der Benutzerabfrage entsprechen, nach einer Neubewertung ausgewählt. Konfiguration des Rerank-Modell-APIs erforderlich.', - }, - rerankModelRequired: 'Rerank-Modell erforderlich', - params: 'Parameter', - top_k: 'Top K', - top_kTip: 'Wird verwendet, um Abschnitte zu filtern, die am ähnlichsten zu Benutzerfragen sind. Das System wird auch dynamisch den Wert von Top K anpassen, entsprechend max_tokens des ausgewählten Modells.', - score_threshold: 'Schwellenwert', - score_thresholdTip: 'Wird verwendet, um den Ähnlichkeitsschwellenwert für die Abschnittsfilterung einzustellen.', - retrieveChangeTip: 'Das Ändern des Indexmodus und des Abfragemodus kann Anwendungen beeinflussen, die mit diesem Wissen verbunden sind.', - }, - debugAsSingleModel: 'Als Einzelmodell debuggen', - debugAsMultipleModel: 'Als Mehrfachmodelle debuggen', - duplicateModel: 'Duplizieren', - publishAs: 'Veröffentlichen als', - assistantType: { - name: 'Assistententyp', - chatAssistant: { - name: 'Basisassistent', - description: 'Erstellen eines chatbasierten Assistenten mit einem Großsprachmodell', - }, - agentAssistant: { - name: 'Agentenassistent', - description: 'Erstellen eines intelligenten Agenten, der autonom Werkzeuge wählen kann, um Aufgaben zu erfüllen', - }, - }, - agent: { - agentMode: 'Agentenmodus', - agentModeDes: 'Den Typ des Inferenzmodus für den Agenten festlegen', - agentModeType: { - ReACT: 'ReAct', - functionCall: 'Funktionsaufruf', - }, - setting: { - name: 'Agenten-Einstellungen', - description: 'Agentenassistenten-Einstellungen ermöglichen die Festlegung des Agentenmodus und erweiterte Funktionen wie integrierte Prompts, nur verfügbar im Agententyp.', - maximumIterations: { - name: 'Maximale Iterationen', - description: 'Begrenzt die Anzahl der Iterationen, die ein Agentenassistent ausführen kann', - }, - }, - buildInPrompt: 'Eingebautes Prompt', - firstPrompt: 'Erstes Prompt', - nextIteration: 'Nächste Iteration', - promptPlaceholder: 'Schreiben Sie hier Ihr Prompt', - tools: { - name: 'Werkzeuge', - description: 'Die Verwendung von Werkzeugen kann die Fähigkeiten von LLM erweitern, z.B. das Internet durchsuchen oder wissenschaftliche Berechnungen durchführen', - enabled: 'Aktiviert', - }, - }, -} - -export default translation +const translation = { + pageTitle: { + line1: 'PROMPT', + line2: 'Engineering', + }, + orchestrate: 'Orchestrieren', + promptMode: { + simple: 'Wechseln Sie in den Expertenmodus, um das gesamte PROMPT zu bearbeiten', + advanced: 'Expertenmodus', + switchBack: 'Zurückwechseln', + advancedWarning: { + title: 'Sie haben in den Expertenmodus gewechselt, und sobald Sie das PROMPT ändern, können Sie NICHT zum Basis-Modus zurückkehren.', + description: 'Im Expertenmodus können Sie das gesamte PROMPT bearbeiten.', + learnMore: 'Mehr erfahren', + ok: 'OK', + }, + operation: { + addMessage: 'Nachricht hinzufügen', + }, + contextMissing: 'Komponente fehlt, die Wirksamkeit des Prompts könnte schlecht sein.', + }, + operation: { + applyConfig: 'Veröffentlichen', + resetConfig: 'Zurücksetzen', + debugConfig: 'Debuggen', + addFeature: 'Funktion hinzufügen', + automatic: 'Generieren', + stopResponding: 'Antworten stoppen', + agree: 'gefällt mir', + disagree: 'gefällt mir nicht', + cancelAgree: 'Gefällt mir zurücknehmen', + cancelDisagree: 'Gefällt mir nicht zurücknehmen', + userAction: 'Benutzer ', + }, + notSetAPIKey: { + title: 'LLM-Anbieterschlüssel wurde nicht festgelegt', + trailFinished: 'Testversion beendet', + description: 'Der LLM-Anbieterschlüssel wurde nicht festgelegt und muss vor dem Debuggen festgelegt werden.', + settingBtn: 'Zu den Einstellungen gehen', + }, + trailUseGPT4Info: { + title: 'Unterstützt derzeit kein gpt-4', + description: 'Um gpt-4 zu verwenden, bitte API-Schlüssel festlegen.', + }, + feature: { + groupChat: { + title: 'Chatverbesserung', + description: 'Voreinstellungen für Konversationen zu Apps hinzufügen kann die Benutzererfahrung verbessern.', + }, + groupExperience: { + title: 'Erfahrungsverbesserung', + }, + conversationOpener: { + title: 'Gesprächseröffnungen', + description: 'In einer Chat-App wird der erste Satz, den die KI aktiv an den Benutzer richtet, üblicherweise als Begrüßung verwendet.', + }, + suggestedQuestionsAfterAnswer: { + title: 'Nachfolgefragen', + description: 'Das Einrichten von Vorschlägen für nächste Fragen kann den Chat für Benutzer verbessern.', + resDes: '3 Vorschläge für die nächste Benutzerfrage.', + tryToAsk: 'Versuchen Sie zu fragen', + }, + moreLikeThis: { + title: 'Mehr davon', + description: 'Mehrere Texte gleichzeitig generieren und dann bearbeiten und weiter generieren', + generateNumTip: 'Anzahl der generierten Texte pro Durchgang', + tip: 'Die Verwendung dieser Funktion verursacht zusätzliche Token-Kosten', + }, + speechToText: { + title: 'Sprache zu Text', + description: 'Einmal aktiviert, können Sie Spracheingabe verwenden.', + resDes: 'Spracheingabe ist aktiviert', + }, + textToSpeech: { + title: 'Text zu Sprache', + description: 'Einmal aktiviert, kann Text in Sprache umgewandelt werden.', + resDes: 'Text zu Audio ist aktiviert', + }, + citation: { + title: 'Zitate und Urheberangaben', + description: 'Einmal aktiviert, zeigen Sie das Quelldokument und den zugeordneten Abschnitt des generierten Inhalts an.', + resDes: 'Zitate und Urheberangaben sind aktiviert', + }, + annotation: { + title: 'Annotation Antwort', + description: 'Sie können manuell hochwertige Antworten zum Cache hinzufügen für bevorzugte Übereinstimmung mit ähnlichen Benutzerfragen.', + resDes: 'Annotationsantwort ist aktiviert', + scoreThreshold: { + title: 'Schwellenwert', + description: 'Wird verwendet, um den Ähnlichkeitsschwellenwert für die Annotation Antwort einzustellen.', + easyMatch: 'Einfache Übereinstimmung', + accurateMatch: 'Genaue Übereinstimmung', + }, + matchVariable: { + title: 'Übereinstimmungsvariable', + choosePlaceholder: 'Wählen Sie Übereinstimmungsvariable', + }, + cacheManagement: 'Annotationen', + cached: 'Annotiert', + remove: 'Entfernen', + removeConfirm: 'Diese Annotation löschen?', + add: 'Annotation hinzufügen', + edit: 'Annotation bearbeiten', + }, + dataSet: { + title: 'Kontext', + noData: 'Sie können Wissen als Kontext importieren', + words: 'Wörter', + textBlocks: 'Textblöcke', + selectTitle: 'Wählen Sie Referenzwissen', + selected: 'Wissen ausgewählt', + noDataSet: 'Kein Wissen gefunden', + toCreate: 'Erstellen gehen', + notSupportSelectMulti: 'Unterstützt derzeit nur ein Wissen', + queryVariable: { + title: 'Abfragevariable', + tip: 'Diese Variable wird als Eingabe für die Kontextabfrage verwendet, um kontextbezogene Informationen in Bezug auf die Eingabe dieser Variable zu erhalten.', + choosePlaceholder: 'Wählen Sie Abfragevariable', + noVar: 'Keine Variablen', + noVarTip: 'Bitte erstellen Sie eine Variable im Variablenbereich', + unableToQueryDataSet: 'Konnte das Wissen nicht abfragen', + unableToQueryDataSetTip: 'Konnte das Wissen nicht erfolgreich abfragen, bitte wählen Sie eine Kontextabfragevariable im Kontextbereich.', + ok: 'OK', + contextVarNotEmpty: 'Kontextabfragevariable darf nicht leer sein', + deleteContextVarTitle: 'Variable „{{varName}}“ löschen?', + deleteContextVarTip: 'Diese Variable wurde als Kontextabfragevariable festgelegt und deren Entfernung wird die normale Verwendung des Wissens beeinträchtigen. Wenn Sie sie trotzdem löschen müssen, wählen Sie sie bitte im Kontextbereich erneut.', + }, + }, + tools: { + title: 'Werkzeuge', + tips: 'Werkzeuge bieten eine standardisierte API-Aufrufmethode, die Benutzereingaben oder Variablen als Anfrageparameter für die Abfrage externer Daten als Kontext verwendet.', + toolsInUse: '{{count}} Werkzeuge in Verwendung', + modal: { + title: 'Werkzeug', + toolType: { + title: 'Werkzeugtyp', + placeholder: 'Bitte wählen Sie den Werkzeugtyp', + }, + name: { + title: 'Name', + placeholder: 'Bitte geben Sie den Namen ein', + }, + variableName: { + title: 'Variablenname', + placeholder: 'Bitte geben Sie den Variablennamen ein', + }, + }, + }, + conversationHistory: { + title: 'Konversationsverlauf', + description: 'Präfixnamen für Konversationsrollen festlegen', + tip: 'Der Konversationsverlauf ist nicht aktiviert, bitte fügen Sie <histories> im Prompt oben ein.', + learnMore: 'Mehr erfahren', + editModal: { + title: 'Konversationsrollennamen bearbeiten', + userPrefix: 'Benutzerpräfix', + assistantPrefix: 'Assistentenpräfix', + }, + }, + toolbox: { + title: 'WERKZEUGKASTEN', + }, + moderation: { + title: 'Inhaltsmoderation', + description: 'Sichern Sie die Ausgabe des Modells durch Verwendung der Moderations-API oder durch Pflege einer Liste sensibler Wörter.', + allEnabled: 'INHALT von EINGABE/AUSGABE aktiviert', + inputEnabled: 'INHALT von EINGABE aktiviert', + outputEnabled: 'INHALT von AUSGABE aktiviert', + modal: { + title: 'Einstellungen zur Inhaltsmoderation', + provider: { + title: 'Anbieter', + openai: 'OpenAI-Moderation', + openaiTip: { + prefix: 'OpenAI-Moderation erfordert einen konfigurierten OpenAI-API-Schlüssel in den ', + suffix: '.', + }, + keywords: 'Schlüsselwörter', + }, + keywords: { + tip: 'Jeweils eine pro Zeile, getrennt durch Zeilenumbrüche. Bis zu 100 Zeichen pro Zeile.', + placeholder: 'Jeweils eine pro Zeile, getrennt durch Zeilenumbrüche', + line: 'Zeile', + }, + content: { + input: 'INHALT der EINGABE moderieren', + output: 'INHALT der AUSGABE moderieren', + preset: 'Voreingestellte Antworten', + placeholder: 'Inhalt der voreingestellten Antworten hier', + condition: 'Moderation von INHALT der EINGABE und AUSGABE mindestens eine aktiviert', + fromApi: 'Voreingestellte Antworten werden durch API zurückgegeben', + errorMessage: 'Voreingestellte Antworten dürfen nicht leer sein', + supportMarkdown: 'Markdown unterstützt', + }, + openaiNotConfig: { + before: 'OpenAI-Moderation erfordert einen konfigurierten OpenAI-API-Schlüssel in den', + after: '', + }, + }, + }, + }, + resetConfig: { + title: 'Zurücksetzen bestätigen?', + message: + 'Zurücksetzen verwirft Änderungen und stellt die zuletzt veröffentlichte Konfiguration wieder her.', + }, + errorMessage: { + nameOfKeyRequired: 'Name des Schlüssels: {{key}} erforderlich', + valueOfVarRequired: '{{key}} Wert darf nicht leer sein', + queryRequired: 'Anfragetext ist erforderlich.', + waitForResponse: + 'Bitte warten Sie auf die Antwort auf die vorherige Nachricht, um abzuschließen.', + waitForBatchResponse: + 'Bitte warten Sie auf die Antwort auf die Stapelaufgabe, um abzuschließen.', + notSelectModel: 'Bitte wählen Sie ein Modell', + waitForImgUpload: 'Bitte warten Sie, bis das Bild hochgeladen ist', + }, + chatSubTitle: 'Anweisungen', + completionSubTitle: 'Vor-Prompt', + promptTip: + 'Prompts leiten KI-Antworten mit Anweisungen und Einschränkungen. Fügen Sie Variablen wie {{input}} ein. Dieses Prompt wird den Benutzern nicht angezeigt.', + formattingChangedTitle: 'Formatierung geändert', + formattingChangedText: + 'Die Änderung der Formatierung wird den Debug-Bereich zurücksetzen, sind Sie sicher?', + variableTitle: 'Variablen', + variableTip: + 'Benutzer füllen Variablen in einem Formular aus, automatisches Ersetzen von Variablen im Prompt.', + notSetVar: 'Variablen ermöglichen es Benutzern, Aufforderungswörter oder Eröffnungsbemerkungen einzuführen, wenn sie Formulare ausfüllen. Sie könnten versuchen, "{{input}}" im Prompt einzugeben.', + autoAddVar: 'Im Vor-Prompt referenzierte undefinierte Variablen, möchten Sie sie im Benutzereingabeformular hinzufügen?', + variableTable: { + key: 'Variablenschlüssel', + name: 'Name des Benutzereingabefelds', + optional: 'Optional', + type: 'Eingabetyp', + action: 'Aktionen', + typeString: 'String', + typeSelect: 'Auswählen', + }, + varKeyError: { + canNoBeEmpty: '{{key}} ist erforderlich', + tooLong: '{{key}} zu lang. Darf nicht länger als 30 Zeichen sein', + notValid: '{{key}} ist ungültig. Darf nur Buchstaben, Zahlen und Unterstriche enthalten', + notStartWithNumber: '{{key}} darf nicht mit einer Zahl beginnen', + keyAlreadyExists: '{{key}} existiert bereits', + }, + otherError: { + promptNoBeEmpty: 'Prompt darf nicht leer sein', + historyNoBeEmpty: 'Konversationsverlauf muss im Prompt gesetzt sein', + queryNoBeEmpty: 'Anfrage muss im Prompt gesetzt sein', + }, + variableConfig: { + modalTitle: 'Feldeinstellungen', + description: 'Einstellung für Variable {{varName}}', + fieldType: 'Feldtyp', + string: 'Kurztext', + paragraph: 'Absatz', + select: 'Auswählen', + notSet: 'Nicht gesetzt, versuchen Sie, {{input}} im Vor-Prompt zu tippen', + stringTitle: 'Formular-Textfeldoptionen', + maxLength: 'Maximale Länge', + options: 'Optionen', + addOption: 'Option hinzufügen', + apiBasedVar: 'API-basierte Variable', + }, + vision: { + name: 'Vision', + description: 'Vision zu aktivieren ermöglicht es dem Modell, Bilder aufzunehmen und Fragen dazu zu beantworten.', + settings: 'Einstellungen', + visionSettings: { + title: 'Vision-Einstellungen', + resolution: 'Auflösung', + resolutionTooltip: `Niedrige Auflösung ermöglicht es dem Modell, eine Bildversion mit niedriger Auflösung von 512 x 512 zu erhalten und das Bild mit einem Budget von 65 Tokens darzustellen. Dies ermöglicht schnellere Antworten des API und verbraucht weniger Eingabetokens für Anwendungsfälle, die kein hohes Detail benötigen. + \n + Hohe Auflösung ermöglicht zunächst, dass das Modell das Bild mit niedriger Auflösung sieht und dann detaillierte Ausschnitte von Eingabebildern als 512px Quadrate basierend auf der Größe des Eingabebildes erstellt. Jeder der detaillierten Ausschnitte verwendet das doppelte Token-Budget für insgesamt 129 Tokens.`, + high: 'Hoch', + low: 'Niedrig', + uploadMethod: 'Upload-Methode', + both: 'Beides', + localUpload: 'Lokaler Upload', + url: 'URL', + uploadLimit: 'Upload-Limit', + }, + }, + voice: { + name: 'Stimme', + defaultDisplay: 'Standardstimme', + description: 'Text-zu-Sprache-Stimmeinstellungen', + settings: 'Einstellungen', + voiceSettings: { + title: 'Stimmeinstellungen', + language: 'Sprache', + resolutionTooltip: 'Text-zu-Sprache unterstützte Sprache.', + voice: 'Stimme', + }, + }, + openingStatement: { + title: 'Gesprächseröffner', + add: 'Hinzufügen', + writeOpener: 'Eröffnung schreiben', + placeholder: 'Schreiben Sie hier Ihre Eröffnungsnachricht, Sie können Variablen verwenden, versuchen Sie {{Variable}} zu tippen.', + openingQuestion: 'Eröffnungsfragen', + noDataPlaceHolder: + 'Den Dialog mit dem Benutzer zu beginnen, kann helfen, in konversationellen Anwendungen eine engere Verbindung mit ihnen herzustellen.', + varTip: 'Sie können Variablen verwenden, versuchen Sie {{Variable}} zu tippen', + tooShort: 'Für die Erzeugung von Eröffnungsbemerkungen für das Gespräch werden mindestens 20 Wörter des Anfangsprompts benötigt.', + notIncludeKey: 'Das Anfangsprompt enthält nicht die Variable: {{key}}. Bitte fügen Sie sie dem Anfangsprompt hinzu.', + }, + modelConfig: { + model: 'Modell', + setTone: 'Ton der Antworten festlegen', + title: 'Modell und Parameter', + modeType: { + chat: 'Chat', + completion: 'Vollständig', + }, + }, + inputs: { + title: 'Debug und Vorschau', + noPrompt: 'Versuchen Sie, etwas Prompt im Vor-Prompt-Eingabefeld zu schreiben', + userInputField: 'Benutzereingabefeld', + noVar: 'Füllen Sie den Wert der Variable aus, der bei jedem Start einer neuen Sitzung automatisch im Prompt ersetzt wird.', + chatVarTip: + 'Füllen Sie den Wert der Variable aus, der bei jedem Start einer neuen Sitzung automatisch im Prompt ersetzt wird', + completionVarTip: + 'Füllen Sie den Wert der Variable aus, der bei jeder Einreichung einer Frage automatisch in den Prompt-Wörtern ersetzt wird.', + previewTitle: 'Prompt-Vorschau', + queryTitle: 'Anfrageinhalt', + queryPlaceholder: 'Bitte geben Sie den Anfragetext ein.', + run: 'AUSFÜHREN', + }, + result: 'Ausgabetext', + datasetConfig: { + settingTitle: 'Abfragen-Einstellungen', + retrieveOneWay: { + title: 'N-zu-1-Abfrage', + description: 'Basierend auf Benutzerabsicht und Beschreibungen des Wissens wählt der Agent autonom das beste Wissen für die Abfrage aus. Am besten für Anwendungen mit deutlichen, begrenzten Wissensgebieten.', + }, + retrieveMultiWay: { + title: 'Mehrwegabfrage', + description: 'Basierend auf Benutzerabsicht werden Abfragen über alle Wissensbereiche hinweg durchgeführt, relevante Texte aus Mehrfachquellen abgerufen und die besten Ergebnisse, die der Benutzerabfrage entsprechen, nach einer Neubewertung ausgewählt. Konfiguration des Rerank-Modell-APIs erforderlich.', + }, + rerankModelRequired: 'Rerank-Modell erforderlich', + params: 'Parameter', + top_k: 'Top K', + top_kTip: 'Wird verwendet, um Abschnitte zu filtern, die am ähnlichsten zu Benutzerfragen sind. Das System wird auch dynamisch den Wert von Top K anpassen, entsprechend max_tokens des ausgewählten Modells.', + score_threshold: 'Schwellenwert', + score_thresholdTip: 'Wird verwendet, um den Ähnlichkeitsschwellenwert für die Abschnittsfilterung einzustellen.', + retrieveChangeTip: 'Das Ändern des Indexmodus und des Abfragemodus kann Anwendungen beeinflussen, die mit diesem Wissen verbunden sind.', + }, + debugAsSingleModel: 'Als Einzelmodell debuggen', + debugAsMultipleModel: 'Als Mehrfachmodelle debuggen', + duplicateModel: 'Duplizieren', + publishAs: 'Veröffentlichen als', + assistantType: { + name: 'Assistententyp', + chatAssistant: { + name: 'Basisassistent', + description: 'Erstellen eines chatbasierten Assistenten mit einem Großsprachmodell', + }, + agentAssistant: { + name: 'Agentenassistent', + description: 'Erstellen eines intelligenten Agenten, der autonom Werkzeuge wählen kann, um Aufgaben zu erfüllen', + }, + }, + agent: { + agentMode: 'Agentenmodus', + agentModeDes: 'Den Typ des Inferenzmodus für den Agenten festlegen', + agentModeType: { + ReACT: 'ReAct', + functionCall: 'Funktionsaufruf', + }, + setting: { + name: 'Agenten-Einstellungen', + description: 'Agentenassistenten-Einstellungen ermöglichen die Festlegung des Agentenmodus und erweiterte Funktionen wie integrierte Prompts, nur verfügbar im Agententyp.', + maximumIterations: { + name: 'Maximale Iterationen', + description: 'Begrenzt die Anzahl der Iterationen, die ein Agentenassistent ausführen kann', + }, + }, + buildInPrompt: 'Eingebautes Prompt', + firstPrompt: 'Erstes Prompt', + nextIteration: 'Nächste Iteration', + promptPlaceholder: 'Schreiben Sie hier Ihr Prompt', + tools: { + name: 'Werkzeuge', + description: 'Die Verwendung von Werkzeugen kann die Fähigkeiten von LLM erweitern, z.B. das Internet durchsuchen oder wissenschaftliche Berechnungen durchführen', + enabled: 'Aktiviert', + }, + }, +} + +export default translation diff --git a/web/i18n/de-DE/app-overview.ts b/web/i18n/de-DE/app-overview.ts index fea278dad7..f8e934a117 100644 --- a/web/i18n/de-DE/app-overview.ts +++ b/web/i18n/de-DE/app-overview.ts @@ -30,26 +30,26 @@ const translation = { overview: { title: 'Übersicht', appInfo: { - explanation: 'Einsatzbereite AI-WebApp', + explanation: 'Einsatzbereite AI-web app', accessibleAddress: 'Öffentliche URL', preview: 'Vorschau', regenerate: 'Regenerieren', regenerateNotice: 'Möchten Sie die öffentliche URL neu generieren?', - preUseReminder: 'Bitte aktivieren Sie WebApp, bevor Sie fortfahren.', + preUseReminder: 'Bitte aktivieren Sie web app, bevor Sie fortfahren.', settings: { entry: 'Einstellungen', - title: 'WebApp-Einstellungen', - webName: 'WebApp-Name', - webDesc: 'WebApp-Beschreibung', + title: 'web app Einstellungen', + webName: 'web app Name', + webDesc: 'web app Beschreibung', webDescTip: 'Dieser Text wird auf der Clientseite angezeigt und bietet grundlegende Anleitungen zur Verwendung der Anwendung', - webDescPlaceholder: 'Geben Sie die Beschreibung der WebApp ein', + webDescPlaceholder: 'Geben Sie die Beschreibung der web app ein', language: 'Sprache', workflow: { title: 'Workflow-Schritte', show: 'Anzeigen', hide: 'Verbergen', subTitle: 'Details zum Arbeitsablauf', - showDesc: 'Ein- oder Ausblenden von Workflow-Details in der WebApp', + showDesc: 'Ein- oder Ausblenden von Workflow-Details in der web app', }, chatColorTheme: 'Chat-Farbschema', chatColorThemeDesc: 'Legen Sie das Farbschema des Chatbots fest', @@ -70,10 +70,10 @@ const translation = { copyrightTooltip: 'Bitte führen Sie ein Upgrade auf den Professional-Plan oder höher durch', }, sso: { - title: 'WebApp-SSO', - description: 'Alle Benutzer müssen sich mit SSO anmelden, bevor sie WebApp verwenden können', + title: 'web app SSO', + description: 'Alle Benutzer müssen sich mit SSO anmelden, bevor sie web app verwenden können', label: 'SSO-Authentifizierung', - tooltip: 'Wenden Sie sich an den Administrator, um WebApp-SSO zu aktivieren', + tooltip: 'Wenden Sie sich an den Administrator, um web app SSO zu aktivieren', }, modalTip: 'Einstellungen für clientseitige Web-Apps.', }, @@ -95,7 +95,7 @@ const translation = { customize: { way: 'Art', entry: 'Anpassen', - title: 'AI-WebApp anpassen', + title: 'AI-web app anpassen', explanation: 'Sie können das Frontend der Web-App an Ihre Szenarien und Stilbedürfnisse anpassen.', way1: { name: 'Forken Sie den Client-Code, ändern Sie ihn und deployen Sie ihn auf Vercel (empfohlen)', diff --git a/web/i18n/de-DE/app.ts b/web/i18n/de-DE/app.ts index 25fddf7a91..1373dd611b 100644 --- a/web/i18n/de-DE/app.ts +++ b/web/i18n/de-DE/app.ts @@ -77,20 +77,20 @@ const translation = { learnMore: 'Weitere Informationen', optional: 'Wahlfrei', noTemplateFound: 'Keine Vorlagen gefunden', - workflowUserDescription: 'Workflow-Orchestrierung für Aufgaben in einer einzigen Runde wie Automatisierung und Stapelverarbeitung.', + workflowUserDescription: 'Autonome KI-Arbeitsabläufe visuell per Drag-and-Drop erstellen.', foundResults: '{{Anzahl}} Befund', chatbotShortDescription: 'LLM-basierter Chatbot mit einfacher Einrichtung', completionUserDescription: 'Erstellen Sie schnell einen KI-Assistenten für Textgenerierungsaufgaben mit einfacher Konfiguration.', noAppsFound: 'Keine Apps gefunden', - advancedShortDescription: 'Workflow für komplexe Dialoge mit mehreren Durchläufen mit Speicher', + advancedShortDescription: 'Workflow optimiert für mehrstufige Chats', forAdvanced: 'FÜR FORTGESCHRITTENE', chooseAppType: 'App-Typ auswählen', completionShortDescription: 'KI-Assistent für Textgenerierungsaufgaben', - forBeginners: 'FÜR ANFÄNGER', + forBeginners: 'Einfachere App-Typen', noIdeaTip: 'Keine Ideen? Schauen Sie sich unsere Vorlagen an', - workflowShortDescription: 'Orchestrierung für Single-Turn-Automatisierungsaufgaben', + workflowShortDescription: 'Agentischer Ablauf für intelligente Automatisierungen', noTemplateFoundTip: 'Versuchen Sie, mit verschiedenen Schlüsselwörtern zu suchen.', - advancedUserDescription: 'Workflow-Orchestrierung für komplexe Dialogaufgaben mit mehreren Runden und Speicherkapazitäten.', + advancedUserDescription: 'Workflow mit Speicherfunktionen und Chatbot-Oberfläche.', chatbotUserDescription: 'Erstellen Sie schnell einen LLM-basierten Chatbot mit einfacher Konfiguration. Sie können später zu Chatflow wechseln.', foundResult: '{{Anzahl}} Ergebnis', agentUserDescription: 'Ein intelligenter Agent, der in der Lage ist, iteratives Denken zu führen und autonome Werkzeuge zu verwenden, um Aufgabenziele zu erreichen.', @@ -161,11 +161,15 @@ const translation = { description: 'Opik ist eine Open-Source-Plattform zum Bewerten, Testen und Überwachen von LLM-Anwendungen.', title: 'Opik', }, + weave: { + title: 'Weben', + description: 'Weave ist eine Open-Source-Plattform zur Bewertung, Testung und Überwachung von LLM-Anwendungen.', + }, }, answerIcon: { - descriptionInExplore: 'Gibt an, ob das WebApp-Symbol zum Ersetzen 🤖 in Explore verwendet werden soll', - title: 'Verwenden Sie das WebApp-Symbol, um es zu ersetzen 🤖', - description: 'Gibt an, ob das WebApp-Symbol zum Ersetzen 🤖 in der freigegebenen Anwendung verwendet werden soll', + descriptionInExplore: 'Gibt an, ob das web app Symbol zum Ersetzen 🤖 in Explore verwendet werden soll', + title: 'Verwenden Sie das web app Symbol, um es zu ersetzen 🤖', + description: 'Gibt an, ob das web app Symbol zum Ersetzen 🤖 in der freigegebenen Anwendung verwendet werden soll', }, importFromDSLUrlPlaceholder: 'DSL-Link hier einfügen', duplicate: 'Duplikat', @@ -201,6 +205,54 @@ const translation = { label: 'APP', noParams: 'Keine Parameter erforderlich', }, + structOutput: { + required: 'Erforderlich', + structured: 'Strukturiert', + structuredTip: 'Strukturierte Ausgaben ist eine Funktion, die sicherstellt, dass das Modell immer Antworten generiert, die Ihrem bereitgestellten JSON-Schema entsprechen.', + modelNotSupportedTip: 'Das aktuelle Modell unterstützt diese Funktion nicht und wird automatisch auf Eingabeinjektion heruntergestuft.', + modelNotSupported: 'Modell nicht unterstützt', + configure: 'Konfigurieren', + notConfiguredTip: 'Die strukturierte Ausgabe wurde bisher nicht konfiguriert.', + moreFillTip: 'Maximal 10 Ebenen der Verschachtelung anzeigen', + LLMResponse: 'LLM-Antwort', + }, + accessItemsDescription: { + anyone: 'Jeder kann auf die Webanwendung zugreifen.', + specific: 'Nur bestimmte Gruppen oder Mitglieder können auf die Webanwendung zugreifen.', + organization: 'Jeder in der Organisation kann auf die Webanwendung zugreifen.', + external: 'Nur authentifizierte externe Benutzer können auf die Webanwendung zugreifen.', + }, + accessControlDialog: { + accessItems: { + anyone: 'Jeder mit dem Link', + specific: 'Spezifische Gruppen oder Mitglieder', + organization: 'Nur Mitglieder innerhalb des Unternehmens', + external: 'Authentifizierte externe Benutzer', + }, + operateGroupAndMember: { + searchPlaceholder: 'Gruppen und Mitglieder suchen', + allMembers: 'Alle Mitglieder', + expand: 'Erweitern', + noResult: 'Kein Ergebnis', + }, + title: 'Zugriffskontrolle für Webanwendungen', + description: 'Webanwendungszugriffsberechtigungen festlegen', + accessLabel: 'Wer hat Zugang', + groups_one: '{{count}} GRUPPE', + members_one: '{{count}} MITGLIED', + members_other: '{{count}} MITGLIEDER', + noGroupsOrMembers: 'Keine Gruppen oder Mitglieder ausgewählt', + webAppSSONotEnabledTip: 'Bitte kontaktieren Sie den Unternehmensadministrator, um die Authentifizierungsmethode der Webanwendung zu konfigurieren.', + updateSuccess: 'Erfolgreich aktualisiert', + groups_other: '{{count}} GRUPPEN', + }, + publishApp: { + title: 'Wer kann auf die Webanwendung zugreifen?', + notSetDesc: 'Derzeit kann niemand auf die Webanwendung zugreifen. Bitte setzen Sie die Berechtigungen.', + notSet: 'Nicht festgelegt', + }, + accessControl: 'Zugriffskontrolle für Webanwendungen', + noAccessPermission: 'Keine Berechtigung zum Zugriff auf die Webanwendung', } export default translation diff --git a/web/i18n/de-DE/billing.ts b/web/i18n/de-DE/billing.ts index 7eae078ad2..f0a0f1990a 100644 --- a/web/i18n/de-DE/billing.ts +++ b/web/i18n/de-DE/billing.ts @@ -69,6 +69,7 @@ const translation = { messageRequest: { title: 'Nachrichtenguthaben', tooltip: 'Nachrichtenaufrufkontingente für verschiedene Tarife unter Verwendung von OpenAI-Modellen (außer gpt4).Nachrichten über dem Limit verwenden Ihren OpenAI-API-Schlüssel.', + titlePerMonth: '{{count,number}} Nachrichten/Monat', }, annotatedResponse: { title: 'Kontingentgrenzen für Annotationen', @@ -77,27 +78,94 @@ const translation = { ragAPIRequestTooltip: 'Bezieht sich auf die Anzahl der API-Aufrufe, die nur die Wissensdatenbankverarbeitungsfähigkeiten von Dify aufrufen.', receiptInfo: 'Nur der Teaminhaber und der Teamadministrator können abonnieren und Abrechnungsinformationen einsehen', annotationQuota: 'Kontingent für Anmerkungen', + unlimitedApiRate: 'Keine API-Ratebeschränkung', + teamMember_other: '{{count,number}} Teammitglieder', + priceTip: 'pro Arbeitsbereich/', + teamWorkspace: '{{count,number}} Team Arbeitsplatz', + annualBilling: 'Jährliche Abrechnung', + self: 'Selbst gehostet', + freeTrialTipPrefix: 'Melden Sie sich an und erhalten Sie ein', + cloud: 'Cloud-Dienst', + apiRateLimitTooltip: 'Die API-Datenbeschränkung gilt für alle Anfragen, die über die Dify-API gemacht werden, einschließlich Textgenerierung, Chat-Konversationen, Workflow-Ausführungen und Dokumentenverarbeitung.', + getStarted: 'Loslegen', + apiRateLimitUnit: '{{count,number}}/Tag', + documentsTooltip: 'Vorgabe für die Anzahl der Dokumente, die aus der Wissensdatenquelle importiert werden.', + apiRateLimit: 'API-Datenlimit', + documents: '{{count,number}} Wissensdokumente', + comparePlanAndFeatures: 'Pläne und Funktionen vergleichen', + freeTrialTipSuffix: 'Keine Kreditkarte erforderlich', + freeTrialTip: 'kostenlose Testversion von 200 OpenAI-Anfragen.', + documentsRequestQuota: '{{count,number}}/min Wissensanforderungsratenlimit', + teamMember_one: '{{count,number}} Teammitglied', + documentsRequestQuotaTooltip: 'Gibt die Gesamtzahl der Aktionen an, die ein Arbeitsbereich pro Minute innerhalb der Wissensbasis ausführen kann, einschließlich der Erstellung, Löschung, Aktualisierung von Datensätzen, des Hochladens von Dokumenten, von Änderungen, der Archivierung und von Abfragen in der Wissensbasis. Diese Kennzahl wird verwendet, um die Leistung von Anfragen an die Wissensbasis zu bewerten. Wenn ein Sandbox-Nutzer beispielsweise in einer Minute 10 aufeinanderfolgende Testdurchläufe durchführt, wird sein Arbeitsbereich für die nächste Minute vorübergehend daran gehindert, die folgenden Aktionen auszuführen: Erstellung, Löschung, Aktualisierung von Datensätzen sowie das Hochladen oder Ändern von Dokumenten.', }, plans: { sandbox: { name: 'Sandbox', description: '200 mal GPT kostenlos testen', includesTitle: 'Beinhaltet:', + for: 'Kostenlose Testversion der Kernfunktionen', }, professional: { name: 'Professionell', description: 'Für Einzelpersonen und kleine Teams, um mehr Leistung erschwinglich freizuschalten.', includesTitle: 'Alles im kostenlosen Tarif, plus:', + for: 'Für unabhängige Entwickler/kleine Teams', }, team: { name: 'Team', description: 'Zusammenarbeiten ohne Grenzen und Top-Leistung genießen.', includesTitle: 'Alles im Professionell-Tarif, plus:', + for: 'Für mittelgroße Teams', }, enterprise: { name: 'Unternehmen', description: 'Erhalten Sie volle Fähigkeiten und Unterstützung für großangelegte, missionskritische Systeme.', includesTitle: 'Alles im Team-Tarif, plus:', + features: { + 2: 'Exklusive Unternehmensfunktionen', + 8: 'Professioneller technischer Support', + 6: 'Erweiterte Sicherheits- und Kontrollsysteme', + 4: 'SSO', + 0: 'Enterprise-Grade Skalierbare Bereitstellungslösungen', + 3: 'Mehrere Arbeitsbereiche und Unternehmensverwaltung', + 1: 'Kommerzielle Lizenzgenehmigung', + 5: 'Verhandelte SLAs durch Dify-Partner', + 7: 'Updates und Wartung von Dify offiziell', + }, + btnText: 'Vertrieb kontaktieren', + price: 'Benutzerdefiniert', + priceTip: 'Jährliche Abrechnung nur', + for: 'Für große Teams', + }, + community: { + features: { + 2: 'Entspricht der Dify Open Source Lizenz', + 1: 'Einzelner Arbeitsbereich', + 0: 'Alle Kernfunktionen wurden im öffentlichen Repository veröffentlicht.', + }, + description: 'Für Einzelbenutzer, kleine Teams oder nicht-kommerzielle Projekte', + for: 'Für Einzelbenutzer, kleine Teams oder nicht-kommerzielle Projekte', + btnText: 'Beginnen Sie mit der Gemeinschaft', + price: 'Kostenlos', + includesTitle: 'Kostenlose Funktionen:', + name: 'Gemeinschaft', + }, + premium: { + features: { + 2: 'WebApp-Logo und Branding-Anpassung', + 0: 'Selbstverwaltete Zuverlässigkeit durch verschiedene Cloud-Anbieter', + 3: 'Priorisierte E-Mail- und Chat-Unterstützung', + 1: 'Einzelner Arbeitsbereich', + }, + includesTitle: 'Alles aus der Community, plus:', + name: 'Premium', + priceTip: 'Basierend auf dem Cloud-Marktplatz', + for: 'Für mittelgroße Organisationen und Teams', + btnText: 'Jetzt Premium erhalten in', + comingSoon: 'Microsoft Azure- und Google Cloud-Support demnächst verfügbar', + description: 'Für mittelgroße Organisationen und Teams', + price: 'Skalierbar', }, }, vectorSpace: { @@ -107,12 +175,26 @@ const translation = { apps: { fullTipLine1: 'Upgraden Sie Ihren Tarif, um', fullTipLine2: 'mehr Apps zu bauen.', + contactUs: 'Kontaktieren Sie uns', + fullTip1: 'Upgrade, um mehr Apps zu erstellen', + fullTip2des: 'Es wird empfohlen, inaktive Anwendungen zu bereinigen, um Speicherplatz freizugeben, oder uns zu kontaktieren.', + fullTip1des: 'Sie haben das Limit für das Erstellen von Apps in diesem Plan erreicht.', + fullTip2: 'Limit erreicht', }, annotatedResponse: { fullTipLine1: 'Upgraden Sie Ihren Tarif, um', fullTipLine2: 'mehr Konversationen zu annotieren.', quotaTitle: 'Kontingent für Annotation-Antworten', }, + usagePage: { + buildApps: 'Apps erstellen', + annotationQuota: 'Annotierungsquote', + teamMembers: 'Teammitglieder', + documentsUploadQuota: 'Dokumenten-Upload-Quota', + vectorSpace: 'Wissensdatenbank', + vectorSpaceTooltip: 'Dokumente mit dem Hochqualitäts-Indexierungsmodus verbrauchen Ressourcen des Knowledge Data Storage. Wenn der Knowledge Data Storage die Grenze erreicht, werden keine neuen Dokumente hochgeladen.', + }, + teamMembers: 'Teammitglieder', } export default translation diff --git a/web/i18n/de-DE/common.ts b/web/i18n/de-DE/common.ts index d4ea088571..3e00cfcaed 100644 --- a/web/i18n/de-DE/common.ts +++ b/web/i18n/de-DE/common.ts @@ -54,6 +54,10 @@ const translation = { viewDetails: 'Details anzeigen', in: 'in', copied: 'Kopiert', + downloadFailed: 'Download fehlgeschlagen. Bitte versuchen Sie es später erneut.', + downloadSuccess: 'Download abgeschlossen.', + more: 'Mehr', + format: 'Format', }, placeholder: { input: 'Bitte eingeben', @@ -141,6 +145,8 @@ const translation = { newDataset: 'Wissen erstellen', tools: 'Werkzeuge', exploreMarketplace: 'Marketplace erkunden', + appDetail: 'App-Details', + account: 'Konto', }, userProfile: { settings: 'Einstellungen', @@ -153,6 +159,9 @@ const translation = { community: 'Gemeinschaft', about: 'Über', logout: 'Abmelden', + compliance: 'Einhaltung', + support: 'Unterstützung', + github: 'GitHub', }, settings: { accountGroup: 'KONTO', @@ -202,6 +211,9 @@ const translation = { feedbackLabel: 'Sagen Sie uns, warum Sie Ihr Konto gelöscht haben?', feedbackPlaceholder: 'Wahlfrei', permanentlyDeleteButton: 'Konto dauerhaft löschen', + workspaceIcon: 'Arbeitsbereichssymbol', + workspaceName: 'Arbeitsbereichsname', + editWorkspaceInfo: 'Arbeitsbereichsinformationen bearbeiten', }, members: { team: 'Team', @@ -455,7 +467,7 @@ const translation = { apiBasedExtension: { title: 'API-Erweiterungen bieten zentralisiertes API-Management und vereinfachen die Konfiguration für eine einfache Verwendung in Difys Anwendungen.', link: 'Erfahren Sie, wie Sie Ihre eigene API-Erweiterung entwickeln.', - linkUrl: 'https://docs.dify.ai/features/extension/api_based_extension', + linkUrl: 'https://docs.dify.ai/en/guides/extension/api-based-extension/README', add: 'API-Erweiterung hinzufügen', selector: { title: 'API-Erweiterung', @@ -540,9 +552,10 @@ const translation = { vectorHash: 'Vektorhash:', hitScore: 'Abrufwertung:', }, - inputPlaceholder: 'Sprechen Sie mit dem Bot', + inputPlaceholder: 'Sprechen Sie mit dem {{botName}}', thought: 'Gedanke', thinking: 'Denken...', + resend: 'Erneut senden', }, promptEditor: { placeholder: 'Schreiben Sie hier Ihr Aufforderungswort, geben Sie \'{\' ein, um eine Variable einzufügen, geben Sie \'/\' ein, um einen Aufforderungs-Inhaltsblock einzufügen', @@ -633,10 +646,31 @@ const translation = { license: { expiring: 'Läuft an einem Tag ab', expiring_plural: 'Läuft in {{count}} Tagen ab', + unlimited: 'Unbegrenzt', }, pagination: { perPage: 'Artikel pro Seite', }, + theme: { + light: 'Licht', + theme: 'Thema', + dark: 'dunkel', + auto: 'System', + }, + compliance: { + iso27001: 'ISO 27001:2022 Zertifizierung', + professionalUpgradeTooltip: 'Nur verfügbar mit einem Teamplan oder höher.', + gdpr: 'DSGVO DPA', + soc2Type2: 'SOC 2 Typ II Bericht', + soc2Type1: 'SOC 2 Typ I Bericht', + sandboxUpgradeTooltip: 'Nur verfügbar mit einem Professional- oder Teamplan.', + }, + imageInput: { + dropImageHere: 'Laden Sie Ihr Bild hierher hoch oder', + browse: 'blättern', + supportedFormats: 'Unterstützt PNG, JPG, JPEG, WEBP und GIF', + }, + you: 'Du', } export default translation diff --git a/web/i18n/de-DE/custom.ts b/web/i18n/de-DE/custom.ts index 2f4cabd67d..42001e0863 100644 --- a/web/i18n/de-DE/custom.ts +++ b/web/i18n/de-DE/custom.ts @@ -3,9 +3,11 @@ const translation = { upgradeTip: { prefix: 'Erweitere deinen Plan auf', suffix: 'um deine Marke anzupassen.', + title: 'Upgrade deinen Plan', + des: 'Upgrade deinen Plan, um deine Marke anzupassen.', }, webapp: { - title: 'WebApp Marke anpassen', + title: 'web app Marke anpassen', removeBrand: 'Entferne Powered by Dify', changeLogo: 'Ändere Powered by Markenbild', changeLogoTip: 'SVG oder PNG Format mit einer Mindestgröße von 40x40px', diff --git a/web/i18n/de-DE/dataset-creation.ts b/web/i18n/de-DE/dataset-creation.ts index a4815c1def..9500c0cd68 100644 --- a/web/i18n/de-DE/dataset-creation.ts +++ b/web/i18n/de-DE/dataset-creation.ts @@ -22,7 +22,7 @@ const translation = { }, uploader: { title: 'Textdatei hochladen', - button: 'Datei hierher ziehen oder', + button: 'Dateien und Ordner hierher ziehen oder klicken', browse: 'Durchsuchen', tip: 'Unterstützt {{supportTypes}}. Maximal {{size}}MB pro Datei.', validation: { @@ -69,7 +69,7 @@ const translation = { unknownError: 'Unbekannter Fehler', resetAll: 'Alles zurücksetzen', extractOnlyMainContent: 'Extrahieren Sie nur den Hauptinhalt (keine Kopf-, Navigations- und Fußzeilen usw.)', - firecrawlDocLink: 'https://docs.dify.ai/guides/knowledge-base/sync-from-website', + firecrawlDocLink: 'https://docs.dify.ai/en/guides/knowledge-base/create-knowledge-and-upload-documents/import-content-data/sync-from-website', firecrawlTitle: 'Extrahieren von Webinhalten mit 🔥Firecrawl', maxDepthTooltip: 'Maximale Tiefe für das Crawlen relativ zur eingegebenen URL. Tiefe 0 kratzt nur die Seite der eingegebenen URL, Tiefe 1 kratzt die URL und alles nach der eingegebenen URL + ein / und so weiter.', crawlSubPage: 'Unterseiten crawlen', @@ -82,6 +82,14 @@ const translation = { jinaReaderNotConfiguredDescription: 'Richten Sie Jina Reader ein, indem Sie Ihren kostenlosen API-Schlüssel für den Zugriff eingeben.', useSitemapTooltip: 'Folgen Sie der Sitemap, um die Website zu crawlen. Ist dies nicht der Fall, crawlt Jina Reader iterativ basierend auf der Seitenrelevanz, sodass weniger, aber qualitativ hochwertigere Seiten angezeigt werden.', jinaReaderDoc: 'Erfahre mehr über Jina Reader', + configureJinaReader: 'Jina Reader konfigurieren', + waterCrawlNotConfigured: 'Watercrawl ist nicht konfiguriert', + configureWatercrawl: 'Wasserkrabbe konfigurieren', + watercrawlDocLink: 'https://docs.dify.ai/de/guides/knowledge-base/create-knowledge-and-upload-documents/import-content-data/sync-from-website', + watercrawlTitle: 'Webinhalt mit Watercrawl extrahieren', + watercrawlDoc: 'Wasserkriechen-Dokumente', + configureFirecrawl: 'Firecrawl konfigurieren', + waterCrawlNotConfiguredDescription: 'Konfigurieren Sie Watercrawl mit dem API-Schlüssel, um es zu verwenden.', }, cancel: 'Abbrechen', }, @@ -200,6 +208,11 @@ const translation = { title: 'Verbinden Sie sich mit anderen Datenquellen?', description: 'Derzeit verfügt die Wissensdatenbank von Dify nur über begrenzte Datenquellen. Das Beitragen einer Datenquelle zur Dify-Wissensdatenbank ist eine fantastische Möglichkeit, die Flexibilität und Leistungsfähigkeit der Plattform für alle Benutzer zu verbessern. Unser Beitragsleitfaden erleichtert Ihnen den Einstieg. Bitte klicken Sie auf den untenstehenden Link, um mehr zu erfahren.', }, + watercrawl: { + configWatercrawl: 'Wasserkrabbe konfigurieren', + apiKeyPlaceholder: 'API-Schlüssel von watercrawl.dev', + getApiKeyLinkText: 'Holen Sie sich Ihren API-Schlüssel von watercrawl.dev', + }, } export default translation diff --git a/web/i18n/de-DE/dataset-settings.ts b/web/i18n/de-DE/dataset-settings.ts index 24cb1207b8..928da40d88 100644 --- a/web/i18n/de-DE/dataset-settings.ts +++ b/web/i18n/de-DE/dataset-settings.ts @@ -25,6 +25,7 @@ const translation = { learnMore: 'Mehr erfahren', description: ' über die Abrufmethode.', longDescription: ' über die Abrufmethode, dies kann jederzeit in den Wissenseinstellungen geändert werden.', + method: 'Abrufmethode', }, save: 'Speichern', permissionsInvitedMembers: 'Teilweise Teammitglieder', diff --git a/web/i18n/de-DE/dataset.ts b/web/i18n/de-DE/dataset.ts index 3d3535b32c..4d513aab1b 100644 --- a/web/i18n/de-DE/dataset.ts +++ b/web/i18n/de-DE/dataset.ts @@ -168,6 +168,54 @@ const translation = { documentsDisabled: '{{num}} Dokumente deaktiviert - seit über 30 Tagen inaktiv', allKnowledge: 'Alles Wissen', allKnowledgeDescription: 'Wählen Sie diese Option aus, um das gesamte Wissen in diesem Arbeitsbereich anzuzeigen. Nur der Workspace-Besitzer kann das gesamte Wissen verwalten.', + metadata: { + createMetadata: { + namePlaceholder: 'Metadatenname hinzufügen', + back: 'Zurück', + title: 'Neue Metadaten', + name: 'Name', + type: 'Art', + }, + checkName: { + empty: 'Der Metadatenname darf nicht leer sein.', + invalid: 'Der Metadatenname darf nur Kleinbuchstaben, Zahlen und Unterstriche enthalten und muss mit einem Kleinbuchstaben beginnen.', + }, + batchEditMetadata: { + editMetadata: 'Metadaten bearbeiten', + multipleValue: 'Mehrwert', + applyToAllSelectDocument: 'Auf alle ausgewählten Dokumente anwenden', + applyToAllSelectDocumentTip: 'Erstellen Sie automatisch alle oben bearbeiteten und neuen Metadaten für alle ausgewählten Dokumente, andernfalls wird die Bearbeitung der Metadaten nur auf Dokumente angewendet, die bereits Metadaten enthalten.', + editDocumentsNum: 'Bearbeiten von {{num}} Dokumenten', + }, + selectMetadata: { + manageAction: 'Verwalten', + search: 'Metadaten durchsuchen', + newAction: 'Neue Metadaten', + }, + datasetMetadata: { + name: 'Name', + disabled: 'Deaktiviert', + description: 'Sie können alle Metadaten in diesem Wissen hier verwalten. Änderungen werden mit jedem Dokument synchronisiert.', + deleteContent: 'Bist du sicher, dass du die Metadaten "{{name}}" löschen möchtest?', + addMetaData: 'Metadaten hinzufügen', + deleteTitle: 'Bestätigen Sie das Löschen', + values: '{{num}} Werte', + builtIn: 'Eingebaut', + rename: 'Umbenennen', + builtInDescription: 'Integrierte Metadaten werden automatisch extrahiert und generiert. Sie müssen vor der Verwendung aktiviert werden und können nicht bearbeitet werden.', + namePlaceholder: 'Metadatenname', + }, + documentMetadata: { + startLabeling: 'Labeling starten', + technicalParameters: 'Technische Parameter', + documentInformation: 'Dokumentinformationen', + metadataToolTip: 'Metadaten dienen als ein entscheidender Filter, der die Genauigkeit und Relevanz der Informationsbeschaffung verbessert. Sie können die Metadaten für dieses Dokument hier ändern und hinzufügen.', + }, + chooseTime: 'Wählen Sie eine Zeit...', + metadata: 'Metadaten', + addMetadata: 'Metadaten hinzufügen', + }, + embeddingModelNotAvailable: 'Das Einbettungsmodell ist nicht verfügbar.', } export default translation diff --git a/web/i18n/de-DE/education.ts b/web/i18n/de-DE/education.ts new file mode 100644 index 0000000000..aa6e3c75d0 --- /dev/null +++ b/web/i18n/de-DE/education.ts @@ -0,0 +1,47 @@ +const translation = { + toVerifiedTip: { + coupon: 'exklusiver 100% Gutschein', + end: 'für den Dify Professional Plan.', + front: 'Sie sind jetzt berechtigt, den Status „Bildung verifiziert“ zu erhalten. Bitte geben Sie unten Ihre Bildungsinformationen ein, um den Prozess abzuschließen und eine Zu erhalten.', + }, + form: { + schoolName: { + placeholder: 'Geben Sie den offiziellen, unabgekürzten Namen Ihrer Schule ein.', + title: 'Ihr Schulname', + }, + schoolRole: { + option: { + teacher: 'Lehrer', + administrator: 'Schuladministrator', + student: 'Schüler', + }, + title: 'Ihre Schulrolle', + }, + terms: { + desc: { + and: 'und', + privacyPolicy: 'Datenschutzrichtlinie', + termsOfService: 'Nutzungsbedingungen', + end: '. Durch die Einreichung:', + front: 'Ihre Informationen und die Nutzung des Status "Bildung bestätigt" unterliegen unseren', + }, + option: { + inSchool: 'Ich bestätige, dass ich an der angegebenen Einrichtung eingeschrieben oder angestellt bin. Dify kann einen Nachweis über die Einschreibung/Anstellung anfordern. Wenn ich meine Berechtigung falsch darstelle, stimme ich zu, alle Gebühren zu zahlen, die aufgrund meines Bildungsstatus ursprünglich erlassen wurden.', + age: 'Ich bestätige, dass ich mindestens 18 Jahre alt bin.', + }, + title: 'Allgemeine Geschäftsbedingungen', + }, + }, + toVerified: 'Bildung überprüfen lassen', + rejectTitle: 'Ihre Dify-Ausbildungsüberprüfung wurde abgelehnt.', + currentSigned: 'DERZEIT ANGEMELDET ALS', + submit: 'Einreichen', + submitError: 'Die Formularübermittlung ist fehlgeschlagen. Bitte versuchen Sie es später erneut.', + rejectContent: 'Leider sind Sie nicht für den Status "Education Verified" berechtigt und können daher den exklusiven 100%-Gutschein für den Dify Professional Plan nicht erhalten, wenn Sie diese E-Mail-Adresse verwenden.', + successContent: 'Wir haben einen 100% Rabattgutschein für den Dify Professional Plan auf Ihr Konto ausgestellt. Der Gutschein ist ein Jahr lang gültig, bitte nutzen Sie ihn innerhalb des Gültigkeitszeitraums.', + learn: 'Erfahren Sie, wie Sie Ihre Ausbildung überprüfen lassen.', + emailLabel: 'Ihre aktuelle E-Mail', + successTitle: 'Sie haben die Dify-Ausbildung verifiziert', +} + +export default translation diff --git a/web/i18n/de-DE/explore.ts b/web/i18n/de-DE/explore.ts index 366726d3d1..7a8e8e04bb 100644 --- a/web/i18n/de-DE/explore.ts +++ b/web/i18n/de-DE/explore.ts @@ -37,6 +37,7 @@ const translation = { HR: 'Personalwesen', Agent: 'Agent', Workflow: 'Arbeitsablauf', + Entertainment: 'Unterhaltung', }, } diff --git a/web/i18n/de-DE/login.ts b/web/i18n/de-DE/login.ts index 2e0d51cf85..23cd7ce11c 100644 --- a/web/i18n/de-DE/login.ts +++ b/web/i18n/de-DE/login.ts @@ -105,6 +105,11 @@ const translation = { licenseInactiveTip: 'Die Dify Enterprise-Lizenz für Ihren Arbeitsbereich ist inaktiv. Wenden Sie sich an Ihren Administrator, um Dify weiterhin zu verwenden.', licenseExpiredTip: 'Die Dify Enterprise-Lizenz für Ihren Arbeitsbereich ist abgelaufen. Wenden Sie sich an Ihren Administrator, um Dify weiterhin zu verwenden.', licenseLost: 'Lizenz verloren', + webapp: { + noLoginMethod: 'Authentifizierungsmethode ist nicht für die Webanwendung konfiguriert', + noLoginMethodTip: 'Bitte kontaktieren Sie den Systemadministrator, um eine Authentifizierungsmethode hinzuzufügen.', + disabled: 'Die Webanmeldeauthentifizierung ist deaktiviert. Bitte kontaktieren Sie den Systemadministrator, um sie zu aktivieren. Sie können versuchen, die App direkt zu verwenden.', + }, } export default translation diff --git a/web/i18n/de-DE/plugin.ts b/web/i18n/de-DE/plugin.ts index 64c59fd79f..ffa7543ac5 100644 --- a/web/i18n/de-DE/plugin.ts +++ b/web/i18n/de-DE/plugin.ts @@ -62,6 +62,7 @@ const translation = { uninstalledTitle: 'Tool nicht installiert', toolLabel: 'Werkzeug', uninstalledContent: 'Dieses Plugin wird aus dem lokalen/GitHub-Repository installiert. Bitte nach der Installation verwenden.', + toolSetting: 'Werkzeugs Einstellungen', }, strategyNum: '{{num}} {{Strategie}} IINKLUSIVE', configureApp: 'App konfigurieren', @@ -180,6 +181,8 @@ const translation = { pluginsResult: '{{num}} Ergebnisse', empower: 'Unterstützen Sie Ihre KI-Entwicklung', and: 'und', + partnerTip: 'Von einem Dify-Partner verifiziert', + verifiedTip: 'Von Dify überprüft', }, task: { clearAll: 'Alle löschen', @@ -192,7 +195,6 @@ const translation = { allCategories: 'Alle Kategorien', install: '{{num}} Installationen', installAction: 'Installieren', - submitPlugin: 'Plugin einreichen', from: 'Von', fromMarketplace: 'Aus dem Marketplace', search: 'Suchen', @@ -204,6 +206,12 @@ const translation = { findMoreInMarketplace: 'Weitere Informationen finden Sie im Marketplace', installPlugin: 'Plugin installieren', installFrom: 'INSTALLIEREN VON', + metadata: { + title: 'Plugins', + }, + difyVersionNotCompatible: 'Die aktuelle Dify-Version ist mit diesem Plugin nicht kompatibel, bitte aktualisieren Sie auf die erforderliche Mindestversion: {{minimalDifyVersion}}', + requestAPlugin: 'Ein Plugin anfordern', + publishPlugins: 'Plugins veröffentlichen', } export default translation diff --git a/web/i18n/de-DE/share-app.ts b/web/i18n/de-DE/share-app.ts index 5ea67dd08f..33c40917dd 100644 --- a/web/i18n/de-DE/share-app.ts +++ b/web/i18n/de-DE/share-app.ts @@ -30,6 +30,12 @@ const translation = { }, tryToSolve: 'Versuchen zu lösen', temporarySystemIssue: 'Entschuldigung, vorübergehendes Systemproblem.', + expand: 'Erweitern', + collapse: 'Reduzieren', + chatSettingsTitle: 'Neues Chat-Setup', + newChatTip: 'Bereits in einem neuen Chat', + viewChatSettings: 'Chateinstellungen anzeigen', + chatFormTip: 'Chat-Einstellungen können nach Beginn des Chats nicht mehr geändert werden.', }, generation: { tabs: { @@ -68,6 +74,11 @@ const translation = { moreThanMaxLengthLine: 'Zeile {{rowIndex}}: {{varName}} Wert darf nicht mehr als {{maxLength}} Zeichen sein', atLeastOne: 'Bitte geben Sie mindestens eine Zeile in die hochgeladene Datei ein.', }, + executions: '{{num}} HINRICHTUNGEN', + execution: 'AUSFÜHRUNG', + }, + login: { + backToHome: 'Zurück zur Startseite', }, } diff --git a/web/i18n/de-DE/time.ts b/web/i18n/de-DE/time.ts index e2410dd34b..16f5bc8475 100644 --- a/web/i18n/de-DE/time.ts +++ b/web/i18n/de-DE/time.ts @@ -1,3 +1,37 @@ -const translation = {} +const translation = { + daysInWeek: { + Sat: 'Sat', + Fri: 'Freitag', + Thu: 'Donnerstag', + Tue: 'Tue', + Sun: 'Sonne', + Mon: 'Mon', + Wed: 'Mittwoch', + }, + months: { + August: 'August', + March: 'März', + January: 'Januar', + June: 'Juni', + July: 'Juli', + November: 'November', + September: 'September', + April: 'April', + February: 'Februar', + May: 'Mai', + December: 'Dezember', + October: 'Oktober', + }, + operation: { + pickDate: 'Datum auswählen', + ok: 'OK', + cancel: 'Stornieren', + now: 'Jetzt', + }, + title: { + pickTime: 'Wähle Zeit', + }, + defaultPlaceholder: 'Wähle eine Zeit...', +} export default translation diff --git a/web/i18n/de-DE/tools.ts b/web/i18n/de-DE/tools.ts index 864ddef431..2f3c24b9da 100644 --- a/web/i18n/de-DE/tools.ts +++ b/web/i18n/de-DE/tools.ts @@ -14,7 +14,6 @@ const translation = { }, author: 'Von', auth: { - unauthorized: 'Zur Autorisierung', authorized: 'Autorisiert', setup: 'Autorisierung einrichten, um zu nutzen', setupModalTitle: 'Autorisierung einrichten', diff --git a/web/i18n/de-DE/workflow.ts b/web/i18n/de-DE/workflow.ts index 74bea2b85e..1a6fa30d9f 100644 --- a/web/i18n/de-DE/workflow.ts +++ b/web/i18n/de-DE/workflow.ts @@ -38,8 +38,6 @@ const translation = { setVarValuePlaceholder: 'Variable setzen', needConnectTip: 'Dieser Schritt ist mit nichts verbunden', maxTreeDepth: 'Maximales Limit von {{depth}} Knoten pro Ast', - needEndNode: 'Der Endblock muss hinzugefügt werden', - needAnswerNode: 'Der Antwortblock muss hinzugefügt werden', workflowProcess: 'Arbeitsablauf', notRunning: 'Noch nicht ausgeführt', previewPlaceholder: 'Geben Sie den Inhalt in das Feld unten ein, um das Debuggen des Chatbots zu starten', @@ -58,7 +56,6 @@ const translation = { learnMore: 'Mehr erfahren', copy: 'Kopieren', duplicate: 'Duplizieren', - addBlock: 'Block hinzufügen', pasteHere: 'Hier einfügen', pointerMode: 'Zeigermodus', handMode: 'Handmodus', @@ -106,6 +103,18 @@ const translation = { addFailureBranch: 'Fail-Branch hinzufügen', loadMore: 'Weitere Workflows laden', noHistory: 'Keine Geschichte', + exportSVG: 'Als SVG exportieren', + noExist: 'Keine solche Variable', + versionHistory: 'Versionsverlauf', + publishUpdate: 'Update veröffentlichen', + referenceVar: 'Referenzvariable', + exportImage: 'Bild exportieren', + exportJPEG: 'Als JPEG exportieren', + exitVersions: 'Ausgangsversionen', + exportPNG: 'Als PNG exportieren', + addBlock: 'Knoten hinzufügen', + needEndNode: 'Der Endknoten muss hinzugefügt werden.', + needAnswerNode: 'Der Antwortknoten muss hinzugefügt werden.', }, env: { envPanelTitle: 'Umgebungsvariablen', @@ -167,19 +176,19 @@ const translation = { stepForward_other: '{{count}} Schritte vorwärts', sessionStart: 'Sitzungsstart', currentState: 'Aktueller Zustand', - nodeTitleChange: 'Blocktitel geändert', - nodeDescriptionChange: 'Blockbeschreibung geändert', - nodeDragStop: 'Block verschoben', - nodeChange: 'Block geändert', - nodeConnect: 'Block verbunden', - nodePaste: 'Block eingefügt', - nodeDelete: 'Block gelöscht', - nodeAdd: 'Block hinzugefügt', - nodeResize: 'Blockgröße geändert', noteAdd: 'Notiz hinzugefügt', noteChange: 'Notiz geändert', noteDelete: 'Notiz gelöscht', - edgeDelete: 'Block getrennt', + edgeDelete: 'Knoten getrennt', + nodeAdd: 'Knoten hinzugefügt', + nodeTitleChange: 'Knotenüberschrift geändert', + nodePaste: 'Knoten eingefügt', + nodeResize: 'Knoten verkleinert', + nodeDescriptionChange: 'Die Knotenbeschreibung wurde geändert', + nodeChange: 'Knoten geändert', + nodeConnect: 'Node verbunden', + nodeDragStop: 'Knoten verschoben', + nodeDelete: 'Knoten gelöscht', }, errorMsg: { fieldRequired: '{{field}} ist erforderlich', @@ -205,10 +214,9 @@ const translation = { testRunIteration: 'Testlaufiteration', back: 'Zurück', iteration: 'Iteration', + loop: 'Schleife', }, tabs: { - 'searchBlock': 'Block suchen', - 'blocks': 'Blöcke', 'tools': 'Werkzeuge', 'allTool': 'Alle', 'builtInTool': 'Eingebaut', @@ -222,6 +230,8 @@ const translation = { 'searchTool': 'Suchwerkzeug', 'plugin': 'Stecker', 'agent': 'Agenten-Strategie', + 'searchBlock': 'Suchknoten', + 'blocks': 'Knoten', }, blocks: { 'start': 'Start', @@ -243,6 +253,9 @@ const translation = { 'list-operator': 'List-Operator', 'document-extractor': 'Doc Extraktor', 'agent': 'Agent', + 'loop': 'Schleife', + 'loop-start': 'Schleifenbeginn', + 'loop-end': 'Schleife beenden', }, blocksAbout: { 'start': 'Definieren Sie die Anfangsparameter zum Starten eines Workflows', @@ -263,6 +276,8 @@ const translation = { 'list-operator': 'Wird verwendet, um Array-Inhalte zu filtern oder zu sortieren.', 'document-extractor': 'Wird verwendet, um hochgeladene Dokumente in Textinhalte zu analysieren, die für LLM leicht verständlich sind.', 'agent': 'Aufruf großer Sprachmodelle zur Beantwortung von Fragen oder zur Verarbeitung natürlicher Sprache', + 'loop': 'Führen Sie eine Schleife aus, bis die Abschlussbedingungen erfüllt sind oder die maximalen Schleifenanzahl erreicht ist.', + 'loop-end': 'Entspricht "break". Dieser Knoten hat keine Konfigurationselemente. Wenn der Schleifenrumpf diesen Knoten erreicht, wird die Schleife beendet.', }, operator: { zoomIn: 'Vergrößern', @@ -273,20 +288,21 @@ const translation = { }, panel: { userInputField: 'Benutzereingabefeld', - changeBlock: 'Block ändern', helpLink: 'Hilfelink', about: 'Über', createdBy: 'Erstellt von ', nextStep: 'Nächster Schritt', - addNextStep: 'Fügen Sie den nächsten Block in diesem Workflow hinzu', - selectNextStep: 'Nächsten Block auswählen', runThisStep: 'Diesen Schritt ausführen', checklist: 'Checkliste', checklistTip: 'Stellen Sie sicher, dass alle Probleme vor der Veröffentlichung gelöst sind', checklistResolved: 'Alle Probleme wurden gelöst', - organizeBlocks: 'Blöcke organisieren', change: 'Ändern', optional: '(optional)', + moveToThisNode: 'Bewege zu diesem Knoten', + selectNextStep: 'Nächsten Schritt auswählen', + addNextStep: 'Fügen Sie den nächsten Schritt in diesem Arbeitsablauf hinzu.', + organizeBlocks: 'Knoten organisieren', + changeBlock: 'Knoten ändern', }, nodes: { common: { @@ -404,6 +420,34 @@ const translation = { variable: 'Variable', }, sysQueryInUser: 'sys.query in Benutzernachricht erforderlich', + jsonSchema: { + warningTips: { + saveSchema: 'Bitte beenden Sie die Bearbeitung des aktuellen Feldes, bevor Sie das Schema speichern.', + }, + stringValidations: 'Stringvalidierungen', + addField: 'Feld hinzufügen', + generateJsonSchema: 'JSON-Schema generieren', + back: 'Zurück', + addChildField: 'Kindfeld hinzufügen', + generationTip: 'Sie können natürliche Sprache verwenden, um schnell ein JSON-Schema zu erstellen.', + title: 'Strukturiertes Ausgabeschema', + resetDefaults: 'Zurücksetzen', + showAdvancedOptions: 'Erweiterte Optionen anzeigen', + fieldNamePlaceholder: 'Feldname', + descriptionPlaceholder: 'Fügen Sie eine Beschreibung hinzu.', + resultTip: 'Hier ist das generierte Ergebnis. Wenn Sie nicht zufrieden sind, können Sie zurückgehen und Ihre Eingabeaufforderung ändern.', + generatedResult: 'Generiertes Ergebnis', + promptTooltip: 'Konvertiere die Textbeschreibung in eine standardisierte JSON-Schema-Struktur.', + promptPlaceholder: 'Beschreibe dein JSON-Schema...', + doc: 'Erfahren Sie mehr über strukturierten Output.', + required: 'erforderlich', + generate: 'Generieren', + apply: 'Bewerben', + import: 'Import aus JSON', + generating: 'Generiere JSON-Schema...', + instruction: 'Anleitung', + regenerate: 'Regenerieren', + }, }, knowledgeRetrieval: { queryVariable: 'Abfragevariable', @@ -416,6 +460,33 @@ const translation = { url: 'Segmentierte URL', metadata: 'Weitere Metadaten', }, + metadata: { + options: { + disabled: { + title: 'Deaktiviert', + subTitle: 'Keine Aktivierung der Metadatfilterung', + }, + automatic: { + desc: 'Automatisch Filterbedingungen für Metadaten basierend auf Abfragevariablen generieren.', + title: 'Automatisch', + subTitle: 'Automatisch Metadatenfilterbedingungen basierend auf der Benutzeranfrage generieren', + }, + manual: { + title: 'Handbuch', + subTitle: 'Manuell Filterbedingungen für Metadaten hinzufügen', + }, + }, + panel: { + placeholder: 'Wert eingeben', + datePlaceholder: 'Wählen Sie eine Zeit...', + add: 'Bedingung hinzufügen', + title: 'Metadatenfilterbedingungen', + select: 'Wählen Sie eine Variable aus...', + conditions: 'Bedingungen', + search: 'Suchmetadaten', + }, + title: 'Metadatenfilterung', + }, }, http: { inputVars: 'Eingabevariablen', @@ -505,6 +576,8 @@ const translation = { 'all of': 'alle', 'exists': 'existiert', 'not in': 'nicht in', + 'after': 'nach', + 'before': 'vor', }, enterValue: 'Wert eingeben', addCondition: 'Bedingung hinzufügen', @@ -520,6 +593,7 @@ const translation = { }, select: 'Auswählen', addSubVariable: 'Untervariable', + condition: 'Bedingung', }, variableAssigner: { title: 'Variablen zuweisen', @@ -562,6 +636,8 @@ const translation = { 'extend': 'Ausdehnen', '*=': '*=', 'overwrite': 'Überschreiben', + 'remove-first': 'Erste entfernen', + 'remove-last': 'Letzte entfernen', }, 'setParameter': 'Parameter setzen...', 'noVarTip': 'Klicken Sie auf die Schaltfläche "+", um Variablen hinzuzufügen', @@ -572,7 +648,6 @@ const translation = { 'assignedVarsDescription': 'Zugewiesene Variablen müssen beschreibbare Variablen sein, z. B. Konversationsvariablen.', }, tool: { - toAuthorize: 'Autorisieren', inputVars: 'Eingabevariablen', outputVars: { text: 'durch das Tool generierter Inhalt', @@ -585,6 +660,7 @@ const translation = { }, json: 'von einem Tool generiertes JSON', }, + authorize: 'Autorisieren', }, questionClassifiers: { model: 'Modell', @@ -766,6 +842,38 @@ const translation = { configureModel: 'Modell konfigurieren', linkToPlugin: 'Link zu Plugins', }, + loop: { + ErrorMethod: { + removeAbnormalOutput: 'Abnormale Ausgaben entfernen', + continueOnError: 'Fortfahren bei Fehler', + operationTerminated: 'Beendet', + }, + comma: ',', + loopNode: 'Schleifen-Knoten', + loop_other: '{{count}} Schleifen', + totalLoopCount: 'Gesamtanzahl der Schleifen: {{count}}', + deleteDesc: 'Das Löschen des Schleifen-Knotens entfernt alle untergeordneten Knoten.', + loopVariables: 'Schleifenvariablen', + loop_one: '{{count}} Schleife', + breakCondition: 'Schleifenbeendigungsbedingung', + setLoopVariables: 'Setze Variablen innerhalb des Schleifenbereichs', + breakConditionTip: 'Nur Variablen innerhalb von Schleifen mit Abbruchbedingungen und Konversationsvariablen können referenziert werden.', + loopMaxCountError: 'Bitte geben Sie eine gültige maximale Schleifenanzahl ein, die von 1 bis {{maxCount}} reicht.', + deleteTitle: 'Schleifen-Knoten löschen?', + currentLoop: 'Aktueller Loop', + loopMaxCount: 'Maximale Schleifenanzahl', + finalLoopVariables: 'Endgültige Schleifenvariablen', + exitConditionTip: 'Ein Schleifen-Knoten benötigt mindestens eine Ausgangsbedingung.', + errorResponseMethod: 'Fehlerantwortmethode', + initialLoopVariables: 'Ursprüngliche Schleifenvariablen', + variableName: 'Variablenname', + error_one: '{{count}} Fehler', + currentLoopCount: 'Aktuelle Schleifenanzahl: {{count}}', + inputMode: 'Eingabemodus', + error_other: '{{count}} Fehler', + output: 'Ausgabewert', + input: 'Eingabe', + }, }, tracing: { stopBy: 'Gestoppt von {{user}}', @@ -777,6 +885,38 @@ const translation = { noVarsForOperation: 'Es stehen keine Variablen für die Zuweisung mit der ausgewählten Operation zur Verfügung.', assignedVarsDescription: 'Zugewiesene Variablen müssen beschreibbare Variablen sein, z. B.', }, + versionHistory: { + filter: { + all: 'Alle', + onlyShowNamedVersions: 'Nur benannte Versionen anzeigen', + onlyYours: 'Nur dein', + reset: 'Filter zurücksetzen', + empty: 'Kein passendes Versionsprotokoll gefunden.', + }, + editField: { + releaseNotesLengthLimit: 'Die Versionshinweise dürfen {{limit}} Zeichen nicht überschreiten.', + titleLengthLimit: 'Der Titel darf {{limit}} Zeichen nicht überschreiten.', + releaseNotes: 'Versionshinweise', + title: 'Titel', + }, + action: { + restoreFailure: 'Wiederherstellung der Version fehlgeschlagen', + updateSuccess: 'Version aktualisiert', + deleteSuccess: 'Version gelöscht', + deleteFailure: 'Version löschen fehlgeschlagen', + restoreSuccess: 'Version wiederhergestellt', + updateFailure: 'Aktualisierung der Version fehlgeschlagen', + }, + latest: 'Neueste', + nameThisVersion: 'Nennen Sie diese Version', + currentDraft: 'Aktueller Entwurf', + releaseNotesPlaceholder: 'Beschreibe, was sich geändert hat.', + defaultName: 'Unbetitelte Version', + title: 'Versionen', + editVersionInfo: 'Versionsinformationen bearbeiten', + deletionTip: 'Die Löschung ist unumkehrbar, bitte bestätigen Sie.', + restorationTip: 'Nach der Wiederherstellung der Version wird der aktuelle Entwurf überschrieben.', + }, } export default translation diff --git a/web/i18n/en-US/app-debug.ts b/web/i18n/en-US/app-debug.ts index 3ee5fd3e1d..349ff37118 100644 --- a/web/i18n/en-US/app-debug.ts +++ b/web/i18n/en-US/app-debug.ts @@ -368,6 +368,7 @@ const translation = { 'inputPlaceholder': 'Please input', 'content': 'Content', 'required': 'Required', + 'hide': 'Hide', 'file': { supportFileTypes: 'Support File Types', image: { diff --git a/web/i18n/en-US/app-overview.ts b/web/i18n/en-US/app-overview.ts index 0261f4cd4e..feedc32e6b 100644 --- a/web/i18n/en-US/app-overview.ts +++ b/web/i18n/en-US/app-overview.ts @@ -30,28 +30,28 @@ const translation = { overview: { title: 'Overview', appInfo: { - explanation: 'Ready-to-use AI WebApp', + explanation: 'Ready-to-use AI web app', accessibleAddress: 'Public URL', preview: 'Preview', launch: 'Launch', regenerate: 'Regenerate', regenerateNotice: 'Do you want to regenerate the public URL?', - preUseReminder: 'Please enable WebApp before continuing.', + preUseReminder: 'Please enable web app before continuing.', settings: { entry: 'Settings', title: 'Web App Settings', modalTip: 'Client-side web app settings. ', - webName: 'WebApp Name', - webDesc: 'WebApp Description', + webName: 'web app Name', + webDesc: 'web app Description', webDescTip: 'This text will be displayed on the client side, providing basic guidance on how to use the application', - webDescPlaceholder: 'Enter the description of the WebApp', + webDescPlaceholder: 'Enter the description of the web app', language: 'Language', workflow: { title: 'Workflow', subTitle: 'Workflow Details', show: 'Show', hide: 'Hide', - showDesc: 'Show or hide workflow details in WebApp', + showDesc: 'Show or hide workflow details in web app', }, chatColorTheme: 'Chat color theme', chatColorThemeDesc: 'Set the color theme of the chatbot', @@ -60,14 +60,14 @@ const translation = { invalidPrivacyPolicy: 'Invalid privacy policy link. Please use a valid link that starts with http or https', sso: { label: 'SSO Enforcement', - title: 'WebApp SSO', - description: 'All users are required to login with SSO before using WebApp', - tooltip: 'Contact the administrator to enable WebApp SSO', + title: 'web app SSO', + description: 'All users are required to login with SSO before using web app', + tooltip: 'Contact the administrator to enable web app SSO', }, more: { entry: 'Show more settings', copyright: 'Copyright', - copyrightTip: 'Display copyright information in the webapp', + copyrightTip: 'Display copyright information in the web app', copyrightTooltip: 'Please upgrade to Professional plan or above', copyRightPlaceholder: 'Enter the name of the author or organization', privacyPolicy: 'Privacy Policy', @@ -96,7 +96,7 @@ const translation = { customize: { way: 'way', entry: 'Customize', - title: 'Customize AI WebApp', + title: 'Customize AI web app', explanation: 'You can customize the frontend of the Web App to fit your scenario and style needs.', way1: { name: 'Fork the client code, modify it and deploy to Vercel (recommended)', diff --git a/web/i18n/en-US/app.ts b/web/i18n/en-US/app.ts index 8139c1e3f2..ccfe23ead6 100644 --- a/web/i18n/en-US/app.ts +++ b/web/i18n/en-US/app.ts @@ -47,13 +47,13 @@ const translation = { completionUserDescription: 'Quickly build an AI assistant for text generation tasks with simple configuration.', agentShortDescription: 'Intelligent agent with reasoning and autonomous tool use', agentUserDescription: 'An intelligent agent capable of iterative reasoning and autonomous tool use to achieve task goals.', - workflowShortDescription: 'Orchestration for single-turn automation tasks', - workflowUserDescription: 'Workflow orchestration for single-round tasks like automation and batch processing.', + workflowShortDescription: 'Agentic flow for intelligent automations', + workflowUserDescription: 'Visually build autonomous AI workflows with drag-and-drop simplicity.', workflowWarning: 'Currently in beta', - advancedShortDescription: 'Workflow for complex multi-turn dialogues with memory', - advancedUserDescription: 'Workflow orchestration for multi-round complex dialogue tasks with memory capabilities.', - chooseAppType: 'Choose App Type', - forBeginners: 'FOR BEGINNERS', + advancedShortDescription: 'Workflow enhanced for multi-turn chats', + advancedUserDescription: 'Workflow with additional memory features and a chatbot interface.', + chooseAppType: 'Choose an App Type', + forBeginners: 'More basic app types', forAdvanced: 'FOR ADVANCED USERS', noIdeaTip: 'No ideas? Check out our templates', captionName: 'App Name & Icon', @@ -112,9 +112,9 @@ const translation = { image: 'Image', }, answerIcon: { - title: 'Use WebApp icon to replace 🤖', - description: 'Whether to use the WebApp icon to replace 🤖 in the shared application', - descriptionInExplore: 'Whether to use the WebApp icon to replace 🤖 in Explore', + title: 'Use web app icon to replace 🤖', + description: 'Whether to use the web app icon to replace 🤖 in the shared application', + descriptionInExplore: 'Whether to use the web app icon to replace 🤖 in Explore', }, switch: 'Switch to Workflow Orchestrate', switchTipStart: 'A new app copy will be created for you, and the new copy will switch to Workflow Orchestrate. The new copy will ', @@ -161,6 +161,10 @@ const translation = { title: 'Opik', description: 'Opik is an open-source platform for evaluating, testing, and monitoring LLM applications.', }, + weave: { + title: 'Weave', + description: 'Weave is an open-source platform for evaluating, testing, and monitoring LLM applications.', + }, inUse: 'In use', configProvider: { title: 'Config ', @@ -191,6 +195,43 @@ const translation = { modelNotSupported: 'Model not supported', modelNotSupportedTip: 'The current model does not support this feature and is automatically downgraded to prompt injection.', }, + accessControl: 'Web App Access Control', + accessItemsDescription: { + anyone: 'Anyone can access the web app (no login required)', + specific: 'Only specific members within the platform can access the Web application', + organization: 'All members within the platform can access the Web application', + external: 'Only authenticated external users can access the Web application', + }, + accessControlDialog: { + title: 'Web App Access Control', + description: 'Set web app access permissions', + accessLabel: 'Who has access', + accessItems: { + anyone: 'Anyone with the link', + specific: 'Specific members within the platform', + organization: 'All members within the platform', + external: 'Authenticated external users', + }, + groups_one: '{{count}} GROUP', + groups_other: '{{count}} GROUPS', + members_one: '{{count}} MEMBER', + members_other: '{{count}} MEMBERS', + noGroupsOrMembers: 'No groups or members selected', + webAppSSONotEnabledTip: 'Please contact your organization administrator to configure external authentication for the Web application.', + operateGroupAndMember: { + searchPlaceholder: 'Search groups and members', + allMembers: 'All members', + expand: 'Expand', + noResult: 'No result', + }, + updateSuccess: 'Update successfully', + }, + publishApp: { + title: 'Who can access web app', + notSet: 'Not set', + notSetDesc: 'Currently nobody can access the web app. Please set permissions.', + }, + noAccessPermission: 'No permission to access web app', } export default translation diff --git a/web/i18n/en-US/billing.ts b/web/i18n/en-US/billing.ts index 893e730842..b75fef3e19 100644 --- a/web/i18n/en-US/billing.ts +++ b/web/i18n/en-US/billing.ts @@ -55,6 +55,10 @@ const translation = { vectorSpaceTooltip: 'Documents with the High Quality indexing mode will consume Knowledge Data Storage resources. When Knowledge Data Storage reaches the limit, new documents will not be uploaded.', documentsRequestQuota: '{{count,number}}/min Knowledge Request Rate Limit', documentsRequestQuotaTooltip: 'Specifies the total number of actions a workspace can perform per minute within the knowledge base, including dataset creation, deletion, updates, document uploads, modifications, archiving, and knowledge base queries. This metric is used to evaluate the performance of knowledge base requests. For example, if a Sandbox user performs 10 consecutive hit tests within one minute, their workspace will be temporarily restricted from performing the following actions for the next minute: dataset creation, deletion, updates, and document uploads or modifications. ', + apiRateLimit: 'API Rate Limit', + apiRateLimitUnit: '{{count,number}}/day', + unlimitedApiRate: 'No API Rate Limit', + apiRateLimitTooltip: 'API Rate Limit applies to all requests made through the Dify API, including text generation, chat conversations, workflow executions, and document processing.', documentProcessingPriority: ' Document Processing', documentProcessingPriorityUpgrade: 'Process more data with higher accuracy at faster speeds.', priority: { @@ -88,9 +92,9 @@ const translation = { member: 'Member', memberAfter: 'Member', messageRequest: { - title: '{{count,number}} messages', - titlePerMonth: '{{count,number}} messages/month', - tooltip: 'Message invocation quotas for various plans using OpenAl models. Messages over the limit will use your OpenAI API Key.', + title: '{{count,number}} message credits', + titlePerMonth: '{{count,number}} message credits/month', + tooltip: 'Message credits are provided to help you easily try out different OpenAI models in Dify. Credits are consumed based on the model type. Once they’re used up, you can switch to your own OpenAI API key.', }, annotatedResponse: { title: '{{count,number}} Annotation Quota Limits', diff --git a/web/i18n/en-US/common.ts b/web/i18n/en-US/common.ts index bf2bf83f68..05000f9343 100644 --- a/web/i18n/en-US/common.ts +++ b/web/i18n/en-US/common.ts @@ -1,4 +1,10 @@ const translation = { + theme: { + theme: 'Theme', + light: 'light', + dark: 'dark', + auto: 'system', + }, api: { success: 'Success', actionSuccess: 'Action succeeded', @@ -57,6 +63,7 @@ const translation = { submit: 'Submit', skip: 'Skip', format: 'Format', + more: 'More', }, errorMsg: { fieldRequired: '{{field}} is required', @@ -140,6 +147,8 @@ const translation = { status: 'beta', explore: 'Explore', apps: 'Studio', + appDetail: 'App Detail', + account: 'Account', plugins: 'Plugins', exploreMarketplace: 'Explore Marketplace', pluginsTips: 'Integrate third-party plugins or create ChatGPT-compatible AI-Plugins.', @@ -189,7 +198,7 @@ const translation = { account: { account: 'Account', myAccount: 'My Account', - studio: 'Dify Studio', + studio: 'Studio', avatar: 'Avatar', name: 'Name', email: 'Email', @@ -201,8 +210,8 @@ const translation = { newPassword: 'New password', confirmPassword: 'Confirm password', notEqual: 'Two passwords are different.', - langGeniusAccount: 'Dify account', - langGeniusAccountTip: 'Your Dify account and associated user data.', + langGeniusAccount: 'Account\'s data', + langGeniusAccountTip: 'The user data of your account.', editName: 'Edit Name', showAppLength: 'Show {{length}} apps', delete: 'Delete Account', @@ -475,7 +484,7 @@ const translation = { apiBasedExtension: { title: 'API extensions provide centralized API management, simplifying configuration for easy use across Dify\'s applications.', link: 'Learn how to develop your own API Extension.', - linkUrl: 'https://docs.dify.ai/features/extension/api_based_extension', + linkUrl: 'https://docs.dify.ai/en/guides/extension/api-based-extension/README', add: 'Add API Extension', selector: { title: 'API Extension', @@ -560,7 +569,7 @@ const translation = { vectorHash: 'Vector hash:', hitScore: 'Retrieval Score:', }, - inputPlaceholder: 'Talk to Bot', + inputPlaceholder: 'Talk to {{botName}}', thinking: 'Thinking...', thought: 'Thought', resend: 'Resend', @@ -650,6 +659,7 @@ const translation = { license: { expiring: 'Expiring in one day', expiring_plural: 'Expiring in {{count}} days', + unlimited: 'Unlimited', }, pagination: { perPage: 'Items per page', @@ -659,6 +669,7 @@ const translation = { browse: 'browse', supportedFormats: 'Supports PNG, JPG, JPEG, WEBP and GIF', }, + you: 'You', } export default translation diff --git a/web/i18n/en-US/custom.ts b/web/i18n/en-US/custom.ts index 408d4c55e4..d88071a018 100644 --- a/web/i18n/en-US/custom.ts +++ b/web/i18n/en-US/custom.ts @@ -7,7 +7,7 @@ const translation = { suffix: 'customize your brand.', }, webapp: { - title: 'Customize WebApp brand', + title: 'Customize web app brand', removeBrand: 'Remove Powered by Dify', changeLogo: 'Change Powered by Brand Image', changeLogoTip: 'SVG or PNG format with a minimum size of 40x40px', diff --git a/web/i18n/en-US/dataset-creation.ts b/web/i18n/en-US/dataset-creation.ts index 5a2ad90620..cf2d454f06 100644 --- a/web/i18n/en-US/dataset-creation.ts +++ b/web/i18n/en-US/dataset-creation.ts @@ -35,7 +35,7 @@ const translation = { }, uploader: { title: 'Upload file', - button: 'Drag and drop file, or', + button: 'Drag and drop file or folder, or', browse: 'Browse', tip: 'Supports {{supportTypes}}. Max {{size}}MB each.', validation: { @@ -80,10 +80,10 @@ const translation = { run: 'Run', firecrawlTitle: 'Extract web content with 🔥Firecrawl', firecrawlDoc: 'Firecrawl docs', - firecrawlDocLink: 'https://docs.dify.ai/guides/knowledge-base/sync-from-website', + firecrawlDocLink: 'https://docs.dify.ai/en/guides/knowledge-base/create-knowledge-and-upload-documents/import-content-data/sync-from-website', watercrawlTitle: 'Extract web content with Watercrawl', watercrawlDoc: 'Watercrawl docs', - watercrawlDocLink: 'https://docs.dify.ai/guides/knowledge-base/sync-from-website', + watercrawlDocLink: 'https://docs.dify.ai/en/guides/knowledge-base/create-knowledge-and-upload-documents/import-content-data/sync-from-website', jinaReaderTitle: 'Convert the entire site to Markdown', jinaReaderDoc: 'Learn more about Jina Reader', jinaReaderDocLink: 'https://jina.ai/reader', diff --git a/web/i18n/en-US/dataset-documents.ts b/web/i18n/en-US/dataset-documents.ts index d7fd70c089..2a79324477 100644 --- a/web/i18n/en-US/dataset-documents.ts +++ b/web/i18n/en-US/dataset-documents.ts @@ -51,7 +51,7 @@ const translation = { empty: { title: 'There is no documentation yet', upload: { - tip: 'You can upload files, sync from the website, or from webb apps like Notion, GitHub, etc.', + tip: 'You can upload files, sync from the website, or from web apps like Notion, GitHub, etc.', }, sync: { tip: 'Dify will periodically download files from your Notion and complete processing.', diff --git a/web/i18n/en-US/dataset-settings.ts b/web/i18n/en-US/dataset-settings.ts index bf10bed436..3562ea9e66 100644 --- a/web/i18n/en-US/dataset-settings.ts +++ b/web/i18n/en-US/dataset-settings.ts @@ -7,7 +7,7 @@ const translation = { nameError: 'Name cannot be empty', desc: 'Knowledge Description', descInfo: 'Please write a clear textual description to outline the content of the Knowledge. This description will be used as a basis for matching when selecting from multiple Knowledge for inference.', - descPlaceholder: 'Describe what is in this data set. A detailed description allows AI to access the content of the data set in a timely manner. If empty, LangGenius will use the default hit strategy.', + descPlaceholder: 'Describe what is in this data set. A detailed description allows AI to access the content of the data set in a timely manner. If empty, Dify will use the default hit strategy.', helpText: 'Learn how to write a good dataset description.', descWrite: 'Learn how to write a good Knowledge description.', permissions: 'Permissions', diff --git a/web/i18n/en-US/education.ts b/web/i18n/en-US/education.ts index b3a13612ed..dd7fedc10f 100644 --- a/web/i18n/en-US/education.ts +++ b/web/i18n/en-US/education.ts @@ -2,7 +2,7 @@ const translation = { toVerified: 'Get Education Verified', toVerifiedTip: { front: 'You are now eligible for Education Verified status. Please enter your education information below to complete the process and receive an', - coupon: 'exclusive 50% coupon', + coupon: 'exclusive 100% coupon', end: 'for the Dify Professional Plan.', }, currentSigned: 'CURRENTLY SIGNED IN AS', @@ -24,7 +24,7 @@ const translation = { desc: { front: 'Your information and use of Education Verified status are subject to our', and: 'and', - end: '. By submitting:', + end: '. By submitting:', termsOfService: 'Terms of Service', privacyPolicy: 'Privacy Policy', }, @@ -38,9 +38,9 @@ const translation = { submitError: 'Form submission failed. Please try again later.', learn: 'Learn how to get education verified', successTitle: 'You Have Got Dify Education Verified', - successContent: 'We have issued a 50% discount coupon for the Dify Professional plan to your account. The coupon is valid for one year, please use it within the validity period.', + successContent: 'We have issued a 100% discount coupon for the Dify Professional plan to your account. The coupon is valid for one year, please use it within the validity period.', rejectTitle: 'Your Dify Educational Verification Has Been Rejected', - rejectContent: 'Unfortunately, you are not eligible for Education Verified status and therefore cannot receive the exclusive 50% coupon for the Dify Professional Plan if you use this email address.', + rejectContent: 'Unfortunately, you are not eligible for Education Verified status and therefore cannot receive the exclusive 100% coupon for the Dify Professional Plan if you use this email address.', emailLabel: 'Your current email', } diff --git a/web/i18n/en-US/explore.ts b/web/i18n/en-US/explore.ts index 40e928f8da..7ae457ce9d 100644 --- a/web/i18n/en-US/explore.ts +++ b/web/i18n/en-US/explore.ts @@ -16,7 +16,7 @@ const translation = { }, }, apps: { - title: 'Explore Apps by Dify', + title: 'Explore Apps', description: 'Use these template apps instantly or customize your own apps based on the templates.', allCategories: 'Recommended', }, diff --git a/web/i18n/en-US/login.ts b/web/i18n/en-US/login.ts index f7d717197b..0beb631d24 100644 --- a/web/i18n/en-US/login.ts +++ b/web/i18n/en-US/login.ts @@ -104,6 +104,11 @@ const translation = { licenseLostTip: 'Failed to connect Dify license server. Please contact your administrator to continue using Dify.', licenseInactive: 'License Inactive', licenseInactiveTip: 'The Dify Enterprise license for your workspace is inactive. Please contact your administrator to continue using Dify.', + webapp: { + noLoginMethod: 'Authentication method not configured for web app', + noLoginMethodTip: 'Please contact the system admin to add an authentication method.', + disabled: 'Webapp authentication is disabled. Please contact the system admin to enable it. You can try to use the app directly.', + }, } export default translation diff --git a/web/i18n/en-US/plugin.ts b/web/i18n/en-US/plugin.ts index a0b36fbd65..00af855282 100644 --- a/web/i18n/en-US/plugin.ts +++ b/web/i18n/en-US/plugin.ts @@ -77,6 +77,7 @@ const translation = { modelNum: '{{num}} MODELS INCLUDED', toolSelector: { title: 'Add tool', + toolSetting: 'Tool Settings', toolLabel: 'Tool', descriptionLabel: 'Tool description', descriptionPlaceholder: 'Brief description of the tool\'s purpose, e.g., get the temperature for a specific location.', @@ -208,7 +209,8 @@ const translation = { installedError: '{{errorLength}} plugins failed to install', clearAll: 'Clear all', }, - submitPlugin: 'Submit plugin', + requestAPlugin: 'Request a plugin', + publishPlugins: 'Publish plugins', difyVersionNotCompatible: 'The current Dify version is not compatible with this plugin, please upgrade to the minimum version required: {{minimalDifyVersion}}', } diff --git a/web/i18n/en-US/share-app.ts b/web/i18n/en-US/share-app.ts index 3db0e98f99..ab589ffb76 100644 --- a/web/i18n/en-US/share-app.ts +++ b/web/i18n/en-US/share-app.ts @@ -34,6 +34,8 @@ const translation = { }, tryToSolve: 'Try to solve', temporarySystemIssue: 'Sorry, temporary system issue.', + expand: 'Expand', + collapse: 'Collapse', }, generation: { tabs: { @@ -75,6 +77,9 @@ const translation = { atLeastOne: 'Please input at least one row in the uploaded file.', }, }, + login: { + backToHome: 'Back to Home', + }, } export default translation diff --git a/web/i18n/en-US/tools.ts b/web/i18n/en-US/tools.ts index f624fac945..433e98720a 100644 --- a/web/i18n/en-US/tools.ts +++ b/web/i18n/en-US/tools.ts @@ -15,7 +15,6 @@ const translation = { }, author: 'By', auth: { - unauthorized: 'To Authorize', authorized: 'Authorized', setup: 'Set up authorization to use', setupModalTitle: 'Set Up Authorization', diff --git a/web/i18n/en-US/workflow.ts b/web/i18n/en-US/workflow.ts index 543f689611..0524ce49fe 100644 --- a/web/i18n/en-US/workflow.ts +++ b/web/i18n/en-US/workflow.ts @@ -46,8 +46,8 @@ const translation = { setVarValuePlaceholder: 'Set variable', needConnectTip: 'This step is not connected to anything', maxTreeDepth: 'Maximum limit of {{depth}} nodes per branch', - needEndNode: 'The End block must be added', - needAnswerNode: 'The Answer block must be added', + needEndNode: 'The End node must be added', + needAnswerNode: 'The Answer node must be added', workflowProcess: 'Workflow Process', notRunning: 'Not running yet', previewPlaceholder: 'Enter content in the box below to start debugging the Chatbot', @@ -66,7 +66,7 @@ const translation = { learnMore: 'Learn More', copy: 'Copy', duplicate: 'Duplicate', - addBlock: 'Add Block', + addBlock: 'Add Node', pasteHere: 'Paste Here', pointerMode: 'Pointer Mode', handMode: 'Hand Mode', @@ -174,19 +174,19 @@ const translation = { stepForward_other: '{{count}} steps forward', sessionStart: 'Session Start', currentState: 'Current State', - nodeTitleChange: 'Block title changed', - nodeDescriptionChange: 'Block description changed', - nodeDragStop: 'Block moved', - nodeChange: 'Block changed', - nodeConnect: 'Block connected', - nodePaste: 'Block pasted', - nodeDelete: 'Block deleted', - nodeAdd: 'Block added', - nodeResize: 'Block resized', + nodeTitleChange: 'Node title changed', + nodeDescriptionChange: 'Node description changed', + nodeDragStop: 'Node moved', + nodeChange: 'Node changed', + nodeConnect: 'Node connected', + nodePaste: 'Node pasted', + nodeDelete: 'Node deleted', + nodeAdd: 'Node added', + nodeResize: 'Node resized', noteAdd: 'Note added', noteChange: 'Note changed', noteDelete: 'Note deleted', - edgeDelete: 'Block disconnected', + edgeDelete: 'Node disconnected', }, errorMsg: { fieldRequired: '{{field}} is required', @@ -215,8 +215,8 @@ const translation = { loop: 'Loop', }, tabs: { - 'searchBlock': 'Search block', - 'blocks': 'Blocks', + 'searchBlock': 'Search node', + 'blocks': 'Nodes', 'searchTool': 'Search tool', 'tools': 'Tools', 'allTool': 'All', @@ -292,18 +292,19 @@ const translation = { }, panel: { userInputField: 'User Input Field', - changeBlock: 'Change Block', + changeBlock: 'Change Node', helpLink: 'Help Link', about: 'About', createdBy: 'Created By ', nextStep: 'Next Step', - addNextStep: 'Add the next block in this workflow', - selectNextStep: 'Select Next Block', + addNextStep: 'Add the next step in this workflow', + selectNextStep: 'Select Next Step', runThisStep: 'Run this step', + moveToThisNode: 'Move to this node', checklist: 'Checklist', checklistTip: 'Make sure all issues are resolved before publishing', checklistResolved: 'All issues are resolved', - organizeBlocks: 'Organize blocks', + organizeBlocks: 'Organize nodes', change: 'Change', optional: '(optional)', }, @@ -638,6 +639,8 @@ const translation = { 'clear': 'Clear', 'extend': 'Extend', 'append': 'Append', + 'remove-first': 'Remove First', + 'remove-last': 'Remove Last', '+=': '+=', '-=': '-=', '*=': '*=', @@ -648,7 +651,7 @@ const translation = { 'assignedVarsDescription': 'Assigned variables must be writable variables, such as conversation variables.', }, tool: { - toAuthorize: 'To authorize', + authorize: 'Authorize', inputVars: 'Input Variables', outputVars: { text: 'tool generated content', @@ -736,6 +739,9 @@ const translation = { loop_one: '{{count}} Loop', loop_other: '{{count}} Loops', currentLoop: 'Current Loop', + comma: ', ', + error_one: '{{count}} Error', + error_other: '{{count}} Errors', breakCondition: 'Loop Termination Condition', breakConditionTip: 'Only variables within loops with termination conditions and conversation variables can be referenced.', loopMaxCount: 'Maximum Loop Count', diff --git a/web/i18n/es-ES/app-debug.ts b/web/i18n/es-ES/app-debug.ts index ab5b82e7d1..8c986bf669 100644 --- a/web/i18n/es-ES/app-debug.ts +++ b/web/i18n/es-ES/app-debug.ts @@ -280,6 +280,7 @@ const translation = { 'inputPlaceholder': 'Por favor ingresa', 'content': 'Contenido', 'required': 'Requerido', + 'hide': 'Ocultar', 'errorMsg': { varNameRequired: 'Nombre de la variable es requerido', labelNameRequired: 'Nombre de la etiqueta es requerido', diff --git a/web/i18n/es-ES/app-overview.ts b/web/i18n/es-ES/app-overview.ts index 97f32b1a7a..8413aa276a 100644 --- a/web/i18n/es-ES/app-overview.ts +++ b/web/i18n/es-ES/app-overview.ts @@ -49,7 +49,7 @@ const translation = { show: 'Mostrar', hide: 'Ocultar', subTitle: 'Detalles del flujo de trabajo', - showDesc: 'Mostrar u ocultar detalles del flujo de trabajo en WebApp', + showDesc: 'Mostrar u ocultar detalles del flujo de trabajo en web app', }, chatColorTheme: 'Tema de color del chat', chatColorThemeDesc: 'Establece el tema de color del chatbot', @@ -70,10 +70,10 @@ const translation = { copyrightTooltip: 'Actualice al plan Profesional o superior', }, sso: { - description: 'Todos los usuarios deben iniciar sesión con SSO antes de usar WebApp', - tooltip: 'Póngase en contacto con el administrador para habilitar el inicio de sesión único de WebApp', + description: 'Todos los usuarios deben iniciar sesión con SSO antes de usar web app', + tooltip: 'Póngase en contacto con el administrador para habilitar el inicio de sesión único de web app', label: 'Autenticación SSO', - title: 'WebApp SSO', + title: 'web app SSO', }, modalTip: 'Configuración de la aplicación web del lado del cliente.', }, diff --git a/web/i18n/es-ES/app.ts b/web/i18n/es-ES/app.ts index f6cc9d1735..c1147720e7 100644 --- a/web/i18n/es-ES/app.ts +++ b/web/i18n/es-ES/app.ts @@ -72,21 +72,21 @@ const translation = { appCreateDSLErrorPart1: 'Se ha detectado una diferencia significativa en las versiones de DSL. Forzar la importación puede hacer que la aplicación no funcione correctamente.', appCreateDSLWarning: 'Precaución: La diferencia de versión de DSL puede afectar a determinadas funciones', appCreateDSLErrorPart3: 'Versión actual de DSL de la aplicación:', - forBeginners: 'PARA PRINCIPIANTES', + forBeginners: 'Tipos de aplicación más básicos', learnMore: 'Aprende más', noTemplateFoundTip: 'Intente buscar usando diferentes palabras clave.', chatbotShortDescription: 'Chatbot basado en LLM con una configuración sencilla', - chooseAppType: 'Elija el tipo de aplicación', + chooseAppType: 'Elija un tipo de aplicación', noAppsFound: 'No se han encontrado aplicaciones', - workflowUserDescription: 'Orquestación del flujo de trabajo para tareas de una sola ronda, como la automatización y el procesamiento por lotes.', - advancedShortDescription: 'Flujo de trabajo para diálogos complejos de varios turnos con memoria', + workflowUserDescription: 'Construya flujos de trabajo autónomos de IA con la simplicidad de arrastrar y soltar.', + advancedShortDescription: 'Flujo de trabajo mejorado para chats de múltiples turnos', forAdvanced: 'PARA USUARIOS AVANZADOS', completionShortDescription: 'Asistente de IA para tareas de generación de texto', optional: 'Opcional', noIdeaTip: '¿No tienes ideas? Echa un vistazo a nuestras plantillas', agentUserDescription: 'Un agente inteligente capaz de realizar un razonamiento iterativo y un uso autónomo de las herramientas para alcanzar los objetivos de las tareas.', - workflowShortDescription: 'Orquestación para tareas de automatización de un solo turno', - advancedUserDescription: 'Orquestación de flujos de trabajo para tareas de diálogo complejas de varias rondas con capacidades de memoria.', + workflowShortDescription: 'Flujo agéntico para automatizaciones inteligentes', + advancedUserDescription: 'Flujo de trabajo con funciones de memoria y una interfaz de chatbot.', agentShortDescription: 'Agente inteligente con razonamiento y uso autónomo de herramientas', foundResults: '{{conteo}} Resultados', noTemplateFound: 'No se han encontrado plantillas', @@ -159,6 +159,10 @@ const translation = { description: 'Opik es una plataforma de código abierto para evaluar, probar y monitorear aplicaciones LLM.', title: 'Opik', }, + weave: { + description: 'Weave es una plataforma de código abierto para evaluar, probar y monitorear aplicaciones de LLM.', + title: 'Tejer', + }, }, answerIcon: { title: 'Usar el icono de la aplicación web para reemplazar 🤖', @@ -194,6 +198,53 @@ const translation = { noParams: 'No se necesitan parámetros', params: 'PARÁMETROS DE LA APLICACIÓN', }, + structOutput: { + notConfiguredTip: 'La salida estructurada aún no ha sido configurada.', + required: 'Requerido', + configure: 'Configurar', + LLMResponse: 'Respuesta del LLM', + moreFillTip: 'Mostrando un máximo de 10 niveles de anidación', + modelNotSupportedTip: 'El modelo actual no admite esta función y se degrada automáticamente a inyección de comandos.', + structuredTip: 'Las Salidas Estructuradas son una función que garantiza que el modelo siempre generará respuestas que se ajusten a su esquema JSON proporcionado.', + modelNotSupported: 'Modelo no soportado', + }, + accessItemsDescription: { + anyone: 'Cualquiera puede acceder a la aplicación web.', + specific: 'Solo grupos o miembros específicos pueden acceder a la aplicación web', + organization: 'Cualquiera en la organización puede acceder a la aplicación web', + external: 'Solo los usuarios externos autenticados pueden acceder a la aplicación web.', + }, + accessControlDialog: { + accessItems: { + anyone: 'Cualquiera con el enlace', + specific: 'Grupos o miembros específicos', + organization: 'Solo miembros dentro de la empresa', + external: 'Usuarios externos autenticados', + }, + operateGroupAndMember: { + searchPlaceholder: 'Buscar grupos y miembros', + allMembers: 'Todos los miembros', + expand: 'Expandir', + noResult: 'Sin resultado', + }, + title: 'Control de Acceso a la Aplicación Web', + description: 'Establecer permisos de acceso a la aplicación web', + accessLabel: '¿Quién tiene acceso?', + groups_one: '{{count}} GRUPO', + groups_other: '{{count}} GRUPOS', + members_one: '{{count}} MIEMBRO', + members_other: '{{count}} MIEMBROS', + noGroupsOrMembers: 'No grupos o miembros seleccionados', + webAppSSONotEnabledTip: 'Por favor, contacte al administrador de la empresa para configurar el método de autenticación de la aplicación web.', + updateSuccess: 'Actualización exitosa', + }, + publishApp: { + title: '¿Quién puede acceder a la aplicación web?', + notSet: 'No establecido', + notSetDesc: 'Actualmente nadie puede acceder a la aplicación web. Por favor, configure los permisos.', + }, + accessControl: 'Control de Acceso a la Aplicación Web', + noAccessPermission: 'No se permite el acceso a la aplicación web', } export default translation diff --git a/web/i18n/es-ES/billing.ts b/web/i18n/es-ES/billing.ts index 8dcee420f5..3f83dafd01 100644 --- a/web/i18n/es-ES/billing.ts +++ b/web/i18n/es-ES/billing.ts @@ -70,6 +70,7 @@ const translation = { messageRequest: { title: 'Créditos de Mensajes', tooltip: 'Cuotas de invocación de mensajes para varios planes utilizando modelos de OpenAI (excepto gpt4). Los mensajes que excedan el límite utilizarán tu clave API de OpenAI.', + titlePerMonth: '{{count,number}} mensajes/mes', }, annotatedResponse: { title: 'Límites de Cuota de Anotación', @@ -77,27 +78,94 @@ const translation = { }, ragAPIRequestTooltip: 'Se refiere al número de llamadas API que invocan solo las capacidades de procesamiento de base de conocimientos de Dify.', receiptInfo: 'Solo el propietario del equipo y el administrador del equipo pueden suscribirse y ver la información de facturación.', + priceTip: 'por espacio de trabajo/', + teamMember_one: '{{count, número}} Miembro del Equipo', + getStarted: 'Comenzar', + apiRateLimitUnit: '{{count, número}}/día', + freeTrialTipSuffix: 'No se requiere tarjeta de crédito', + unlimitedApiRate: 'Sin límite de tasa de API', + apiRateLimit: 'Límite de tasa de API', + documentsTooltip: 'Cuota sobre el número de documentos importados desde la Fuente de Datos del Conocimiento.', + comparePlanAndFeatures: 'Compara planes y características', + cloud: 'Servicio en la nube', + teamMember_other: '{{count,number}} Miembros del equipo', + annualBilling: 'Facturación Anual', + self: 'Autoalojado', + freeTrialTip: 'prueba gratuita de 200 llamadas de OpenAI.', + teamWorkspace: '{{count,number}} Espacio de Trabajo en Equipo', + documents: '{{count,number}} Documentos de Conocimiento', + documentsRequestQuota: '{{count,number}}/min Límite de tasa de solicitud de conocimiento', + freeTrialTipPrefix: 'Regístrate y obtén un', + apiRateLimitTooltip: 'El límite de tasa de la API se aplica a todas las solicitudes realizadas a través de la API de Dify, incluidos la generación de texto, las conversaciones de chat, las ejecuciones de flujo de trabajo y el procesamiento de documentos.', + documentsRequestQuotaTooltip: 'Especifica el número total de acciones que un espacio de trabajo puede realizar por minuto dentro de la base de conocimientos, incluyendo la creación, eliminación, actualización de conjuntos de datos, carga de documentos, modificaciones, archivo y consultas a la base de conocimientos. Esta métrica se utiliza para evaluar el rendimiento de las solicitudes a la base de conocimientos. Por ejemplo, si un usuario de Sandbox realiza 10 pruebas consecutivas en un minuto, su espacio de trabajo será temporalmente restringido de realizar las siguientes acciones durante el siguiente minuto: creación de conjuntos de datos, eliminación, actualizaciones y carga o modificaciones de documentos.', }, plans: { sandbox: { name: 'Sandbox', description: 'Prueba gratuita de 200 veces GPT', includesTitle: 'Incluye:', + for: 'Prueba gratuita de capacidades básicas', }, professional: { name: 'Profesional', description: 'Para individuos y pequeños equipos que desean desbloquear más poder de manera asequible.', includesTitle: 'Todo en el plan gratuito, más:', + for: 'Para desarrolladores independientes/equipos pequeños', }, team: { name: 'Equipo', description: 'Colabora sin límites y disfruta de un rendimiento de primera categoría.', includesTitle: 'Todo en el plan Profesional, más:', + for: 'Para equipos de tamaño mediano', }, enterprise: { name: 'Empresa', description: 'Obtén capacidades completas y soporte para sistemas críticos a gran escala.', includesTitle: 'Todo en el plan Equipo, más:', + features: { + 0: 'Soluciones de implementación escalables de nivel empresarial', + 7: 'Actualizaciones y Mantenimiento por Dify Oficialmente', + 8: 'Soporte Técnico Profesional', + 3: 'Múltiples Espacios de Trabajo y Gestión Empresarial', + 1: 'Autorización de Licencia Comercial', + 2: 'Características Exclusivas de la Empresa', + 5: 'SLA negociados por Dify Partners', + 4: 'SSO', + 6: 'Seguridad y Controles Avanzados', + }, + btnText: 'Contactar ventas', + for: 'Para equipos de gran tamaño', + price: 'Personalizado', + priceTip: 'Facturación Anual Solo', + }, + community: { + features: { + 0: 'Todas las características principales se lanzaron bajo el repositorio público', + 2: 'Cumple con la Licencia de Código Abierto de Dify', + 1: 'Espacio de trabajo único', + }, + includesTitle: 'Características gratuitas:', + for: 'Para usuarios individuales, pequeños equipos o proyectos no comerciales', + price: 'Gratis', + btnText: 'Comienza con la Comunidad', + name: 'Comunidad', + description: 'Para usuarios individuales, pequeños equipos o proyectos no comerciales', + }, + premium: { + features: { + 0: 'Confiabilidad autogestionada por varios proveedores de nube', + 1: 'Espacio de trabajo único', + 3: 'Soporte prioritario por correo electrónico y chat', + 2: 'Personalización de logotipos y marcas de WebApp', + }, + description: 'Para organizaciones y equipos de tamaño mediano', + comingSoon: 'Soporte de Microsoft Azure y Google Cloud disponible próximamente', + btnText: 'Obtén Premium en', + priceTip: 'Basado en el Mercado de la Nube', + price: 'Escalable', + includesTitle: 'Todo de Community, además:', + name: 'Premium', + for: 'Para organizaciones y equipos de tamaño mediano', }, }, vectorSpace: { @@ -107,12 +175,26 @@ const translation = { apps: { fullTipLine1: 'Actualiza tu plan para', fullTipLine2: 'crear más aplicaciones.', + fullTip1des: 'Has alcanzado el límite de aplicaciones de construcción en este plan', + fullTip2des: 'Se recomienda limpiar las aplicaciones inactivas para liberar espacio de uso, o contactarnos.', + fullTip1: 'Actualiza para crear más aplicaciones', + fullTip2: 'Límite de plan alcanzado', + contactUs: 'Contáctanos', }, annotatedResponse: { fullTipLine1: 'Actualiza tu plan para', fullTipLine2: 'anotar más conversaciones.', quotaTitle: 'Cuota de Respuesta Anotada', }, + usagePage: { + buildApps: 'Construir aplicaciones', + documentsUploadQuota: 'Cuota de carga de documentos', + vectorSpace: 'Almacenamiento de Datos de Conocimiento', + teamMembers: 'Miembros del equipo', + annotationQuota: 'Cuota de anotación', + vectorSpaceTooltip: 'Los documentos con el modo de indexación de alta calidad consumirán recursos de Almacenamiento de Datos de Conocimiento. Cuando el Almacenamiento de Datos de Conocimiento alcanza el límite, no se subirán nuevos documentos.', + }, + teamMembers: 'Miembros del equipo', } export default translation diff --git a/web/i18n/es-ES/common.ts b/web/i18n/es-ES/common.ts index 5933105ffd..82ed315f1c 100644 --- a/web/i18n/es-ES/common.ts +++ b/web/i18n/es-ES/common.ts @@ -54,6 +54,10 @@ const translation = { in: 'en', viewDetails: 'Ver detalles', copied: 'Copiado', + more: 'Más', + downloadSuccess: 'Descarga completada.', + downloadFailed: 'La descarga ha fallado. Por favor, inténtalo de nuevo más tarde.', + format: 'Formato', }, errorMsg: { fieldRequired: '{{field}} es requerido', @@ -145,6 +149,8 @@ const translation = { newDataset: 'Crear Conocimiento', tools: 'Herramientas', exploreMarketplace: 'Explora el mercado', + appDetail: 'Detalles de la aplicación', + account: 'Cuenta', }, userProfile: { settings: 'Configuraciones', @@ -157,6 +163,9 @@ const translation = { community: 'Comunidad', about: 'Acerca de', logout: 'Cerrar sesión', + support: 'Apoyo', + compliance: 'Cumplimiento', + github: 'GitHub', }, settings: { accountGroup: 'CUENTA', @@ -206,6 +215,9 @@ const translation = { feedbackTitle: 'Retroalimentación', feedbackLabel: '¿Cuéntanos por qué eliminaste tu cuenta?', feedbackPlaceholder: 'Opcional', + workspaceIcon: 'Icono de espacio de trabajo', + editWorkspaceInfo: 'Editar información del espacio de trabajo', + workspaceName: 'Nombre del espacio de trabajo', }, members: { team: 'Equipo', @@ -459,7 +471,7 @@ const translation = { apiBasedExtension: { title: 'Las extensiones basadas en API proporcionan una gestión centralizada de API, simplificando la configuración para su fácil uso en las aplicaciones de Dify.', link: 'Aprende cómo desarrollar tu propia Extensión API.', - linkUrl: 'https://docs.dify.ai/features/extension/api_based_extension', + linkUrl: 'https://docs.dify.ai/en/guides/extension/api-based-extension/README', add: 'Agregar Extensión API', selector: { title: 'Extensión API', @@ -544,9 +556,10 @@ const translation = { vectorHash: 'Hash de vector:', hitScore: 'Puntuación de recuperación:', }, - inputPlaceholder: 'Hablar con el bot', + inputPlaceholder: 'Hablar con el {{botName}}', thinking: 'Pensamiento...', thought: 'Pensamiento', + resend: 'Reenviar', }, promptEditor: { placeholder: 'Escribe tu palabra de indicación aquí, ingresa \'{\' para insertar una variable, ingresa \'/\' para insertar un bloque de contenido de indicación', @@ -637,6 +650,26 @@ const translation = { pagination: { perPage: 'Elementos por página', }, + theme: { + auto: 'sistema', + light: 'luz', + theme: 'Tema', + dark: 'noche', + }, + compliance: { + iso27001: 'Certificación ISO 27001:2022', + gdpr: 'GDPR DPA', + soc2Type1: 'Informe SOC 2 Tipo I', + sandboxUpgradeTooltip: 'Solo disponible con un plan Profesional o de Equipo.', + professionalUpgradeTooltip: 'Solo disponible con un plan de equipo o superior.', + soc2Type2: 'Informe SOC 2 Tipo II', + }, + imageInput: { + supportedFormats: 'Soporta PNG, JPG, JPEG, WEBP y GIF', + browse: 'navegar', + dropImageHere: 'Deja tu imagen aquí, o', + }, + you: 'Tú', } export default translation diff --git a/web/i18n/es-ES/custom.ts b/web/i18n/es-ES/custom.ts index 0dd6512589..b7997581d4 100644 --- a/web/i18n/es-ES/custom.ts +++ b/web/i18n/es-ES/custom.ts @@ -3,9 +3,11 @@ const translation = { upgradeTip: { prefix: 'Actualiza tu plan para', suffix: 'personalizar tu marca.', + des: 'Actualiza tu plan para personalizar tu marca', + title: 'Actualiza tu plan', }, webapp: { - title: 'Personalizar marca de WebApp', + title: 'Personalizar marca de web app', removeBrand: 'Eliminar Powered by Dify', changeLogo: 'Cambiar Imagen de Marca Powered by', changeLogoTip: 'Formato SVG o PNG con un tamaño mínimo de 40x40px', diff --git a/web/i18n/es-ES/dataset-creation.ts b/web/i18n/es-ES/dataset-creation.ts index 9d9b45e4a6..39846883d1 100644 --- a/web/i18n/es-ES/dataset-creation.ts +++ b/web/i18n/es-ES/dataset-creation.ts @@ -27,7 +27,7 @@ const translation = { }, uploader: { title: 'Cargar archivo', - button: 'Arrastra y suelta el archivo, o', + button: 'Arrastre y suelte archivos o carpetas, o', browse: 'Buscar', tip: 'Soporta {{supportTypes}}. Máximo {{size}}MB cada uno.', validation: { @@ -63,7 +63,7 @@ const translation = { run: 'Ejecutar', firecrawlTitle: 'Extraer contenido web con 🔥Firecrawl', firecrawlDoc: 'Documentación de Firecrawl', - firecrawlDocLink: 'https://docs.dify.ai/guides/knowledge-base/sync-from-website', + firecrawlDocLink: 'https://docs.dify.ai/en/guides/knowledge-base/create-knowledge-and-upload-documents/import-content-data/sync-from-website', options: 'Opciones', crawlSubPage: 'Rastrear subpáginas', limit: 'Límite', @@ -87,6 +87,14 @@ const translation = { useSitemapTooltip: 'Siga el mapa del sitio para rastrear el sitio. De lo contrario, Jina Reader rastreará de forma iterativa en función de la relevancia de la página, lo que producirá menos páginas pero de mayor calidad.', chooseProvider: 'Seleccione un proveedor', jinaReaderDoc: 'Más información sobre Jina Reader', + watercrawlTitle: 'Extraer contenido web con Watercrawl', + waterCrawlNotConfigured: 'Watercrawl no está configurado', + configureFirecrawl: 'Configurar Firecrawl', + watercrawlDoc: 'Documentos de Watercrawl', + configureJinaReader: 'Configurar Jina Reader', + watercrawlDocLink: 'https://docs.dify.ai/es/guías/base-de-conocimientos/crear-conocimientos-y-subir-documentos/importar-datos-de-contenido/sincronizar-desde-el-sitio-web', + configureWatercrawl: 'Configurar Watercrawl', + waterCrawlNotConfiguredDescription: 'Configura Watercrawl con la clave de API para usarlo.', }, cancel: 'Cancelar', }, @@ -200,6 +208,11 @@ const translation = { description: 'Actualmente, la base de conocimientos de Ifiy solo tiene fuentes de datos limitadas. Contribuir con una fuente de datos a la base de conocimientos de Dify es una manera fantástica de ayudar a mejorar la flexibilidad y el poder de la plataforma para todos los usuarios. Nuestra guía de contribuciones hace que sea fácil comenzar. Haga clic en el enlace a continuación para obtener más información.', title: '¿Conectarse a otras fuentes de datos?', }, + watercrawl: { + getApiKeyLinkText: 'Obtén tu clave API de watercrawl.dev', + apiKeyPlaceholder: 'Clave API de watercrawl.dev', + configWatercrawl: 'Configurar Watercrawl', + }, } export default translation diff --git a/web/i18n/es-ES/dataset-settings.ts b/web/i18n/es-ES/dataset-settings.ts index ee8072e278..f1c87aa8d6 100644 --- a/web/i18n/es-ES/dataset-settings.ts +++ b/web/i18n/es-ES/dataset-settings.ts @@ -27,6 +27,7 @@ const translation = { learnMore: 'Aprende más', description: ' sobre el método de recuperación.', longDescription: ' sobre el método de recuperación, puedes cambiar esto en cualquier momento en la configuración del conjunto de datos.', + method: 'Método de recuperación', }, save: 'Guardar', retrievalSettings: 'Configuración de recuperación', diff --git a/web/i18n/es-ES/dataset.ts b/web/i18n/es-ES/dataset.ts index 3bfdd8f46c..b759f26ec8 100644 --- a/web/i18n/es-ES/dataset.ts +++ b/web/i18n/es-ES/dataset.ts @@ -168,6 +168,52 @@ const translation = { localDocs: 'Documentos locales', allKnowledgeDescription: 'Seleccione esta opción para mostrar todos los conocimientos de este espacio de trabajo. Solo el propietario del espacio de trabajo puede administrar todo el conocimiento.', allKnowledge: 'Todo el conocimiento', + metadata: { + createMetadata: { + title: 'Nuevos Metadatos', + back: 'Atrás', + type: 'Escribe', + namePlaceholder: 'Agregar nombre de metadatos', + name: 'Nombre', + }, + checkName: { + empty: 'El nombre de metadatos no puede estar vacío', + invalid: 'El nombre de los metadatos solo puede contener letras minúsculas, números y guiones bajos, y debe comenzar con una letra minúscula.', + }, + batchEditMetadata: { + multipleValue: 'Valor Múltiple', + editMetadata: 'Editar Metadatos', + editDocumentsNum: 'Editando {{num}} documentos', + applyToAllSelectDocument: 'Aplicar a todos los documentos seleccionados', + applyToAllSelectDocumentTip: 'Cree automáticamente todos los metadatos editados y nuevos anteriores para todos los documentos seleccionados, de lo contrario, la edición de metadatos solo se aplicará a los documentos que ya los tengan.', + }, + selectMetadata: { + manageAction: 'Gestionar', + search: 'Buscar metadatos', + newAction: 'Nuevos Metadatos', + }, + datasetMetadata: { + addMetaData: 'Agregar Metadatos', + values: '{{num}} Valores', + deleteContent: '¿Estás seguro de que quieres eliminar los metadatos "{{name}}"?', + rename: 'Renombrar', + deleteTitle: 'Confirme para eliminar', + namePlaceholder: 'Nombre de metadatos', + builtInDescription: 'Los metadatos integrados se extraen y generan automáticamente. Deben estar habilitados antes de su uso y no se pueden editar.', + name: 'Nombre', + description: 'Puedes gestionar todos los metadatos en este conocimiento aquí. Las modificaciones se sincronizarán en todos los documentos.', + }, + documentMetadata: { + technicalParameters: 'Parámetros técnicos', + startLabeling: 'Comenzar a etiquetar', + documentInformation: 'Información del documento', + metadataToolTip: 'Los metadatos sirven como un filtro crítico que mejora la precisión y relevancia de la recuperación de información. Puede modificar y agregar metadatos para este documento aquí.', + }, + metadata: 'Metadatos', + chooseTime: 'Elige una hora...', + addMetadata: 'Agregar Metadatos', + }, + embeddingModelNotAvailable: 'El modelo de embeddings no está disponible.', } export default translation diff --git a/web/i18n/es-ES/education.ts b/web/i18n/es-ES/education.ts new file mode 100644 index 0000000000..9382a2de98 --- /dev/null +++ b/web/i18n/es-ES/education.ts @@ -0,0 +1,47 @@ +const translation = { + toVerifiedTip: { + coupon: 'cupón exclusivo del 100%', + end: 'para el Plan Profesional de Dify.', + front: 'Ahora eres elegible para el estado de Educación Verificada. Por favor, introduce tu información educativa a continuación para completar el proceso y recibir un', + }, + form: { + schoolName: { + title: 'El nombre de tu escuela', + placeholder: 'Ingrese el nombre oficial y completo de su escuela', + }, + schoolRole: { + option: { + student: 'Estudiante', + administrator: 'Administrador escolar', + teacher: 'Profesor', + }, + title: 'Tu rol en la escuela', + }, + terms: { + desc: { + termsOfService: 'Términos de Servicio', + privacyPolicy: 'Política de privacidad', + and: 'y', + front: 'Su información y uso del estado de Educación Verificada están sujetos a nuestra', + end: '. Al enviar:', + }, + option: { + age: 'Confirmo que tengo al menos 18 años', + inSchool: 'Confirmo que estoy inscrito o empleado en la institución indicada. Dify puede solicitar prueba de inscripción/empleo. Si falseo mi elegibilidad, acepto pagar cualquier tarifa que se haya eximido inicialmente en función de mi estado educativo.', + }, + title: 'Términos y Acuerdos', + }, + }, + emailLabel: 'Tu correo electrónico actual', + submit: 'Enviar', + submitError: 'Error en el envío del formulario. Por favor, inténtelo de nuevo más tarde.', + successTitle: 'Tienes la educación Dify verificada', + toVerified: 'Verifica la educación', + successContent: 'Hemos emitido un cupón de descuento del 100% para el plan Dify Professional en tu cuenta. El cupón es válido por un año, por favor utilízalo dentro del período de validez.', + learn: 'Aprende cómo obtener la verificación de la educación', + rejectTitle: 'Su verificación educativa de Dify ha sido rechazada.', + currentSigned: 'ACTUALMENTE CONECTADO COMO', + rejectContent: 'Desafortunadamente, no eres elegible para el estado de Educación Verificada y, por lo tanto, no puedes recibir el exclusivo cupón del 100% para el Plan Profesional de Dify si utilizas esta dirección de correo electrónico.', +} + +export default translation diff --git a/web/i18n/es-ES/explore.ts b/web/i18n/es-ES/explore.ts index 74274d4699..204f8da6c3 100644 --- a/web/i18n/es-ES/explore.ts +++ b/web/i18n/es-ES/explore.ts @@ -37,6 +37,7 @@ const translation = { HR: 'Recursos Humanos', Agent: 'Agente', Workflow: 'Flujo de trabajo', + Entertainment: 'Entretenimiento', }, } diff --git a/web/i18n/es-ES/login.ts b/web/i18n/es-ES/login.ts index bb465ac1be..8c575e58ee 100644 --- a/web/i18n/es-ES/login.ts +++ b/web/i18n/es-ES/login.ts @@ -105,6 +105,11 @@ const translation = { licenseLost: 'Licencia perdida', licenseExpiredTip: 'La licencia de Dify Enterprise para su espacio de trabajo ha caducado. Póngase en contacto con su administrador para seguir utilizando Dify.', licenseLostTip: 'No se pudo conectar el servidor de licencias de Dife. Póngase en contacto con su administrador para seguir utilizando Dify.', + webapp: { + disabled: 'La autenticación de la aplicación web está desactivada. Por favor, contacte al administrador del sistema para habilitarla. Puede intentar usar la aplicación directamente.', + noLoginMethodTip: 'Por favor, contacta al administrador del sistema para agregar un método de autenticación.', + noLoginMethod: 'Método de autenticación no configurado para la aplicación web', + }, } export default translation diff --git a/web/i18n/es-ES/plugin.ts b/web/i18n/es-ES/plugin.ts index 9453c20f97..f96b29f2d7 100644 --- a/web/i18n/es-ES/plugin.ts +++ b/web/i18n/es-ES/plugin.ts @@ -62,6 +62,7 @@ const translation = { unsupportedTitle: 'Acción no admitida', params: 'CONFIGURACIÓN DE RAZONAMIENTO', uninstalledLink: 'Administrar en Plugins', + toolSetting: 'Configuraciones de la herramienta', }, endpointDeleteContent: '¿Te gustaría eliminar {{nombre}}?', endpointDisableTip: 'Deshabilitar punto de conexión', @@ -180,6 +181,8 @@ const translation = { discover: 'Descubrir', and: 'y', difyMarketplace: 'Mercado de Dify', + verifiedTip: 'Verificado por Dify', + partnerTip: 'Verificado por un socio de Dify', }, task: { installing: 'Instalando plugins {{installingLength}}, 0 hecho.', @@ -192,7 +195,6 @@ const translation = { fromMarketplace: 'De Marketplace', endpointsEnabled: '{{num}} conjuntos de puntos finales habilitados', from: 'De', - submitPlugin: 'Enviar plugin', installAction: 'Instalar', install: '{{num}} instalaciones', allCategories: 'Todas las categorías', @@ -204,6 +206,12 @@ const translation = { findMoreInMarketplace: 'Más información en Marketplace', installPlugin: 'Instalar plugin', searchPlugins: 'Plugins de búsqueda', + metadata: { + title: 'Complementos', + }, + difyVersionNotCompatible: 'La versión actual de Dify no es compatible con este plugin, por favor actualiza a la versión mínima requerida: {{minimalDifyVersion}}', + requestAPlugin: 'Solicitar un plugin', + publishPlugins: 'Publicar plugins', } export default translation diff --git a/web/i18n/es-ES/share-app.ts b/web/i18n/es-ES/share-app.ts index b1ac171389..caeb056d89 100644 --- a/web/i18n/es-ES/share-app.ts +++ b/web/i18n/es-ES/share-app.ts @@ -30,6 +30,12 @@ const translation = { }, tryToSolve: 'Intentar resolver', temporarySystemIssue: 'Lo sentimos, hay un problema temporal del sistema.', + expand: 'Ampliar', + collapse: 'Contraer', + viewChatSettings: 'Ver configuraciones de chat', + newChatTip: 'Ya en un nuevo chat', + chatFormTip: 'No se pueden modificar los ajustes del chat después de que el chat ha comenzado.', + chatSettingsTitle: 'Nueva configuración de chat', }, generation: { tabs: { @@ -68,6 +74,11 @@ const translation = { moreThanMaxLengthLine: 'Fila {{rowIndex}}: el valor de {{varName}} no puede tener más de {{maxLength}} caracteres', atLeastOne: 'Por favor, ingresa al menos una fila en el archivo cargado.', }, + execution: 'EJECUCIÓN', + executions: '{{num}} EJECUCIONES', + }, + login: { + backToHome: 'Volver a Inicio', }, } diff --git a/web/i18n/es-ES/time.ts b/web/i18n/es-ES/time.ts index e2410dd34b..920c80eea3 100644 --- a/web/i18n/es-ES/time.ts +++ b/web/i18n/es-ES/time.ts @@ -1,3 +1,37 @@ -const translation = {} +const translation = { + daysInWeek: { + Sun: 'Sol', + Thu: 'Jue', + Tue: 'Mar', + Sat: 'Sáb', + Mon: 'Mon', + Fri: 'Viernes', + Wed: 'Miércoles', + }, + months: { + August: 'Agosto', + September: 'Septiembre', + April: 'Abril', + February: 'Febrero', + January: 'Enero', + November: 'Noviembre', + October: 'octubre', + May: 'Mayo', + June: 'Junio', + December: 'Diciembre', + July: 'Julio', + March: 'Marzo', + }, + operation: { + ok: 'De acuerdo', + pickDate: 'Seleccionar fecha', + cancel: 'Cancelar', + now: 'Ahora', + }, + title: { + pickTime: 'Elegir hora', + }, + defaultPlaceholder: 'Elige una hora...', +} export default translation diff --git a/web/i18n/es-ES/tools.ts b/web/i18n/es-ES/tools.ts index 91bce677e4..fd37eef5b1 100644 --- a/web/i18n/es-ES/tools.ts +++ b/web/i18n/es-ES/tools.ts @@ -15,7 +15,6 @@ const translation = { }, author: 'Por', auth: { - unauthorized: 'Para Autorizar', authorized: 'Autorizado', setup: 'Configurar la autorización para usar', setupModalTitle: 'Configurar Autorización', diff --git a/web/i18n/es-ES/workflow.ts b/web/i18n/es-ES/workflow.ts index 0ee1b0a223..9f0c670819 100644 --- a/web/i18n/es-ES/workflow.ts +++ b/web/i18n/es-ES/workflow.ts @@ -38,8 +38,6 @@ const translation = { setVarValuePlaceholder: 'Establecer variable', needConnectTip: 'Este paso no está conectado a nada', maxTreeDepth: 'Límite máximo de {{depth}} nodos por rama', - needEndNode: 'Debe agregarse el bloque de Fin', - needAnswerNode: 'Debe agregarse el bloque de Respuesta', workflowProcess: 'Proceso de flujo de trabajo', notRunning: 'Aún no se está ejecutando', previewPlaceholder: 'Ingrese contenido en el cuadro de abajo para comenzar a depurar el Chatbot', @@ -58,7 +56,6 @@ const translation = { learnMore: 'Más información', copy: 'Copiar', duplicate: 'Duplicar', - addBlock: 'Agregar bloque', pasteHere: 'Pegar aquí', pointerMode: 'Modo puntero', handMode: 'Modo mano', @@ -106,6 +103,18 @@ const translation = { addFailureBranch: 'Agregar rama de error', noHistory: 'Sin historia', loadMore: 'Cargar más flujos de trabajo', + versionHistory: 'Historial de versiones', + exportSVG: 'Exportar como SVG', + exitVersions: 'Versiones de salida', + exportJPEG: 'Exportar como JPEG', + exportPNG: 'Exportar como PNG', + referenceVar: 'Variable de referencia', + publishUpdate: 'Publicar actualización', + noExist: 'No existe tal variable', + exportImage: 'Exportar imagen', + needAnswerNode: 'Se debe agregar el nodo de respuesta', + needEndNode: 'Se debe agregar el nodo Final', + addBlock: 'Agregar nodo', }, env: { envPanelTitle: 'Variables de Entorno', @@ -167,19 +176,19 @@ const translation = { stepForward_other: '{{count}} pasos hacia adelante', sessionStart: 'Inicio de sesión', currentState: 'Estado actual', - nodeTitleChange: 'Se cambió el título del bloque', - nodeDescriptionChange: 'Se cambió la descripción del bloque', - nodeDragStop: 'Bloque movido', - nodeChange: 'Bloque cambiado', - nodeConnect: 'Bloque conectado', - nodePaste: 'Bloque pegado', - nodeDelete: 'Bloque eliminado', - nodeAdd: 'Bloque agregado', - nodeResize: 'Bloque redimensionado', noteAdd: 'Nota agregada', noteChange: 'Nota cambiada', noteDelete: 'Nota eliminada', - edgeDelete: 'Bloque desconectado', + nodeTitleChange: 'Título del nodo cambiado', + nodeAdd: 'Nodo añadido', + nodePaste: 'Nodo pegado', + nodeDragStop: 'Nodo movido', + nodeConnect: 'Nodo conectado', + edgeDelete: 'Nodo desconectado', + nodeDelete: 'Nodo eliminado', + nodeChange: 'Nodo cambiado', + nodeDescriptionChange: 'Descripción del nodo cambiada', + nodeResize: 'Nodo redimensionado', }, errorMsg: { fieldRequired: 'Se requiere {{field}}', @@ -205,10 +214,9 @@ const translation = { testRunIteration: 'Iteración de ejecución de prueba', back: 'Atrás', iteration: 'Iteración', + loop: 'Bucle', }, tabs: { - 'searchBlock': 'Buscar bloque', - 'blocks': 'Bloques', 'tools': 'Herramientas', 'allTool': 'Todos', 'builtInTool': 'Incorporadas', @@ -222,6 +230,8 @@ const translation = { 'searchTool': 'Herramienta de búsqueda', 'agent': 'Estrategia del agente', 'plugin': 'Plugin', + 'searchBlock': 'Buscar nodo', + 'blocks': 'Nodos', }, blocks: { 'start': 'Inicio', @@ -243,6 +253,9 @@ const translation = { 'document-extractor': 'Extractor de documentos', 'list-operator': 'Operador de lista', 'agent': 'Agente', + 'loop-end': 'Salir del bucle', + 'loop': 'Bucle', + 'loop-start': 'Inicio del bucle', }, blocksAbout: { 'start': 'Define los parámetros iniciales para iniciar un flujo de trabajo', @@ -263,6 +276,8 @@ const translation = { 'list-operator': 'Se utiliza para filtrar u ordenar el contenido de la matriz.', 'document-extractor': 'Se utiliza para analizar documentos cargados en contenido de texto que es fácilmente comprensible por LLM.', 'agent': 'Invocar modelos de lenguaje de gran tamaño para responder preguntas o procesar el lenguaje natural', + 'loop-end': 'Equivalente a "romper". Este nodo no tiene elementos de configuración. Cuando el cuerpo del bucle alcanza este nodo, el bucle termina.', + 'loop': 'Ejecuta un bucle de lógica hasta que se cumpla la condición de terminación o se alcance el conteo máximo de bucles.', }, operator: { zoomIn: 'Acercar', @@ -273,20 +288,21 @@ const translation = { }, panel: { userInputField: 'Campo de entrada del usuario', - changeBlock: 'Cambiar bloque', helpLink: 'Enlace de ayuda', about: 'Acerca de', createdBy: 'Creado por ', nextStep: 'Siguiente paso', - addNextStep: 'Agregar el siguiente bloque en este flujo de trabajo', - selectNextStep: 'Seleccionar siguiente bloque', runThisStep: 'Ejecutar este paso', checklist: 'Lista de verificación', checklistTip: 'Asegúrate de resolver todos los problemas antes de publicar', checklistResolved: 'Se resolvieron todos los problemas', - organizeBlocks: 'Organizar bloques', change: 'Cambiar', optional: '(opcional)', + moveToThisNode: 'Mueve a este nodo', + organizeBlocks: 'Organizar nodos', + addNextStep: 'Agrega el siguiente paso en este flujo de trabajo', + changeBlock: 'Cambiar Nodo', + selectNextStep: 'Seleccionar siguiente paso', }, nodes: { common: { @@ -404,6 +420,34 @@ const translation = { variable: 'Variable', }, sysQueryInUser: 'se requiere sys.query en el mensaje del usuario', + jsonSchema: { + warningTips: { + saveSchema: 'Por favor, termina de editar el campo actual antes de guardar el esquema.', + }, + showAdvancedOptions: 'Mostrar opciones avanzadas', + addField: 'Agregar campo', + generatedResult: 'Resultado Generado', + generateJsonSchema: 'Generar esquema JSON', + apply: 'Aplicar', + descriptionPlaceholder: 'Agregar descripción', + stringValidations: 'Validaciones de cadenas', + addChildField: 'Agregar campo de niño', + back: 'Atrás', + promptTooltip: 'Convierta la descripción del texto en una estructura de esquema JSON estandarizada.', + doc: 'Aprender más sobre la salida estructurada', + generating: 'Generando esquema JSON...', + fieldNamePlaceholder: 'Nombre del campo', + resultTip: 'Aquí está el resultado generado. Si no estás satisfecho, puedes regresar y modificar tu solicitud.', + title: 'Esquema de salida estructurada', + regenerate: 'Regenerar', + instruction: 'Instrucción', + generationTip: 'Puedes usar lenguaje natural para crear rápidamente un esquema JSON.', + promptPlaceholder: 'Describe tu esquema JSON...', + required: 'requerido', + generate: 'Generar', + import: 'Importar desde JSON', + resetDefaults: 'Restablecer', + }, }, knowledgeRetrieval: { queryVariable: 'Variable de consulta', @@ -416,6 +460,31 @@ const translation = { url: 'URL segmentada', metadata: 'Metadatos adicionales', }, + metadata: { + options: { + disabled: { + subTitle: 'No habilitar el filtrado de metadatos', + }, + automatic: { + subTitle: 'Generar automáticamente condiciones de filtrado de metadatos basadas en la consulta del usuario', + desc: 'Generar automáticamente condiciones de filtrado de metadatos basadas en la variable de consulta', + }, + manual: { + title: 'Manual', + subTitle: 'Añadir manualmente condiciones de filtro de metadatos', + }, + }, + panel: { + conditions: 'Condiciones', + title: 'Condiciones del filtro de metadatos', + add: 'Agregar condición', + select: 'Seleccionar variable...', + datePlaceholder: 'Elige una hora...', + placeholder: 'Ingrese valor', + search: 'Buscar metadatos', + }, + title: 'Filtrado de Metadatos', + }, }, http: { inputVars: 'Variables de entrada', @@ -505,6 +574,8 @@ const translation = { 'exists': 'Existe', 'all of': 'Todos los', 'not exists': 'no existe', + 'after': 'después', + 'before': 'antes', }, enterValue: 'Ingresa un valor', addCondition: 'Agregar condición', @@ -520,6 +591,7 @@ const translation = { }, select: 'Escoger', addSubVariable: 'Sub Variable', + condition: 'Condición', }, variableAssigner: { title: 'Asignar variables', @@ -562,6 +634,8 @@ const translation = { 'overwrite': 'Sobrescribir', '/=': '/=', 'set': 'Poner', + 'remove-last': 'Eliminar último', + 'remove-first': 'Eliminar primero', }, 'variables': 'Variables', 'setParameter': 'Establecer parámetro...', @@ -572,7 +646,6 @@ const translation = { 'assignedVarsDescription': 'Las variables asignadas deben ser variables grabables, como las variables de conversación.', }, tool: { - toAuthorize: 'Para autorizar', inputVars: 'Variables de entrada', outputVars: { text: 'Contenido generado por la herramienta', @@ -585,6 +658,7 @@ const translation = { }, json: 'JSON generado por la herramienta', }, + authorize: 'autorizar', }, questionClassifiers: { model: 'modelo', @@ -769,6 +843,37 @@ const translation = { toolNotAuthorizedTooltip: '{{herramienta}} No autorizado', modelNotSelected: 'Modelo no seleccionado', }, + loop: { + ErrorMethod: { + removeAbnormalOutput: 'Eliminar salida anormal', + operationTerminated: 'Terminado', + continueOnError: 'Continuar con el error', + }, + loopMaxCount: 'Conteo máximo de bucles', + output: 'Variable de Salida', + currentLoopCount: 'Contador de bucles actual: {{count}}', + currentLoop: 'Bucle de corriente', + loopNode: 'Nodo de bucle', + deleteDesc: 'Eliminar el nodo de bucle eliminará todos los nodos hijos', + totalLoopCount: 'Total de loops: {{count}}', + comma: ',', + finalLoopVariables: 'Variables del Bucle Final', + inputMode: 'Modo de entrada', + deleteTitle: '¿Eliminar nodo de bucle?', + setLoopVariables: 'Establecer variables dentro del alcance del bucle', + loop_other: '{{count}} bucles', + breakCondition: 'Condición de terminación del bucle', + loopMaxCountError: 'Por favor, introduce un conteo máximo de bucles válido, que varíe entre 1 y {{maxCount}}.', + exitConditionTip: 'Un nodo de bucle necesita al menos una condición de salida', + error_one: '{{count}} Error', + loop_one: '{{count}} Bucle', + initialLoopVariables: 'Variables de Bucle Iniciales', + errorResponseMethod: 'Método de respuesta de error', + breakConditionTip: 'Solo se pueden hacer referencia a las variables dentro de bucles con condiciones de terminación y variables de conversación.', + error_other: '{{count}} Errores', + loopVariables: 'Variables de bucle', + variableName: 'Nombre de Variable', + }, }, tracing: { stopBy: 'Pásate por {{usuario}}', @@ -780,6 +885,36 @@ const translation = { noAssignedVars: 'No hay variables asignadas disponibles', conversationVars: 'Variables de conversación', }, + versionHistory: { + filter: { + onlyYours: 'Solo tuyo', + onlyShowNamedVersions: 'Solo muestra versiones nombradas', + empty: 'No se encontró un historial de versiones coincidente.', + reset: 'Restablecer filtro', + }, + editField: { + titleLengthLimit: 'El título no puede exceder {{limit}} caracteres', + title: 'Título', + releaseNotesLengthLimit: 'Las notas de lanzamiento no pueden exceder {{limit}} caracteres', + releaseNotes: 'Notas de Lanzamiento', + }, + action: { + deleteSuccess: 'Versión eliminada', + updateSuccess: 'Versión actualizada', + restoreFailure: 'Error al restaurar la versión', + deleteFailure: 'Error al eliminar la versión', + updateFailure: 'Error al actualizar la versión', + restoreSuccess: 'Versión restaurada', + }, + releaseNotesPlaceholder: 'Describe lo que cambió', + restorationTip: 'Después de la restauración de la versión, el borrador actual será sobrescrito.', + nameThisVersion: 'Nombra esta versión', + defaultName: 'Versión sin título', + title: 'Versiones', + deletionTip: 'La eliminación es irreversible, por favor confirma.', + currentDraft: 'Borrador Actual', + editVersionInfo: 'Editar información de la versión', + }, } export default translation diff --git a/web/i18n/fa-IR/app-debug.ts b/web/i18n/fa-IR/app-debug.ts index 00891f3b17..5cf9c15efe 100644 --- a/web/i18n/fa-IR/app-debug.ts +++ b/web/i18n/fa-IR/app-debug.ts @@ -315,6 +315,7 @@ const translation = { 'inputPlaceholder': 'لطفاً وارد کنید', 'content': 'محتوا', 'required': 'مورد نیاز', + 'hide': 'مخفی کردن', 'errorMsg': { varNameRequired: 'نام متغیر مورد نیاز است', labelNameRequired: 'نام برچسب مورد نیاز است', diff --git a/web/i18n/fa-IR/app-overview.ts b/web/i18n/fa-IR/app-overview.ts index cf3368dd6f..891002b4e4 100644 --- a/web/i18n/fa-IR/app-overview.ts +++ b/web/i18n/fa-IR/app-overview.ts @@ -35,20 +35,20 @@ const translation = { preview: 'پیش‌نمایش', regenerate: 'تولید مجدد', regenerateNotice: 'آیا می‌خواهید آدرس عمومی را دوباره تولید کنید؟', - preUseReminder: 'لطفاً قبل از ادامه، WebApp را فعال کنید.', + preUseReminder: 'لطفاً قبل از ادامه، web app را فعال کنید.', settings: { entry: 'تنظیمات', - title: 'تنظیمات WebApp', - webName: 'نام WebApp', - webDesc: 'توضیحات WebApp', + title: 'تنظیمات web app', + webName: 'نام web app', + webDesc: 'توضیحات web app', webDescTip: 'این متن در سمت مشتری نمایش داده می‌شود و راهنمایی‌های اولیه در مورد نحوه استفاده از برنامه را ارائه می‌دهد', - webDescPlaceholder: 'توضیحات WebApp را وارد کنید', + webDescPlaceholder: 'توضیحات web app را وارد کنید', language: 'زبان', workflow: { title: 'مراحل کاری', show: 'نمایش', hide: 'مخفی کردن', - showDesc: 'نمایش یا پنهان کردن جزئیات گردش کار در WebApp', + showDesc: 'نمایش یا پنهان کردن جزئیات گردش کار در web app', subTitle: 'جزئیات گردش کار', }, chatColorTheme: 'تم رنگی چت', @@ -70,10 +70,10 @@ const translation = { copyrightTooltip: 'لطفا به طرح حرفه ای یا بالاتر ارتقا دهید', }, sso: { - title: 'WebApp SSO', + title: 'web app SSO', label: 'احراز هویت SSO', - description: 'همه کاربران باید قبل از استفاده از WebApp با SSO وارد شوند', - tooltip: 'برای فعال کردن WebApp SSO با سرپرست تماس بگیرید', + description: 'همه کاربران باید قبل از استفاده از web app با SSO وارد شوند', + tooltip: 'برای فعال کردن web app SSO با سرپرست تماس بگیرید', }, modalTip: 'تنظیمات برنامه وب سمت مشتری.', }, @@ -95,7 +95,7 @@ const translation = { customize: { way: 'راه', entry: 'سفارشی‌سازی', - title: 'سفارشی‌سازی WebApp AI', + title: 'سفارشی‌سازی web app AI', explanation: 'شما می‌توانید ظاهر جلویی برنامه وب را برای برآوردن نیازهای سناریو و سبک خود سفارشی کنید.', way1: { name: 'کلاینت را شاخه کنید، آن را تغییر دهید و در Vercel مستقر کنید (توصیه می‌شود)', diff --git a/web/i18n/fa-IR/app.ts b/web/i18n/fa-IR/app.ts index 70e7801810..13473d21f5 100644 --- a/web/i18n/fa-IR/app.ts +++ b/web/i18n/fa-IR/app.ts @@ -79,10 +79,10 @@ const translation = { completionShortDescription: 'دستیار هوش مصنوعی برای تسک های تولید متن', foundResult: '{{تعداد}} نتیجه', chatbotUserDescription: 'به سرعت یک چت بات مبتنی بر LLM با پیکربندی ساده بسازید. بعدا می توانید به Chatflow بروید.', - chooseAppType: 'نوع برنامه را انتخاب کنید', + chooseAppType: 'انتخاب نوع برنامه', foundResults: '{{تعداد}} نتیجه', noIdeaTip: 'ایده ای ندارید؟ قالب های ما را بررسی کنید', - forBeginners: 'برای مبتدیان', + forBeginners: 'انواع برنامه‌های پایه‌تر', noAppsFound: 'هیچ برنامه ای یافت نشد', chatbotShortDescription: 'چت بات مبتنی بر LLM با راه اندازی ساده', optional: 'اختیاری', @@ -91,11 +91,11 @@ const translation = { noTemplateFoundTip: 'سعی کنید با استفاده از کلمات کلیدی مختلف جستجو کنید.', noTemplateFound: 'هیچ الگویی یافت نشد', forAdvanced: 'برای کاربران پیشرفته', - workflowShortDescription: 'ارکستراسیون برای تسک های اتوماسیون تک نوبت', - workflowUserDescription: 'ارکستراسیون گردش کار برای کارهای تک مرحله ای مانند اتوماسیون و پردازش دسته ای.', - advancedUserDescription: 'ارکستراسیون گردش کار برای کارهای گفتگوی پیچیده چند مرحله ای با قابلیت های حافظه.', + workflowShortDescription: 'جریان عاملی برای اتوماسیون‌های هوشمند', + workflowUserDescription: 'ساخت بصری گردش‌کارهای خودکار هوش مصنوعی با سادگی کشیدن و رها کردن', + advancedUserDescription: 'گردش‌کار با ویژگی‌های حافظه اضافی و رابط چت‌بات', completionUserDescription: 'به سرعت یک دستیار هوش مصنوعی برای وظایف تولید متن با پیکربندی ساده بسازید.', - advancedShortDescription: 'گردش کار برای دیالوگ های پیچیده چند چرخشی با حافظه', + advancedShortDescription: 'گردش‌کار پیشرفته برای گفتگوهای چند مرحله‌ای', agentUserDescription: 'یک عامل هوشمند که قادر به استدلال تکراری و استفاده از ابزار مستقل برای دستیابی به اهداف وظیفه است.', }, editApp: 'ویرایش اطلاعات', @@ -163,11 +163,15 @@ const translation = { title: 'اوپیک', description: 'Opik یک پلت فرم منبع باز برای ارزیابی، آزمایش و نظارت بر برنامه های LLM است.', }, + weave: { + title: 'بافندگی', + description: 'ویو یک پلتفرم متن باز برای ارزیابی، آزمایش و نظارت بر برنامه‌های LLM است.', + }, }, answerIcon: { - descriptionInExplore: 'آیا از نماد WebApp برای جایگزینی 🤖 در Explore استفاده کنیم یا خیر', - description: 'آیا از نماد WebApp برای جایگزینی 🤖 در برنامه مشترک استفاده کنیم یا خیر', - title: 'از نماد WebApp برای جایگزینی 🤖 استفاده کنید', + descriptionInExplore: 'آیا از نماد web app برای جایگزینی 🤖 در Explore استفاده کنیم یا خیر', + description: 'آیا از نماد web app برای جایگزینی 🤖 در برنامه مشترک استفاده کنیم یا خیر', + title: 'از نماد web app برای جایگزینی 🤖 استفاده کنید', }, mermaid: { handDrawn: 'دست کشیده شده', @@ -194,6 +198,54 @@ const translation = { label: 'برنامه', placeholder: 'برنامه ای را انتخاب کنید...', }, + structOutput: { + required: 'ضروری', + modelNotSupported: 'مدل پشتیبانی نمی شود', + notConfiguredTip: 'خروجی ساختاری هنوز تنظیم نشده است', + structured: 'ساختار یافته', + configure: 'تنظیمات', + moreFillTip: 'نمایش حداکثر ۱۰ سطح تو در تو', + LLMResponse: 'پاسخ مدل زبان بزرگ', + modelNotSupportedTip: 'مدل فعلی این ویژگی را پشتیبانی نمی‌کند و به‌طور خودکار به تزریق درخواست تنزل پیدا می‌کند.', + structuredTip: 'خروجی‌های ساختاری یک ویژگی است که تضمین می‌کند مدل همیشه پاسخ‌هایی تولید می‌کند که به طرح JSON ارائه شده شما پایبند باشد.', + }, + accessItemsDescription: { + specific: 'فقط گروه‌ها یا اعضای خاصی می‌توانند به اپلیکیشن وب دسترسی پیدا کنند.', + anyone: 'هر کسی می‌تواند به وب‌اپلیکیشن دسترسی پیدا کند', + organization: 'هر کسی در سازمان می‌تواند به اپلیکیشن وب دسترسی پیدا کند.', + external: 'تنها کاربران خارجی تأیید شده می‌توانند به برنامه وب دسترسی پیدا کنند.', + }, + accessControlDialog: { + accessItems: { + specific: 'گروه‌ها یا اعضای خاص', + organization: 'فقط اعضای داخل سازمان', + anyone: 'هر کسی که لینک را داشته باشد', + external: 'کاربران خارجی تأیید شده', + }, + operateGroupAndMember: { + searchPlaceholder: 'گروه‌ها و اعضا را جستجو کنید', + allMembers: 'تمام اعضا', + noResult: 'نتیجه‌ای نیست', + expand: 'گسترش', + }, + description: 'مجوزهای دسترسی به برنامه وب را تنظیم کنید', + accessLabel: 'چه کسی به آن دسترسی دارد', + groups_one: '{{count}} گروه', + groups_other: '{{count}} گروه', + members_one: '{{count}} عضو', + members_other: '{{count}} عضو', + noGroupsOrMembers: 'هیچ گروه یا عضوی انتخاب نشده است', + title: 'کنترل دسترسی به وب اپلیکیشن', + updateSuccess: 'به‌روز رسانی با موفقیت انجام شد', + webAppSSONotEnabledTip: 'لطفاً با مدیر شرکت تماس بگیرید تا روش احراز هویت برنامه وب را پیکربندی کند.', + }, + publishApp: { + notSet: 'تنظیم نشده است', + notSetDesc: 'در حال حاضر هیچ‌کس نمی‌تواند به برنامه وب دسترسی پیدا کند. لطفاً مجوزها را تنظیم کنید.', + title: 'چه کسی می‌تواند به برنامه وب دسترسی داشته باشد؟', + }, + accessControl: 'کنترل دسترسی به وب اپلیکیشن', + noAccessPermission: 'دسترسی به برنامه وب مجاز نیست', } export default translation diff --git a/web/i18n/fa-IR/billing.ts b/web/i18n/fa-IR/billing.ts index 480c31f742..e4de29ced5 100644 --- a/web/i18n/fa-IR/billing.ts +++ b/web/i18n/fa-IR/billing.ts @@ -70,6 +70,7 @@ const translation = { messageRequest: { title: 'اعتبارات پیام', tooltip: 'سهمیه‌های فراخوانی پیام برای طرح‌های مختلف با استفاده از مدل‌های OpenAI (به جز gpt4). پیام‌های بیش از حد محدودیت از کلید API OpenAI شما استفاده می‌کنند.', + titlePerMonth: '{{count,number}} پیام در ماه', }, annotatedResponse: { title: 'محدودیت‌های سهمیه حاشیه‌نویسی', @@ -77,27 +78,94 @@ const translation = { }, ragAPIRequestTooltip: 'به تعداد درخواست‌های API که فقط قابلیت‌های پردازش پایگاه دانش Dify را فراخوانی می‌کنند اشاره دارد.', receiptInfo: 'فقط صاحب تیم و مدیر تیم می‌توانند اشتراک تهیه کنند و اطلاعات صورتحساب را مشاهده کنند', + apiRateLimitUnit: '{{count,number}}/روز', + cloud: 'سرویس ابری', + documents: '{{count,number}} سندهای دانش', + self: 'خود میزبان', + apiRateLimit: 'محدودیت نرخ API', + annualBilling: 'صورتحساب سالانه', + freeTrialTip: 'آزمایش رایگان ۲۰۰ تماس OpenAI.', + teamMember_other: '{{count,number}} اعضای تیم', + unlimitedApiRate: 'هیچ محدودیتی برای نرخ API وجود ندارد.', + freeTrialTipPrefix: 'ثبت‌نام کنید و یک', + comparePlanAndFeatures: 'طرح ها و ویژگی ها را مقایسه کنید', + teamMember_one: '{{count,number}} عضو تیم', + priceTip: 'برای هر محیط کار/', + documentsTooltip: 'حجم مستندات وارد شده از منبع داده‌های دانش.', + freeTrialTipSuffix: 'نیاز به کارت اعتباری نیست', + teamWorkspace: '{{count,number}} فضاي کار تيمي', + getStarted: 'شروع کنید', + documentsRequestQuota: '{{count,number}}/دقیقه محدودیت نرخ درخواست دانش', + apiRateLimitTooltip: 'محدودیت نرخ API برای همه درخواست‌های انجام شده از طریق API Dify اعمال می‌شود، از جمله تولید متن، محاوره‌های چت، اجرای گردش‌های کار و پردازش اسناد.', + documentsRequestQuotaTooltip: 'تعیین می‌کند که تعداد کلی اقداماتی که یک فضای کاری می‌تواند در هر دقیقه در داخل پایگاه دانش انجام دهد، شامل ایجاد مجموعه داده، حذف، به‌روزرسانی، بارگذاری مستندات، تغییرات، بایگانی و پرسش از پایگاه دانش است. این معیار برای ارزیابی عملکرد درخواست‌های پایگاه دانش استفاده می‌شود. به عنوان مثال، اگر یک کاربر Sandbox در طی یک دقیقه 10 آزمایش متوالی انجام دهد، فضای کاری او به طور موقت از انجام اقدامات زیر در دقیقه بعدی محدود خواهد شد: ایجاد مجموعه داده، حذف، به‌روزرسانی و بارگذاری یا تغییر مستندات.', }, plans: { sandbox: { name: 'محیط آزمایشی', description: '200 بار آزمایش رایگان GPT', includesTitle: 'شامل:', + for: 'دوره آزمایشی رایگان قابلیت‌های اصلی', }, professional: { name: 'حرفه‌ای', description: 'برای افراد و تیم‌های کوچک برای باز کردن قدرت بیشتر به طور مقرون به صرفه.', includesTitle: 'همه چیز در طرح رایگان، به علاوه:', + for: 'برای توسعه‌دهندگان مستقل/تیم‌های کوچک', }, team: { name: 'تیم', description: 'همکاری بدون محدودیت و لذت بردن از عملکرد برتر.', includesTitle: 'همه چیز در طرح حرفه‌ای، به علاوه:', + for: 'برای تیم‌های متوسط', }, enterprise: { name: 'سازمانی', description: 'دریافت کامل‌ترین قابلیت‌ها و پشتیبانی برای سیستم‌های بزرگ و بحرانی.', includesTitle: 'همه چیز در طرح تیم، به علاوه:', + features: { + 0: 'راهکارهای استقرار مقیاس‌پذیر در سطح سازمانی', + 8: 'پشتیبانی فنی حرفه‌ای', + 3: 'چندین فضای کاری و مدیریت سازمانی', + 5: 'SLA های توافق شده توسط شرکای Dify', + 4: 'SSO', + 2: 'ویژگی‌های انحصاری سازمانی', + 1: 'مجوز صدور مجوز تجاری', + 6: 'امنیت و کنترل‌های پیشرفته', + 7: 'به‌روزرسانی‌ها و نگهداری توسط دیفی به‌طور رسمی', + }, + price: 'سفارشی', + btnText: 'تماس با فروش', + for: 'برای تیم‌های بزرگ', + priceTip: 'فقط صورتحساب سالیانه', + }, + community: { + features: { + 0: 'تمام ویژگی‌های اصلی منتشر شده در مخزن عمومی', + 2: 'با رعایت مجوز منبع باز دیفی', + 1: 'فضای کاری واحد', + }, + btnText: 'شروع کنید با جامعه', + price: 'رایگان', + includesTitle: 'ویژگی‌های رایگان:', + description: 'برای کاربران فردی، تیم‌های کوچک یا پروژه‌های غیر تجاری', + name: 'جامعه', + for: 'برای کاربران فردی، تیم‌های کوچک یا پروژه‌های غیر تجاری', + }, + premium: { + features: { + 1: 'محل کار واحد', + 0: 'قابل اطمینان خودمدیریتی توسط ارائه‌دهندگان مختلف ابر', + 2: 'شعار و سفارشی‌سازی برند وب‌اپلیکیشن', + 3: 'پشتیبانی اولویت ایمیل و چت', + }, + btnText: 'گرفتن نسخه پریمیوم در', + description: 'برای سازمان‌ها و تیم‌های میان‌رده', + price: 'قابل گسترش', + includesTitle: 'همه چیز از جامعه، به علاوه:', + for: 'برای سازمان‌ها و تیم‌های میان‌رده', + name: 'پیشرفته', + priceTip: 'بر اساس بازار ابری', + comingSoon: 'پشتیبانی مایکروسافت آژور و گوگل کلود به زودی در دسترس خواهد بود', }, }, vectorSpace: { @@ -107,12 +175,26 @@ const translation = { apps: { fullTipLine1: 'طرح خود را ارتقاء دهید تا', fullTipLine2: 'اپلیکیشن‌های بیشتری بسازید.', + fullTip2: 'محدودیت طرح به پایان رسید', + contactUs: 'با ما تماس بگیرید', + fullTip1: 'به‌روزرسانی کنید تا برنامه‌های بیشتری ایجاد کنید', + fullTip1des: 'شما به محدودیت ساخت برنامه‌ها در این طرح رسیده‌اید', + fullTip2des: 'توصیه می‌شود برنامه‌های غیرفعال را پاک کنید تا فضای استفاده را آزاد کنید، یا با ما تماس بگیرید.', }, annotatedResponse: { fullTipLine1: 'طرح خود را ارتقاء دهید تا', fullTipLine2: 'مکالمات بیشتری را حاشیه‌نویسی کنید.', quotaTitle: 'سهمیه پاسخ حاشیه‌نویسی', }, + usagePage: { + documentsUploadQuota: 'حجم بارگذاری اسناد', + vectorSpace: 'ذخیره‌سازی داده‌های دانش', + teamMembers: 'اعضای تیم', + annotationQuota: 'سهام حاشیه', + buildApps: 'ساخت برنامه ها', + vectorSpaceTooltip: 'سندهایی که با حالت نمایه‌سازی با کیفیت بالا تهیه می‌شوند، منابع ذخیره‌سازی داده‌های دانش را مصرف خواهند کرد. زمانی که ذخیره‌سازی داده‌های دانش به حد خود برسد، اسناد جدید بارگزاری نخواهند شد.', + }, + teamMembers: 'اعضای تیم', } export default translation diff --git a/web/i18n/fa-IR/common.ts b/web/i18n/fa-IR/common.ts index 44d6bb006b..7cd9a89684 100644 --- a/web/i18n/fa-IR/common.ts +++ b/web/i18n/fa-IR/common.ts @@ -54,6 +54,10 @@ const translation = { copied: 'کپی', viewDetails: 'دیدن جزئیات', in: 'در', + downloadFailed: 'دانلود ناموفق بود. لطفاً بعداً دوباره تلاش کنید.', + more: 'بیشتر', + format: 'قالب', + downloadSuccess: 'دانلود کامل شد.', }, errorMsg: { fieldRequired: '{{field}} الزامی است', @@ -145,6 +149,8 @@ const translation = { newDataset: 'ایجاد دانش', tools: 'ابزارها', exploreMarketplace: 'بازار را کاوش کنید', + appDetail: 'جزئیات برنامه', + account: 'حساب', }, userProfile: { settings: 'تنظیمات', @@ -157,6 +163,9 @@ const translation = { community: 'انجمن', about: 'درباره', logout: 'خروج', + github: 'گیت‌هاب', + compliance: 'انطباق', + support: 'پشتیبانی', }, settings: { accountGroup: 'حساب کاربری', @@ -206,6 +215,9 @@ const translation = { deleteSuccessTip: 'حساب شما برای پایان دادن به حذف به زمان نیاز دارد. وقتی همه چیز تمام شد به شما ایمیل خواهیم زد.', deletePrivacyLinkTip: 'برای کسب اطلاعات بیشتر در مورد نحوه مدیریت داده های شما، لطفا به ما مراجعه کنید', feedbackLabel: 'به ما بگویید چرا حساب خود را حذف کرده اید؟', + editWorkspaceInfo: 'ویرایش اطلاعات فضای کار', + workspaceName: 'نام فضای کاری', + workspaceIcon: 'آیکون محیط کار', }, members: { team: 'تیم', @@ -459,7 +471,7 @@ const translation = { apiBasedExtension: { title: 'افزونه‌های مبتنی بر API مدیریت متمرکز API را فراهم می‌کنند و پیکربندی را برای استفاده آسان در برنامه‌های Dify ساده می‌کنند.', link: 'نحوه توسعه افزونه API خود را بیاموزید.', - linkUrl: 'https://docs.dify.ai/features/extension/api_based_extension', + linkUrl: 'https://docs.dify.ai/en/guides/extension/api-based-extension/README', add: 'افزودن افزونه API', selector: { title: 'افزونه API', @@ -547,6 +559,7 @@ const translation = { inputPlaceholder: 'با ربات صحبت کنید', thought: 'فکر', thinking: 'تفکر...', + resend: 'دوباره ارسال کنید', }, promptEditor: { placeholder: 'دستور خود را اینجا بنویسید، «{» را وارد کنید تا یک متغیر درج کنید، «/» را وارد کنید تا یک بلوک محتوای دستور درج کنید', @@ -633,10 +646,31 @@ const translation = { license: { expiring_plural: 'انقضا در {{count}} روز', expiring: 'انقضا در یک روز', + unlimited: 'نامحدود', }, pagination: { perPage: 'موارد در هر صفحه', }, + theme: { + auto: 'سیستم', + theme: 'تم', + dark: 'تاریک', + light: 'نور', + }, + compliance: { + soc2Type1: 'گزارش نوع I SOC 2', + sandboxUpgradeTooltip: 'تنها با برنامه حرفه‌ای یا تیمی در دسترس است.', + gdpr: 'GDPR DPA', + soc2Type2: 'گزارش نوع II SOC 2', + iso27001: 'گواهینامه ISO 27001:2022', + professionalUpgradeTooltip: 'تنها با برنامه تیم یا بالاتر در دسترس است.', + }, + imageInput: { + dropImageHere: 'عکس خود را اینجا رها کنید، یا', + supportedFormats: 'از فرمت‌های PNG، JPG، JPEG، WEBP و GIF پشتیبانی می‌کند', + browse: 'مرورگر', + }, + you: 'تو', } export default translation diff --git a/web/i18n/fa-IR/custom.ts b/web/i18n/fa-IR/custom.ts index bcf3f26150..d06f60e983 100644 --- a/web/i18n/fa-IR/custom.ts +++ b/web/i18n/fa-IR/custom.ts @@ -3,6 +3,8 @@ const translation = { upgradeTip: { prefix: 'طرح خود را ارتقا دهید به', suffix: 'تا برند خود را سفارشی کنید.', + title: 'طرح خود را ارتقا دهید', + des: 'طرح خود را ارتقا دهید تا برند خود را سفارشی کنید', }, webapp: { title: 'سفارشی سازی برند وب اپ', diff --git a/web/i18n/fa-IR/dataset-creation.ts b/web/i18n/fa-IR/dataset-creation.ts index a2fded6ffe..4d938bbafe 100644 --- a/web/i18n/fa-IR/dataset-creation.ts +++ b/web/i18n/fa-IR/dataset-creation.ts @@ -27,7 +27,7 @@ const translation = { }, uploader: { title: 'بارگذاری فایل', - button: 'کشیدن و رها کردن فایل، یا', + button: 'فایل ها یا پوشه ها را بکشید و رها کنید یا', browse: 'مرور', tip: 'پشتیبانی از {{supportTypes}}. حداکثر {{size}}MB هر کدام.', validation: { @@ -63,7 +63,7 @@ const translation = { run: 'اجرا', firecrawlTitle: 'استخراج محتوای وب با fireFirecrawl', firecrawlDoc: 'مستندات Firecrawl', - firecrawlDocLink: '<a href="https://docs.dify.ai/guides/knowledge-base/sync-from-website">https://docs.dify.ai/guides/knowledge-base/sync-from-website</a>', + firecrawlDocLink: '<a href="https://docs.dify.ai/en/guides/knowledge-base/create-knowledge-and-upload-documents/import-content-data/sync-from-website">https://docs.dify.ai/en/guides/knowledge-base/create-knowledge-and-upload-documents/import-content-data/sync-from-website</a>', options: 'گزینهها', crawlSubPage: 'خزش صفحات فرعی', limit: 'محدودیت', @@ -87,6 +87,14 @@ const translation = { useSitemap: 'از نقشه سایت استفاده کنید', jinaReaderNotConfiguredDescription: 'با وارد کردن کلید API رایگان خود برای دسترسی، Jina Reader را راه اندازی کنید.', useSitemapTooltip: 'نقشه سایت را دنبال کنید تا سایت را بخزید. در غیر این صورت، Jina Reader بر اساس ارتباط صفحه به صورت تکراری می خزد و صفحات کمتر اما با کیفیت بالاتر را به دست می آورد.', + watercrawlDoc: 'مستندات واتر کراول', + configureFirecrawl: 'تنظیم Firecrawl', + waterCrawlNotConfiguredDescription: 'برای استفاده از Watercrawl، آن را با کلید API پیکربندی کنید.', + waterCrawlNotConfigured: 'Watercrawl پیکربندی نشده است', + configureJinaReader: 'پیکربندی خواننده جینا', + watercrawlDocLink: 'https://docs.dify.ai/fa/guides/knowledge-base/create-knowledge-and-upload-documents/import-content-data/sync-from-website', + watercrawlTitle: 'محتوای وب را با واترکرال استخراج کنید', + configureWatercrawl: 'تنظیم واترکراول', }, cancel: 'لغو', }, @@ -200,6 +208,11 @@ const translation = { description: 'در حال حاضر، پایگاه دانش Dify فقط منابع داده محدودی دارد. کمک به یک منبع داده به پایگاه دانش Dify راهی فوق العاده برای کمک به افزایش انعطاف پذیری و قدرت پلتفرم برای همه کاربران است. راهنمای مشارکت ما شروع کار را آسان می کند. لطفا برای کسب اطلاعات بیشتر روی لینک زیر کلیک کنید.', title: 'به منابع داده دیگر متصل شوید؟', }, + watercrawl: { + getApiKeyLinkText: 'کلید API خود را از watercrawl.dev دریافت کنید', + configWatercrawl: 'تنظیم واترکراول', + apiKeyPlaceholder: 'کلید API از watercrawl.dev', + }, } export default translation diff --git a/web/i18n/fa-IR/dataset-settings.ts b/web/i18n/fa-IR/dataset-settings.ts index 0243929c36..457b784333 100644 --- a/web/i18n/fa-IR/dataset-settings.ts +++ b/web/i18n/fa-IR/dataset-settings.ts @@ -27,6 +27,7 @@ const translation = { learnMore: 'بیشتر بدانید', description: ' درباره روش بازیابی.', longDescription: ' درباره روش بازیابی، می‌توانید در هر زمانی در تنظیمات دانش این را تغییر دهید.', + method: 'روش بازیابی', }, save: 'ذخیره', externalKnowledgeAPI: 'API دانش خارجی', diff --git a/web/i18n/fa-IR/dataset.ts b/web/i18n/fa-IR/dataset.ts index 70012a0590..8ab2fb9179 100644 --- a/web/i18n/fa-IR/dataset.ts +++ b/web/i18n/fa-IR/dataset.ts @@ -168,6 +168,54 @@ const translation = { localDocs: 'اسناد محلی', allKnowledge: 'همه دانش ها', allKnowledgeDescription: 'برای نمایش تمام دانش در این فضای کاری انتخاب کنید. فقط مالک فضای کاری می تواند تمام دانش را مدیریت کند.', + metadata: { + createMetadata: { + name: 'نام', + title: 'متادیتای جدید', + namePlaceholder: 'افزودن نام متاداده', + back: 'عقب', + type: 'نوع', + }, + checkName: { + invalid: 'نام متاداده فقط می‌تواند شامل حروف کوچک، اعداد و زیرخط‌ها باشد و باید با یک حرف کوچک آغاز شود.', + empty: 'نام فراداده نمی‌تواند خالی باشد', + }, + batchEditMetadata: { + multipleValue: 'چندین ارزش', + editDocumentsNum: 'ویرایش {{num}} سند', + applyToAllSelectDocumentTip: 'به‌طور خودکار تمام متاداده‌های ویرایش شده و جدید فوق را برای تمام اسناد انتخاب شده ایجاد کنید، در غیر این صورت ویرایش متاداده فقط به اسنادی که دارای آن هستند اعمال خواهد شد.', + applyToAllSelectDocument: 'به تمام اسناد انتخاب شده اعمال کنید', + editMetadata: 'ویرایش متا داده ها', + }, + selectMetadata: { + search: 'جستجوی متا داده', + newAction: 'متادیتای جدید', + manageAction: 'مدیریت', + }, + datasetMetadata: { + deleteContent: 'آیا از حذف متاداده "{{name}}" اطمینان دارید؟', + builtIn: 'ساخته شده درون‌سازی', + description: 'شما می‌توانید تمام فراداده‌ها را در این دانش مدیریت کنید. تغییرات به هر سندی همزمان می‌شوند.', + deleteTitle: 'برای حذف تأیید کنید', + disabled: 'متعادل', + values: '{{num}} مقدار', + namePlaceholder: 'نام متادیتا', + addMetaData: 'متادیتا اضافه کنید', + builtInDescription: 'متاداده‌های داخلی به‌طور خودکار استخراج و تولید می‌شوند. قبل از استفاده باید فعال شود و قابل ویرایش نیست.', + name: 'نام', + rename: 'تغییر نام', + }, + documentMetadata: { + technicalParameters: 'پارامترهای فنی', + documentInformation: 'اطلاعات سند', + startLabeling: 'شروع برچسب‌گذاری', + metadataToolTip: 'متادیتا به عنوان یک فیلتر حیاتی عمل می‌کند که دقت و ارتباط اطلاعات بازیابی را بهبود می‌بخشد. شما می‌توانید متادیتا را برای این سند در اینجا ویرایش و اضافه کنید.', + }, + addMetadata: 'متادیتا اضافه کنید', + chooseTime: 'زمانی را انتخاب کنید...', + metadata: 'متادیتا', + }, + embeddingModelNotAvailable: 'مدل جاسازی در دسترس نیست.', } export default translation diff --git a/web/i18n/fa-IR/education.ts b/web/i18n/fa-IR/education.ts new file mode 100644 index 0000000000..331c3a6408 --- /dev/null +++ b/web/i18n/fa-IR/education.ts @@ -0,0 +1,47 @@ +const translation = { + toVerifiedTip: { + coupon: 'کوپن انحصاری ۱۰۰٪', + end: 'برای طرح حرفه‌ای دیفی.', + front: 'شما اکنون برای وضعیت تأیید شده آموزشی واجد شرایط هستید. لطفاً اطلاعات تحصیلی خود را در زیر وارد کنید تا فرآیند را کامل کرده و یک دریافت کنید.', + }, + form: { + schoolName: { + title: 'نام مدرسه شما', + placeholder: 'نام رسمی و کامل مدرسه خود را وارد کنید', + }, + schoolRole: { + option: { + teacher: 'معلم', + student: 'دانش آموز', + administrator: 'مدیر مدرسه', + }, + title: 'نقش شما در مدرسه', + }, + terms: { + desc: { + privacyPolicy: 'سیاست حریم خصوصی', + end: 'با ارسال:', + and: 'و', + termsOfService: 'شرایط خدمات', + front: 'اطلاعات شما و استفاده از وضعیت تأیید شده آموزشی تابع شرایط ما است.', + }, + option: { + age: 'من تأیید می‌کنم که حداقل ۱۸ سال سن دارم', + inSchool: 'من تأیید می‌کنم که در مؤسسه‌ای که نام برده شده، ثبت‌نام شده یا استخدام شده‌ام. دیفی ممکن است از من بخواهد مدرکی برای ثبت‌نام/استخدام ارائه دهم. اگر صلاحیتم را اشتباه نمایم، موافقت می‌کنم که هر گونه هزینه‌ای که به‌خاطر وضعیت تحصیلی من ابتدا معاف شده، پرداخت کنم.', + }, + title: 'شرایط و توافقات', + }, + }, + submitError: 'ارسال فرم ناموفق بود. لطفا بعداً دوباره تلاش کنید.', + emailLabel: 'ایمیل فعلی شما', + currentSigned: 'اکنون به عنوان', + rejectContent: 'متاسفانه، شما واجد شرایط وضعیت تأیید شده آموزشی نیستید و به همین دلیل نمی‌توانید کوپن انحصاری ۱۰۰٪ برای طرح حرفه‌ای Dify را در صورت استفاده از این آدرس ایمیل دریافت کنید.', + learn: 'یاد بگیرید چگونه مدارک تحصیلی خود را تأیید کنید', + successContent: 'ما یک کوپن تخفیف ۱۰۰٪ برای طرح حرفه‌ای Dify به حساب شما صادر کرده‌ایم. این کوپن به مدت یک سال اعتبار دارد، لطفاً در بازه اعتبار از آن استفاده کنید.', + toVerified: 'تحصیلات خود را تأیید کنید', + rejectTitle: 'تأییدیه آموزشی دیفی شما رد شده است', + submit: 'ارسال', + successTitle: 'شما آموزش دیفی تأیید شده دارید', +} + +export default translation diff --git a/web/i18n/fa-IR/explore.ts b/web/i18n/fa-IR/explore.ts index f127e5cc62..b2c6708b54 100644 --- a/web/i18n/fa-IR/explore.ts +++ b/web/i18n/fa-IR/explore.ts @@ -37,6 +37,7 @@ const translation = { HR: 'منابع انسانی', Agent: 'عامل', Workflow: 'گردش', + Entertainment: 'سرگرمی', }, } diff --git a/web/i18n/fa-IR/login.ts b/web/i18n/fa-IR/login.ts index 7394ab325f..7d853c7b2d 100644 --- a/web/i18n/fa-IR/login.ts +++ b/web/i18n/fa-IR/login.ts @@ -105,6 +105,11 @@ const translation = { licenseExpiredTip: 'مجوز Dify Enterprise برای فضای کاری شما منقضی شده است. لطفا برای ادامه استفاده از Dify با سرپرست خود تماس بگیرید.', licenseInactiveTip: 'مجوز Dify Enterprise برای فضای کاری شما غیرفعال است. لطفا برای ادامه استفاده از Dify با سرپرست خود تماس بگیرید.', licenseLostTip: 'اتصال سرور مجوز Dify انجام نشد. لطفا برای ادامه استفاده از Dify با سرپرست خود تماس بگیرید.', + webapp: { + disabled: 'احراز هویت وب اپ غیرفعال است. لطفاً با مدیر سیستم تماس بگیرید تا آن را فعال کند. می‌توانید سعی کنید مستقیماً از اپلیکیشن استفاده کنید.', + noLoginMethodTip: 'لطفاً با مدیر سیستم تماس بگیرید تا یک روش احراز هویت اضافه کند.', + noLoginMethod: 'روش احراز هویت برای برنامه وب پیکربندی نشده است', + }, } export default translation diff --git a/web/i18n/fa-IR/plugin.ts b/web/i18n/fa-IR/plugin.ts index 5ecde0ed57..97a03b3461 100644 --- a/web/i18n/fa-IR/plugin.ts +++ b/web/i18n/fa-IR/plugin.ts @@ -62,6 +62,7 @@ const translation = { uninstalledContent: 'این افزونه از مخزن local/GitHub نصب شده است. لطفا پس از نصب استفاده کنید.', unsupportedTitle: 'اکشن پشتیبانی نشده', unsupportedContent2: 'برای تغییر نسخه کلیک کنید.', + toolSetting: 'تنظیمات ابزار', }, endpointDeleteTip: 'حذف نقطه پایانی', disabled: 'غیر فعال', @@ -180,6 +181,8 @@ const translation = { difyMarketplace: 'بازار دیفی', empower: 'توسعه هوش مصنوعی خود را توانمند کنید', discover: 'کشف', + verifiedTip: 'تأیید شده توسط دیفی', + partnerTip: 'تأیید شده توسط یک شریک دیفی', }, task: { installing: 'نصب پلاگین های {{installingLength}}، 0 انجام شد.', @@ -192,7 +195,6 @@ const translation = { searchTools: 'ابزارهای جستجو...', findMoreInMarketplace: 'اطلاعات بیشتر در Marketplace', searchInMarketplace: 'جستجو در Marketplace', - submitPlugin: 'ارسال افزونه', searchCategories: 'دسته بندی ها را جستجو کنید', fromMarketplace: 'از بازار', installPlugin: 'افزونه را نصب کنید', @@ -204,6 +206,12 @@ const translation = { installAction: 'نصب', allCategories: 'همه دسته بندی ها', search: 'جستجو', + metadata: { + title: 'پلاگین ها', + }, + difyVersionNotCompatible: 'نسخه فعلی دیفی با این پلاگین سازگار نیست، لطفاً به نسخه حداقل مورد نیاز به‌روزرسانی کنید: {{minimalDifyVersion}}', + requestAPlugin: 'درخواست یک افزونه', + publishPlugins: 'انتشار افزونه ها', } export default translation diff --git a/web/i18n/fa-IR/share-app.ts b/web/i18n/fa-IR/share-app.ts index f3f1360a92..03ed4e8ea9 100644 --- a/web/i18n/fa-IR/share-app.ts +++ b/web/i18n/fa-IR/share-app.ts @@ -26,6 +26,12 @@ const translation = { }, tryToSolve: 'سعی کنید حل کنید', temporarySystemIssue: 'ببخشید، مشکل موقت سیستمی.', + expand: 'باز کردن', + collapse: 'بستن', + newChatTip: 'قبلاً در یک چت جدید', + viewChatSettings: 'تنظیمات چت را مشاهده کنید', + chatFormTip: 'تنظیمات چت پس از شروع چت قابل تغییر نیستند.', + chatSettingsTitle: 'راه‌اندازی چت جدید', }, generation: { tabs: { @@ -64,6 +70,11 @@ const translation = { moreThanMaxLengthLine: 'ردیف {{rowIndex}}: مقدار {{varName}} نمی‌تواند بیشتر از {{maxLength}} کاراکتر باشد', atLeastOne: 'لطفاً حداقل یک ردیف در فایل بارگذاری شده وارد کنید.', }, + executions: '{{num}} اعدام', + execution: 'اجرا', + }, + login: { + backToHome: 'بازگشت به خانه', }, } diff --git a/web/i18n/fa-IR/time.ts b/web/i18n/fa-IR/time.ts index e2410dd34b..2e4ffea784 100644 --- a/web/i18n/fa-IR/time.ts +++ b/web/i18n/fa-IR/time.ts @@ -1,3 +1,37 @@ -const translation = {} +const translation = { + daysInWeek: { + Sat: 'شنبه', + Sun: 'خورشید', + Mon: 'مون', + Wed: 'چهارشنبه', + Fri: 'جمعه', + Tue: 'سه شنبه', + Thu: 'پنج‌شنبه', + }, + months: { + October: 'اکتبر', + February: 'فوریه', + September: 'سپتامبر', + July: 'جولای', + August: 'اوت', + January: 'ژانویه', + April: 'آوریل', + November: 'نوامبر', + March: 'مارس', + May: 'مه', + June: 'ژوئن', + December: 'دسامبر', + }, + operation: { + pickDate: 'تاریخ را انتخاب کنید', + ok: 'خوب', + now: 'حالا', + cancel: 'لغو', + }, + title: { + pickTime: 'زمان انتخاب کنید', + }, + defaultPlaceholder: 'زمانی را انتخاب کنید...', +} export default translation diff --git a/web/i18n/fa-IR/tools.ts b/web/i18n/fa-IR/tools.ts index dc6146d27e..fddfd2d826 100644 --- a/web/i18n/fa-IR/tools.ts +++ b/web/i18n/fa-IR/tools.ts @@ -15,7 +15,6 @@ const translation = { }, author: 'توسط', auth: { - unauthorized: 'برای مجوز دادن', authorized: 'مجوز داده شده', setup: 'تنظیم مجوز برای استفاده', setupModalTitle: 'تنظیم مجوز', diff --git a/web/i18n/fa-IR/workflow.ts b/web/i18n/fa-IR/workflow.ts index 71fd48d5c2..fe7e9372b0 100644 --- a/web/i18n/fa-IR/workflow.ts +++ b/web/i18n/fa-IR/workflow.ts @@ -38,8 +38,6 @@ const translation = { setVarValuePlaceholder: 'تنظیم متغیر', needConnectTip: 'این مرحله به هیچ چیزی متصل نیست', maxTreeDepth: 'حداکثر عمق {{depth}} نود در هر شاخه', - needEndNode: 'بلوک پایان باید اضافه شود', - needAnswerNode: 'بلوک پاسخ باید اضافه شود', workflowProcess: 'فرآیند جریان کار', notRunning: 'هنوز در حال اجرا نیست', previewPlaceholder: 'محتوا را در کادر زیر وارد کنید تا اشکال‌زدایی چت‌بات را شروع کنید', @@ -58,7 +56,6 @@ const translation = { learnMore: 'اطلاعات بیشتر', copy: 'کپی', duplicate: 'تکرار', - addBlock: 'افزودن بلوک', pasteHere: 'چسباندن اینجا', pointerMode: 'حالت اشاره‌گر', handMode: 'حالت دست', @@ -106,6 +103,18 @@ const translation = { addFailureBranch: 'افزودن برنچ Fail', noHistory: 'بدون تاریخچه', loadMore: 'بارگذاری گردش کار بیشتر', + exportPNG: 'صادرات به فرمت PNG', + noExist: 'هیچگونه متغیری وجود ندارد', + exitVersions: 'نسخه‌های خروجی', + referenceVar: 'متغیر مرجع', + exportSVG: 'صادرات به فرمت SVG', + exportJPEG: 'صادرات به فرمت JPEG', + exportImage: 'تصویر را صادر کنید', + versionHistory: 'تاریخچه نسخه', + publishUpdate: 'به‌روزرسانی منتشر کنید', + needEndNode: 'باید گره پایان اضافه شود', + needAnswerNode: 'باید گره پاسخ اضافه شود', + addBlock: 'نود اضافه کنید', }, env: { envPanelTitle: 'متغیرهای محیطی', @@ -167,19 +176,19 @@ const translation = { stepForward_other: '{{count}} قدم به جلو', sessionStart: 'شروع جلسه', currentState: 'وضعیت کنونی', - nodeTitleChange: 'عنوان بلوک تغییر کرده است', - nodeDescriptionChange: 'توضیحات بلوک تغییر کرده است', - nodeDragStop: 'بلوک جابجا شده است', - nodeChange: 'بلوک تغییر کرده است', - nodeConnect: 'بلوک متصل شده است', - nodePaste: 'بلوک چسبانده شده است', - nodeDelete: 'بلوک حذف شده است', - nodeAdd: 'بلوک اضافه شده است', - nodeResize: 'اندازه بلوک تغییر کرده است', noteAdd: 'یادداشت اضافه شده است', noteChange: 'یادداشت تغییر کرده است', noteDelete: 'یادداشت حذف شده است', - edgeDelete: 'بلوک قطع شده است', + nodeDelete: 'نود حذف شد', + nodeAdd: 'نود اضافه شد', + nodeDragStop: 'گره منتقل شد', + edgeDelete: 'گره قطع شده است', + nodeResize: 'اندازه نود تغییر یافته است', + nodePaste: 'نود پیست شده است', + nodeTitleChange: 'عنوان نود تغییر کرد', + nodeConnect: 'گره متصل است', + nodeDescriptionChange: 'شرح نود تغییر کرد', + nodeChange: 'نود تغییر کرد', }, errorMsg: { fieldRequired: '{{field}} الزامی است', @@ -205,10 +214,9 @@ const translation = { testRunIteration: 'تکرار اجرای آزمایشی', back: 'بازگشت', iteration: 'تکرار', + loop: 'حلقه', }, tabs: { - 'searchBlock': 'جستجوی بلوک', - 'blocks': 'بلوک‌ها', 'tools': 'ابزارها', 'allTool': 'همه', 'builtInTool': 'درون‌ساخت', @@ -222,6 +230,8 @@ const translation = { 'searchTool': 'ابزار جستجو', 'plugin': 'افزونه', 'agent': 'استراتژی نمایندگی', + 'blocks': 'گره‌ها', + 'searchBlock': 'گره جستجو', }, blocks: { 'start': 'شروع', @@ -243,6 +253,9 @@ const translation = { 'list-operator': 'عملگر لیست', 'document-extractor': 'استخراج کننده سند', 'agent': 'عامل', + 'loop-start': 'شروع حلقه', + 'loop-end': 'خروج از حلقه', + 'loop': 'حلقه', }, blocksAbout: { 'start': 'پارامترهای اولیه برای راه‌اندازی جریان کار را تعریف کنید', @@ -263,6 +276,8 @@ const translation = { 'list-operator': 'برای فیلتر کردن یا مرتب سازی محتوای آرایه استفاده می شود.', 'document-extractor': 'برای تجزیه اسناد آپلود شده به محتوای متنی استفاده می شود که به راحتی توسط LLM قابل درک است.', 'agent': 'فراخوانی مدل های زبان بزرگ برای پاسخ به سوالات یا پردازش زبان طبیعی', + 'loop-end': 'معادل "شکستن". این گره هیچ مورد پیکربندی ندارد. هنگامی که بدنه حلقه به این گره می‌رسد، حلقه متوقف می‌شود.', + 'loop': 'یک حلقه منطقی را اجرا کنید تا زمانی که شرایط خاتمه برآورده شود یا حداکثر تعداد حلقه به پایان برسد.', }, operator: { zoomIn: 'بزرگ‌نمایی', @@ -273,20 +288,21 @@ const translation = { }, panel: { userInputField: 'فیلد ورودی کاربر', - changeBlock: 'تغییر بلوک', helpLink: 'لینک کمک', about: 'درباره', createdBy: 'ساخته شده توسط', nextStep: 'مرحله بعدی', - addNextStep: 'افزودن بلوک بعدی به این جریان کار', - selectNextStep: 'انتخاب بلوک بعدی', runThisStep: 'اجرا کردن این مرحله', checklist: 'چک‌لیست', checklistTip: 'اطمینان حاصل کنید که همه مسائل قبل از انتشار حل شده‌اند', checklistResolved: 'تمام مسائل حل شده‌اند', - organizeBlocks: 'سازماندهی بلوک‌ها', change: 'تغییر', optional: '(اختیاری)', + moveToThisNode: 'به این گره بروید', + selectNextStep: 'گام بعدی را انتخاب کنید', + changeBlock: 'تغییر گره', + organizeBlocks: 'گره‌ها را سازماندهی کنید', + addNextStep: 'مرحله بعدی را به این فرآیند اضافه کنید', }, nodes: { common: { @@ -404,6 +420,34 @@ const translation = { variable: 'متغیر', }, sysQueryInUser: 'sys.query در پیام کاربر ضروری است', + jsonSchema: { + warningTips: { + saveSchema: 'لطفاً قبل از ذخیره‌سازی طرح، ویرایش فیلد فعلی را کامل کنید.', + }, + import: 'واردات از JSON', + addField: 'فیلد اضافه کنید', + required: 'ضروری', + generatedResult: 'نتیجه تولید شده', + generate: 'تولید کنید', + doc: 'بیشتر درباره خروجی ساختار یافته بیاموزید', + generating: 'تولید طرح‌واره JSON...', + instruction: 'دستورالعمل', + back: 'عقب', + resetDefaults: 'تنظیم مجدد', + showAdvancedOptions: 'نمایش گزینه‌های پیشرفته', + regenerate: 'تولید مجدد', + apply: 'اعمال کنید', + title: 'الگوی خروجی ساختاری', + promptTooltip: 'تبدیل توصیف متنی به یک ساختار استاندارد شده JSON Schema.', + stringValidations: 'اعتبارسنجی رشته', + resultTip: 'این نتیجه تولید شده است. اگر راضی نیستید، می‌توانید به عقب برگردید و درخواست خود را ویرایش کنید.', + descriptionPlaceholder: 'توضیحات را اضافه کنید', + addChildField: 'افزودن فیلد فرزند', + generateJsonSchema: 'ایجاد اسکیما JSON', + promptPlaceholder: 'اسکیمای JSON خود را توصیف کنید...', + fieldNamePlaceholder: 'نام میدان', + generationTip: 'شما می‌توانید از زبان طبیعی برای ایجاد سریع یک طرح‌واره JSON استفاده کنید.', + }, }, knowledgeRetrieval: { queryVariable: 'متغیر جستجو', @@ -416,6 +460,33 @@ const translation = { url: 'URL تقسیم‌بندی شده', metadata: 'سایر متاداده‌ها', }, + metadata: { + options: { + disabled: { + title: 'متعادل', + subTitle: 'عدم فعال‌سازی فیلترهای متاداده', + }, + automatic: { + title: 'خودکار', + desc: 'شرایط فیلتر متاداده را بر اساس متغیر جستجو به صورت خودکار تولید کنید', + subTitle: 'شرایط فیلتر متادیتا را به طور خودکار بر اساس پرسش کاربر تولید کنید', + }, + manual: { + subTitle: 'به‌صورت دستی شرایط فیلتر کردن متادیتا را اضافه کنید', + title: 'دستوری', + }, + }, + panel: { + add: 'شرط اضافه کنید', + placeholder: 'مقدار را وارد کنید', + datePlaceholder: 'زمانی را انتخاب کنید...', + search: 'جستجوی متا داده', + select: 'متغیر را انتخاب کنید...', + title: 'شرایط فیلتر متادیتا', + conditions: 'شرایط', + }, + title: 'فیلتر کردن فراداده', + }, }, http: { inputVars: 'متغیرهای ورودی', @@ -505,6 +576,8 @@ const translation = { 'all of': 'همه از', 'not in': 'نه در', 'exists': 'موجود', + 'after': 'بعد از', + 'before': 'قبل از', }, enterValue: 'مقدار را وارد کنید', addCondition: 'افزودن شرط', @@ -520,6 +593,7 @@ const translation = { }, select: 'انتخاب', addSubVariable: 'متغیر فرعی', + condition: 'شرط', }, variableAssigner: { title: 'تخصیص متغیرها', @@ -562,6 +636,8 @@ const translation = { '-=': '-=', 'append': 'الحاق', '/=': '/=', + 'remove-first': 'حذف اول', + 'remove-last': 'آخرین را حذف کنید', }, 'noVarTip': 'برای افزودن متغیرها روی دکمه "+" کلیک کنید', 'selectAssignedVariable': 'متغیر اختصاص داده شده را انتخاب کنید...', @@ -572,7 +648,6 @@ const translation = { 'varNotSet': 'متغیر NOT Set', }, tool: { - toAuthorize: 'برای مجوز دادن', inputVars: 'متغیرهای ورودی', outputVars: { text: 'محتوای تولید شده توسط ابزار', @@ -585,6 +660,7 @@ const translation = { }, json: 'json تولید شده توسط ابزار', }, + authorize: 'مجوز دادن', }, questionClassifiers: { model: 'مدل', @@ -766,6 +842,38 @@ const translation = { strategyNotFoundDescAndSwitchVersion: 'نسخه افزونه نصب شده این استراتژی را ارائه نمی دهد. برای تغییر نسخه کلیک کنید.', model: 'مدل', }, + loop: { + ErrorMethod: { + removeAbnormalOutput: 'خروجی غیرعادی را حذف کنید', + operationTerminated: 'منحل شد', + continueOnError: 'ادامه در صورت بروز خطا', + }, + variableName: 'نام متغیر', + error_other: '{{count}} خطا', + loopNode: 'گره حلقه', + exitConditionTip: 'یک گره حلقه به حداقل یک شرط خروج نیاز دارد.', + inputMode: 'حالت ورودی', + loop_other: '{{count}} حلقه', + error_one: '{{count}} خطا', + comma: ',', + loopVariables: 'متغیرهای حلقه', + setLoopVariables: 'متغیرها را در محدوده حلقه تنظیم کنید', + loop_one: '{{count}} حلقه', + deleteTitle: 'حذف گره حلقه؟', + input: 'ورودی', + finalLoopVariables: 'متغیرهای نهایی حلقه', + output: 'متغیر خروجی', + totalLoopCount: 'تعداد کل حلقه: {{count}}', + currentLoop: 'حلقه جاری', + initialLoopVariables: 'متغیرهای حلقه اولیه', + errorResponseMethod: 'روش پاسخ خطا', + loopMaxCountError: 'لطفاً یک تعداد حداکثر حلقه معتبر وارد کنید که در بازه‌ی ۱ تا {{maxCount}} باشد.', + deleteDesc: 'حذف نود حلقه همه نودهای فرزند را حذف خواهد کرد', + loopMaxCount: 'حداکثر تعداد حلقه', + currentLoopCount: 'تعداد حلقه‌های فعلی: {{count}}', + breakCondition: 'شرط خاتمه حلقه', + breakConditionTip: 'فقط متغیرهای داخل حلقه‌ها با شرایط خاتمه و متغیرهای گفتگو می‌توانند مورد ارجاع قرار گیرند.', + }, }, tracing: { stopBy: 'متوقف شده توسط {{user}}', @@ -777,6 +885,38 @@ const translation = { assignedVarsDescription: 'متغیرهای اختصاص داده شده باید متغیرهای قابل نوشتن باشند، مانند', noAssignedVars: 'هیچ متغیر اختصاص داده شده در دسترس نیست', }, + versionHistory: { + filter: { + reset: 'بازنشانی فیلتر', + onlyYours: 'فقط مال شماست', + onlyShowNamedVersions: 'فقط نسخه‌های نام‌گذاری شده را نمایش بدهید', + all: 'همه', + empty: 'هیچ تاریخچه نسخه‌ای مطابق پیدا نشد', + }, + editField: { + title: 'عنوان', + releaseNotes: 'یادداشت‌های نسخه', + titleLengthLimit: 'عنوان نمی‌تواند از {{limit}} کاراکتر بیشتر شود', + releaseNotesLengthLimit: 'یادداشت‌های انتشار نمی‌توانند از {{limit}} کاراکتر تجاوز کنند', + }, + action: { + updateSuccess: 'نسخه به‌روزرسانی شد', + deleteSuccess: 'نسخه حذف شد', + restoreSuccess: 'نسخه بازگردانی شده', + deleteFailure: 'حذف نسخه موفق نبود', + restoreFailure: 'بازگرداندن نسخه ناموفق بود', + updateFailure: 'به‌روزرسانی نسخه ناموفق بود', + }, + latest: 'آخرین', + editVersionInfo: 'ویرایش اطلاعات نسخه', + nameThisVersion: 'این نسخه را نامگذاری کنید', + currentDraft: 'پیش نویس فعلی', + defaultName: 'نسخه بدون عنوان', + title: 'نسخه‌ها', + releaseNotesPlaceholder: 'شرح دهید چه چیزی تغییر کرده است', + restorationTip: 'پس از بازیابی نسخه، پیش‌نویس فعلی بازنویسی خواهد شد.', + deletionTip: 'حذف غیرقابل برگشت است، لطفا تأیید کنید.', + }, } export default translation diff --git a/web/i18n/fr-FR/app-debug.ts b/web/i18n/fr-FR/app-debug.ts index 2fd863742b..6671092930 100644 --- a/web/i18n/fr-FR/app-debug.ts +++ b/web/i18n/fr-FR/app-debug.ts @@ -268,6 +268,7 @@ const translation = { 'labelName': 'Label Name', 'inputPlaceholder': 'Please input', 'required': 'Required', + 'hide': 'Caché', 'errorMsg': { varNameRequired: 'Variable name is required', labelNameRequired: 'Label name is required', diff --git a/web/i18n/fr-FR/app-overview.ts b/web/i18n/fr-FR/app-overview.ts index 43cbdf499c..82db5d0be8 100644 --- a/web/i18n/fr-FR/app-overview.ts +++ b/web/i18n/fr-FR/app-overview.ts @@ -30,12 +30,12 @@ const translation = { overview: { title: 'Aperçu', appInfo: { - explanation: 'WebApp AI prête à l\'emploi', + explanation: 'web app AI prête à l\'emploi', accessibleAddress: 'URL publique', preview: 'Aperçu', regenerate: 'Regénérer', regenerateNotice: 'Voulez-vous régénérer l\'URL publique ?', - preUseReminder: 'Veuillez activer WebApp avant de continuer.', + preUseReminder: 'Veuillez activer web app avant de continuer.', settings: { entry: 'Paramètres', title: 'Paramètres de l\'application Web', @@ -48,7 +48,7 @@ const translation = { title: 'Étapes du workflow', show: 'Afficher', hide: 'Masquer', - showDesc: 'Afficher ou masquer les détails du flux de travail dans WebApp', + showDesc: 'Afficher ou masquer les détails du flux de travail dans web app', subTitle: 'Détails du flux de travail', }, chatColorTheme: 'Thème de couleur du chatbot', @@ -71,9 +71,9 @@ const translation = { }, sso: { label: 'Authentification SSO', - title: 'WebApp SSO', - tooltip: 'Contactez l’administrateur pour activer l’authentification unique WebApp', - description: 'Tous les utilisateurs doivent se connecter avec l’authentification unique avant d’utiliser WebApp', + title: 'web app SSO', + tooltip: 'Contactez l’administrateur pour activer l’authentification unique web app', + description: 'Tous les utilisateurs doivent se connecter avec l’authentification unique avant d’utiliser web app', }, modalTip: 'Paramètres de l’application web côté client.', }, diff --git a/web/i18n/fr-FR/app.ts b/web/i18n/fr-FR/app.ts index 005418108a..5c0965815e 100644 --- a/web/i18n/fr-FR/app.ts +++ b/web/i18n/fr-FR/app.ts @@ -73,26 +73,26 @@ const translation = { appCreateDSLErrorPart3: 'Version actuelle de l’application DSL :', appCreateDSLErrorPart2: 'Voulez-vous continuer ?', foundResults: '{{compte}} Résultats', - workflowShortDescription: 'Orchestration pour les tâches d’automatisation à tour unique', + workflowShortDescription: 'Flux agentique pour automatisations intelligentes', agentShortDescription: 'Agent intelligent avec raisonnement et utilisation autonome de l’outil', learnMore: 'Pour en savoir plus', noTemplateFound: 'Aucun modèle trouvé', completionShortDescription: 'Assistant IA pour les tâches de génération de texte', chatbotShortDescription: 'Chatbot basé sur LLM avec configuration simple', - advancedUserDescription: 'Orchestration du flux de travail pour les tâches de dialogue complexes à plusieurs tours avec des capacités de mémoire.', + advancedUserDescription: 'Workflow avec fonctionnalités de mémoire et interface de chatbot.', noTemplateFoundTip: 'Essayez d’effectuer une recherche à l’aide de mots-clés différents.', noAppsFound: 'Aucune application trouvée', - chooseAppType: 'Choisissez le type d’application', + chooseAppType: 'Choisissez un type d’application', forAdvanced: 'POUR LES UTILISATEURS AVANCÉS', chatbotUserDescription: 'Créez rapidement un chatbot basé sur LLM avec une configuration simple. Vous pouvez passer à Chatflow plus tard.', - workflowUserDescription: 'Orchestration du flux de travail pour les tâches ponctuelles telles que l’automatisation et le traitement par lots.', + workflowUserDescription: 'Créez visuellement des flux IA autonomes avec la simplicité du glisser-déposer.', completionUserDescription: 'Créez rapidement un assistant IA pour les tâches de génération de texte avec une configuration simple.', agentUserDescription: 'Un agent intelligent capable d’un raisonnement itératif et d’une utilisation autonome d’outils pour atteindre les objectifs de la tâche.', - forBeginners: 'POUR LES DÉBUTANTS', + forBeginners: 'Types d’applications plus basiques', foundResult: '{{compte}} Résultat', noIdeaTip: 'Pas d’idées ? Consultez nos modèles', optional: 'Optionnel', - advancedShortDescription: 'Flux de travail pour des dialogues complexes à plusieurs tours avec mémoire', + advancedShortDescription: 'Workflow amélioré pour conversations multi-tours', }, editApp: 'Modifier les informations', editAppTitle: 'Modifier les informations de l\'application', @@ -159,11 +159,15 @@ const translation = { description: 'Opik est une plate-forme open-source pour l’évaluation, le test et la surveillance des applications LLM.', title: 'Opik', }, + weave: { + title: 'Tisser', + description: 'Weave est une plateforme open-source pour évaluer, tester et surveiller les applications LLM.', + }, }, answerIcon: { - description: 'S’il faut utiliser l’icône WebApp pour remplacer 🤖 dans l’application partagée', - title: 'Utiliser l’icône WebApp pour remplacer 🤖', - descriptionInExplore: 'Utilisation de l’icône WebApp pour remplacer 🤖 dans Explore', + description: 'S’il faut utiliser l’icône web app pour remplacer 🤖 dans l’application partagée', + title: 'Utiliser l’icône web app pour remplacer 🤖', + descriptionInExplore: 'Utilisation de l’icône web app pour remplacer 🤖 dans Explore', }, importFromDSLUrlPlaceholder: 'Collez le lien DSL ici', importFromDSL: 'Importation à partir d’une DSL', @@ -194,6 +198,54 @@ const translation = { label: 'APPLI', placeholder: 'Sélectionnez une application...', }, + structOutput: { + LLMResponse: 'Réponse LLM', + notConfiguredTip: 'La sortie structurée n\'a pas encore été configurée.', + required: 'Obligatoire', + structuredTip: 'Les sorties structurées sont une fonctionnalité qui garantit que le modèle générera toujours des réponses qui respectent votre schéma JSON fourni.', + modelNotSupportedTip: 'Le modèle actuel ne prend pas en charge cette fonctionnalité et est automatiquement rétrogradé à l\'injection de prompt.', + modelNotSupported: 'Modèle non pris en charge', + moreFillTip: 'Affichage d\'un maximum de 10 niveaux d\'imbrication', + configure: 'Configurer', + structured: 'systématique', + }, + accessItemsDescription: { + anyone: 'Tout le monde peut accéder à l\'application web.', + specific: 'Seules des groupes ou membres spécifiques peuvent accéder à l\'application web.', + organization: 'Toute personne dans l\'organisation peut accéder à l\'application web.', + external: 'Seuls les utilisateurs externes authentifiés peuvent accéder à l\'application Web.', + }, + accessControlDialog: { + accessItems: { + anyone: 'Quiconque avec le lien', + specific: 'Groupes ou membres spécifiques', + organization: 'Seuls les membres au sein de l\'entreprise', + external: 'Utilisateurs externes authentifiés', + }, + operateGroupAndMember: { + searchPlaceholder: 'Rechercher des groupes et des membres', + allMembers: 'Tous les membres', + expand: 'Développer', + noResult: 'Aucun résultat', + }, + title: 'Contrôle d\'accès à l\'application Web', + description: 'Définir les autorisations d\'accès à l\'application web', + accessLabel: 'Qui a accès', + groups_one: '{{count}} GROUPE', + groups_other: '{{count}} GROUPES', + members_one: '{{count}} MEMBRE', + members_other: '{{count}} MEMBRES', + noGroupsOrMembers: 'Aucun groupe ou membre sélectionné', + webAppSSONotEnabledTip: 'Veuillez contacter l\'administrateur de l\'entreprise pour configurer la méthode d\'authentification de l\'application web.', + updateSuccess: 'Mise à jour réussie', + }, + publishApp: { + title: 'Qui peut accéder à l\'application web', + notSet: 'Non défini', + notSetDesc: 'Actuellement, personne ne peut accéder à l\'application web. Veuillez définir les autorisations.', + }, + accessControl: 'Contrôle d\'accès à l\'application Web', + noAccessPermission: 'Pas de permission d\'accéder à l\'application web', } export default translation diff --git a/web/i18n/fr-FR/billing.ts b/web/i18n/fr-FR/billing.ts index 2bcdfd5b23..879a067941 100644 --- a/web/i18n/fr-FR/billing.ts +++ b/web/i18n/fr-FR/billing.ts @@ -69,6 +69,7 @@ const translation = { messageRequest: { title: 'Crédits de message', tooltip: 'Quotas d\'invocation de messages pour divers plans utilisant les modèles OpenAI (sauf gpt4). Les messages dépassant la limite utiliseront votre clé API OpenAI.', + titlePerMonth: '{{count,number}} messages/mois', }, annotatedResponse: { title: 'Limites de quota d\'annotation', @@ -77,27 +78,94 @@ const translation = { ragAPIRequestTooltip: 'Fait référence au nombre d\'appels API invoquant uniquement les capacités de traitement de la base de connaissances de Dify.', receiptInfo: 'Seuls le propriétaire de l\'équipe et l\'administrateur de l\'équipe peuvent s\'abonner et consulter les informations de facturation', annotationQuota: 'Quota d’annotation', + apiRateLimitUnit: '{{count,number}}/jour', + priceTip: 'par espace de travail/', + freeTrialTipSuffix: 'Aucune carte de crédit requise', + teamWorkspace: '{{count,number}} Espace de travail d\'équipe', + teamMember_one: '{{count,number}} membre de l\'équipe', + annualBilling: 'Facturation Annuelle', + self: 'Auto-hébergé', + documentsRequestQuota: '{{count,number}}/min Limite de Fréquence de Demande de Connaissance', + teamMember_other: '{{count,number}} Membres de l\'équipe', + getStarted: 'Commencer', + unlimitedApiRate: 'Pas de limite de taux d\'API', + cloud: 'Service cloud', + documentsTooltip: 'Quota sur le nombre de documents importés à partir de la source de données de connaissance.', + freeTrialTip: 'essai gratuit de 200 appels OpenAI.', + freeTrialTipPrefix: 'Inscrivez-vous et obtenez un', + apiRateLimit: 'Limite de taux de l\'API', + comparePlanAndFeatures: 'Comparer les plans et les fonctionnalités', + apiRateLimitTooltip: 'La limite de taux de l\'API s\'applique à toutes les demandes effectuées via l\'API Dify, y compris la génération de texte, les conversations de chat, les exécutions de flux de travail et le traitement de documents.', + documents: '{{count,number}} Documents de connaissance', + documentsRequestQuotaTooltip: 'Spécifie le nombre total d\'actions qu\'un espace de travail peut effectuer par minute dans la base de connaissances, y compris la création, la suppression, les mises à jour de jeux de données, le téléchargement de documents, les modifications, l\'archivage et les requêtes de la base de connaissances. Ce paramètre est utilisé pour évaluer les performances des requêtes de la base de connaissances. Par exemple, si un utilisateur de Sandbox effectue 10 tests de validité consécutifs en une minute, son espace de travail sera temporairement restreint dans l\'exécution des actions suivantes pendant la minute suivante : création, suppression, mises à jour de jeux de données, et téléchargements ou modifications de documents.', }, plans: { sandbox: { name: 'Bac à sable', description: '200 essais gratuits de GPT', includesTitle: 'Inclus :', + for: 'Essai gratuit des fonctionnalités principales', }, professional: { name: 'Professionnel', description: 'Pour les individus et les petites équipes afin de débloquer plus de puissance à un prix abordable.', includesTitle: 'Tout ce qui est dans le plan gratuit, plus :', + for: 'Pour les développeurs indépendants / petites équipes', }, team: { name: 'Équipe', description: 'Collaborez sans limites et profitez d\'une performance de premier ordre.', includesTitle: 'Tout ce qui est inclus dans le plan Professionnel, plus :', + for: 'Pour les équipes de taille moyenne', }, enterprise: { name: 'Entreprise', description: 'Obtenez toutes les capacités et le support pour les systèmes à grande échelle et critiques pour la mission.', includesTitle: 'Tout ce qui est inclus dans le plan Équipe, plus :', + features: { + 5: 'SLA négociés par Dify Partners', + 1: 'Autorisation de Licence Commerciale', + 2: 'Fonctionnalités exclusives pour les entreprises', + 4: 'SSO', + 8: 'Support Technique Professionnel', + 3: 'Gestion de plusieurs espaces de travail et d\'entreprise', + 6: 'Sécurité et contrôles avancés', + 7: 'Mises à jour et maintenance par Dify Officiellement', + 0: 'Solutions de déploiement évolutives de niveau entreprise', + }, + for: 'Pour les équipes de grande taille', + btnText: 'Contacter les ventes', + priceTip: 'Facturation Annuel Seulement', + price: 'Personnalisé', + }, + community: { + features: { + 2: 'Conforme à la licence open source de Dify', + 1: 'Espace de travail unique', + 0: 'Toutes les fonctionnalités principales publiées dans le référentiel public', + }, + name: 'Communauté', + btnText: 'Commencez avec la communauté', + for: 'Pour les utilisateurs individuels, les petites équipes ou les projets non commerciaux', + includesTitle: 'Fonctionnalités gratuites :', + price: 'Gratuit', + description: 'Pour les utilisateurs individuels, les petites équipes ou les projets non commerciaux', + }, + premium: { + features: { + 3: 'Support par e-mail et chat prioritaire', + 1: 'Espace de travail unique', + 0: 'Fiabilité autogérée par divers fournisseurs de cloud', + 2: 'Personnalisation du logo et de la marque de l\'application Web', + }, + for: 'Pour les organisations et les équipes de taille moyenne', + includesTitle: 'Tout de la communauté, en plus :', + name: 'Premium', + description: 'Pour les organisations et les équipes de taille moyenne', + comingSoon: 'Support de Microsoft Azure et Google Cloud bientôt disponible', + btnText: 'Obtenez Premium dans', + price: 'Scalable', + priceTip: 'Basé sur le marché des nuages', }, }, vectorSpace: { @@ -107,12 +175,26 @@ const translation = { apps: { fullTipLine1: 'Mettez à jour votre plan pour', fullTipLine2: 'construire plus d\'applications.', + fullTip2: 'Limite de plan atteinte', + contactUs: 'Contactez-nous', + fullTip1: 'Mettez à niveau pour créer plus d\'applications', + fullTip2des: 'Il est recommandé de nettoyer les applications inactives pour libérer de l\'espace d\'utilisation, ou de nous contacter.', + fullTip1des: 'Vous avez atteint la limite de création d\'applications avec ce plan.', }, annotatedResponse: { fullTipLine1: 'Mettez à niveau votre plan pour', fullTipLine2: 'annotez plus de conversations.', quotaTitle: 'Quota de Réponse d\'Annotation', }, + usagePage: { + buildApps: 'Construire des applications', + vectorSpace: 'Stockage de données de connaissance', + vectorSpaceTooltip: 'Les documents avec le mode d\'indexation de haute qualité utiliseront des ressources de stockage de données de connaissance. Lorsque le stockage de données de connaissance atteindra la limite, de nouveaux documents ne pourront pas être téléchargés.', + teamMembers: 'Membres de l\'équipe', + annotationQuota: 'Quota d\'annotation', + documentsUploadQuota: 'Quota de téléchargement de documents', + }, + teamMembers: 'Membres de l\'équipe', } export default translation diff --git a/web/i18n/fr-FR/common.ts b/web/i18n/fr-FR/common.ts index a7fc9c671d..f08ef40c4d 100644 --- a/web/i18n/fr-FR/common.ts +++ b/web/i18n/fr-FR/common.ts @@ -54,6 +54,10 @@ const translation = { viewDetails: 'Voir les détails', copied: 'Copied', in: 'dans', + format: 'Format', + downloadFailed: 'Échec du téléchargement. Veuillez réessayer plus tard.', + more: 'Plus', + downloadSuccess: 'Téléchargement terminé.', }, placeholder: { input: 'Veuillez entrer', @@ -141,6 +145,8 @@ const translation = { newDataset: 'Créer des Connaissances', tools: 'Outils', exploreMarketplace: 'Explorer Marketplace', + appDetail: 'Détails de l\'application', + account: 'Compte', }, userProfile: { settings: 'Paramètres', @@ -153,6 +159,9 @@ const translation = { community: 'Communauté', about: 'À propos', logout: 'Se déconnecter', + support: 'Soutien', + github: 'GitHub', + compliance: 'Conformité', }, settings: { accountGroup: 'COMPTE', @@ -202,6 +211,9 @@ const translation = { feedbackTitle: 'Rétroaction', feedbackLabel: 'Dites-nous pourquoi vous avez supprimé votre compte ?', feedbackPlaceholder: 'Optionnel', + workspaceName: 'Nom de l\'espace de travail', + workspaceIcon: 'Icône de l\'espace de travail', + editWorkspaceInfo: 'Modifier les informations de l\'espace de travail', }, members: { team: 'Équipe', @@ -540,9 +552,10 @@ const translation = { vectorHash: 'Hachage vectoriel:', hitScore: 'Score de Récupération:', }, - inputPlaceholder: 'Parler au bot', + inputPlaceholder: 'Parler au {{botName}}', thinking: 'Pensée...', thought: 'Pensée', + resend: 'Renvoyer', }, promptEditor: { placeholder: 'Écrivez votre mot d\'invite ici, entrez \'{\' pour insérer une variable, entrez \'/\' pour insérer un bloc de contenu d\'invite', @@ -633,10 +646,31 @@ const translation = { license: { expiring: 'Expirant dans un jour', expiring_plural: 'Expirant dans {{count}} jours', + unlimited: 'Illimité', }, pagination: { perPage: 'Articles par page', }, + theme: { + auto: 'système', + light: 'lumière', + dark: 'sombre', + theme: 'Thème', + }, + compliance: { + soc2Type1: 'Rapport SOC 2 Type I', + iso27001: 'Certification ISO 27001:2022', + professionalUpgradeTooltip: 'Disponible uniquement avec un plan Équipe ou supérieur.', + gdpr: 'RGPD DPA', + soc2Type2: 'Rapport SOC 2 Type II', + sandboxUpgradeTooltip: 'Disponible uniquement avec un plan Professionnel ou Équipe.', + }, + imageInput: { + browse: 'naviguer', + dropImageHere: 'Déposez votre image ici, ou', + supportedFormats: 'Prend en charge PNG, JPG, JPEG, WEBP et GIF', + }, + you: 'Vous', } export default translation diff --git a/web/i18n/fr-FR/custom.ts b/web/i18n/fr-FR/custom.ts index c0c651cdb7..ddb35cac4f 100644 --- a/web/i18n/fr-FR/custom.ts +++ b/web/i18n/fr-FR/custom.ts @@ -3,9 +3,11 @@ const translation = { upgradeTip: { prefix: 'Mettez à niveau votre plan pour', suffix: 'personnalisez votre marque.', + des: 'Mettez à niveau votre plan pour personnaliser votre marque', + title: 'Améliorez votre plan', }, webapp: { - title: 'Personnalisez la marque WebApp', + title: 'Personnalisez la marque web app', removeBrand: 'Supprimer Propulsé par Dify', changeLogo: 'Changer Propulsé par l\'Image de Marque', changeLogoTip: 'Format SVG ou PNG avec une taille minimum de 40x40px', diff --git a/web/i18n/fr-FR/dataset-creation.ts b/web/i18n/fr-FR/dataset-creation.ts index 9dec33c5ad..6339ceaac2 100644 --- a/web/i18n/fr-FR/dataset-creation.ts +++ b/web/i18n/fr-FR/dataset-creation.ts @@ -22,7 +22,7 @@ const translation = { }, uploader: { title: 'Télécharger le fichier texte', - button: 'Glisser et déposer le fichier, ou', + button: 'Faites glisser et déposez des fichiers ou des dossiers, ou', browse: 'Parcourir', tip: 'Prend en charge {{supportTypes}}. Max {{size}}MB chacun.', validation: { @@ -61,7 +61,7 @@ const translation = { preview: 'Aperçu', crawlSubPage: 'Explorer les sous-pages', configure: 'Configurer', - firecrawlDocLink: 'https://docs.dify.ai/guides/knowledge-base/sync-from-website', + firecrawlDocLink: 'https://docs.dify.ai/en/guides/knowledge-base/create-knowledge-and-upload-documents/import-content-data/sync-from-website', maxDepth: 'Profondeur maximale', fireCrawlNotConfigured: 'Firecrawl n’est pas configuré', firecrawlTitle: 'Extraire du contenu web avec 🔥Firecrawl', @@ -82,6 +82,14 @@ const translation = { jinaReaderNotConfigured: 'Jina Reader n’est pas configuré', chooseProvider: 'Sélectionnez un fournisseur', jinaReaderTitle: 'Convertir l’intégralité du site en Markdown', + watercrawlTitle: 'Extraire du contenu web avec Watercrawl', + watercrawlDoc: 'Documents Watercrawl', + waterCrawlNotConfiguredDescription: 'Configurez Watercrawl avec la clé API pour l\'utiliser.', + configureJinaReader: 'Configurer le lecteur Jina', + configureWatercrawl: 'Configurer Watercrawl', + waterCrawlNotConfigured: 'Watercrawl n\'est pas configuré', + watercrawlDocLink: 'https://docs.dify.ai/fr/guide/base-de-connaissances/créer-des-connaissances-et-télécharger-des-documents/importer-des-données-de-contenu/synchroniser-depuis-un-site-web', + configureFirecrawl: 'Configurer Firecrawl', }, cancel: 'Annuler', }, @@ -200,6 +208,11 @@ const translation = { description: 'Actuellement, la base de connaissances de Dify ne dispose que de sources de données limitées. Contribuer à une source de données dans la base de connaissances Dify est un moyen fantastique d’améliorer la flexibilité et la puissance de la plateforme pour tous les utilisateurs. Notre guide de contribution facilite la prise en main. Veuillez cliquer sur le lien ci-dessous pour en savoir plus.', title: 'Se connecter à d’autres sources de données ?', }, + watercrawl: { + apiKeyPlaceholder: 'Clé API de watercrawl.dev', + configWatercrawl: 'Configurer Watercrawl', + getApiKeyLinkText: 'Obtenez votre clé API sur watercrawl.dev', + }, } export default translation diff --git a/web/i18n/fr-FR/dataset-settings.ts b/web/i18n/fr-FR/dataset-settings.ts index 20d8c47149..9e7529efae 100644 --- a/web/i18n/fr-FR/dataset-settings.ts +++ b/web/i18n/fr-FR/dataset-settings.ts @@ -25,6 +25,7 @@ const translation = { learnMore: 'En savoir plus', description: 'à propos de la méthode de récupération.', longDescription: 'À propos de la méthode de récupération, vous pouvez la modifier à tout moment dans les paramètres de Connaissance.', + method: 'Méthode de récupération', }, save: 'Enregistrer', me: '(Vous)', diff --git a/web/i18n/fr-FR/dataset.ts b/web/i18n/fr-FR/dataset.ts index b288e513ef..c9739e20a1 100644 --- a/web/i18n/fr-FR/dataset.ts +++ b/web/i18n/fr-FR/dataset.ts @@ -168,6 +168,54 @@ const translation = { enable: 'Activer', allKnowledge: 'Toutes les connaissances', allKnowledgeDescription: 'Sélectionnez cette option pour afficher toutes les connaissances dans cet espace de travail. Seul le propriétaire de l’espace de travail peut gérer toutes les connaissances.', + metadata: { + createMetadata: { + name: 'Nom', + title: 'Nouveaux Métadonnées', + namePlaceholder: 'Ajouter le nom des métadonnées', + type: 'Type', + back: 'Retour', + }, + checkName: { + empty: 'Le nom des métadonnées ne peut pas être vide', + invalid: 'Le nom des métadonnées ne peut contenir que des lettres minuscules, des chiffres et des tirets bas et doit commencer par une lettre minuscule.', + }, + batchEditMetadata: { + editMetadata: 'Modifier les métadonnées', + applyToAllSelectDocumentTip: 'Créez automatiquement toutes les métadonnées modifiées et nouvelles pour tous les documents sélectionnés, sinon l\'édition des métadonnées ne s\'appliquera qu\'aux documents qui en ont.', + applyToAllSelectDocument: 'Appliquer à tous les documents sélectionnés', + multipleValue: 'Valeur multiple', + editDocumentsNum: 'Édition de {{num}} documents', + }, + selectMetadata: { + search: 'Rechercher des métadonnées', + newAction: 'Nouveaux métadonnées', + manageAction: 'Gérer', + }, + datasetMetadata: { + description: 'Vous pouvez gérer toutes les métadonnées dans cette connaissance ici. Les modifications seront synchronisées avec chaque document.', + rename: 'Renommer', + builtIn: 'Intégré', + addMetaData: 'Ajouter des métadonnées', + namePlaceholder: 'Nom de métadonnées', + builtInDescription: 'Les métadonnées intégrées sont automatiquement extraites et générées. Elles doivent être activées avant utilisation et ne peuvent pas être modifiées.', + deleteTitle: 'Confirmer la suppression', + values: '{{num}} Valeurs', + deleteContent: 'Êtes-vous sûr de vouloir supprimer les métadonnées "{{name}}" ?', + name: 'Nom', + disabled: 'handicapés', + }, + documentMetadata: { + technicalParameters: 'Paramètres techniques', + metadataToolTip: 'Les métadonnées servent de filtre essentiel qui améliore l\'exactitude et la pertinence de la recherche d\'informations. Vous pouvez modifier et ajouter des métadonnées pour ce document ici.', + documentInformation: 'Informations du document', + startLabeling: 'Commencer l\'étiquetage', + }, + addMetadata: 'Ajouter des métadonnées', + metadata: 'Métadonnées', + chooseTime: 'Choisissez un moment...', + }, + embeddingModelNotAvailable: 'Le modèle d\'embedding n\'est pas disponible.', } export default translation diff --git a/web/i18n/fr-FR/education.ts b/web/i18n/fr-FR/education.ts new file mode 100644 index 0000000000..8dcb687a6d --- /dev/null +++ b/web/i18n/fr-FR/education.ts @@ -0,0 +1,47 @@ +const translation = { + toVerifiedTip: { + front: 'Vous êtes maintenant éligible pour le statut Vérifié en Éducation. Veuillez entrer vos informations éducatives ci-dessous pour compléter le processus et recevoir un', + coupon: 'coupon exclusif 100%', + end: 'pour le Plan Professionnel Dify.', + }, + form: { + schoolName: { + placeholder: 'Entrez le nom officiel et complet de votre école', + title: 'Le nom de votre école', + }, + schoolRole: { + option: { + administrator: 'Administrateur scolaire', + student: 'Étudiant', + teacher: 'Professeur', + }, + title: 'Votre rôle à l\'école', + }, + terms: { + desc: { + and: 'et', + privacyPolicy: 'Politique de confidentialité', + termsOfService: 'Conditions d\'utilisation', + end: '. En soumettant :', + front: 'Vos informations et votre utilisation du statut Éducation Vérifiée sont soumises à notre', + }, + option: { + age: 'Je confirme que j\'ai au moins 18 ans.', + inSchool: 'Je confirme que je suis inscrit ou employé dans l\'institution indiquée. Dify peut demander une preuve d\'inscription/employé. Si je falsifie mon éligibilité, j\'accepte de payer tous les frais initialement annulés en fonction de mon statut éducatif.', + }, + title: 'Conditions et accords', + }, + }, + emailLabel: 'Votre email actuel', + learn: 'Apprenez comment faire vérifier votre éducation', + currentSigned: 'ACTUELLEMENT CONNECTÉ EN TANT QUE', + successTitle: 'Vous avez obtenu une éducation Dify vérifiée.', + successContent: 'Nous avons émis un coupon de réduction de 100 % pour le plan Dify Professionnel sur votre compte. Le coupon est valable pendant un an, veuillez l\'utiliser dans la période de validité.', + rejectTitle: 'Votre vérification éducative Dify a été rejetée.', + submit: 'Soumettre', + submitError: 'L\'envoi du formulaire a échoué. Veuillez réessayer plus tard.', + toVerified: 'Faire vérifier l\'éducation', + rejectContent: 'Malheureusement, vous n\'êtes pas éligible au statut Éducation Vérifié et ne pouvez donc pas recevoir le coupon exclusif de 100 % pour le Plan Professionnel Dify si vous utilisez cette adresse e-mail.', +} + +export default translation diff --git a/web/i18n/fr-FR/explore.ts b/web/i18n/fr-FR/explore.ts index 627ed03e88..d868ebd2df 100644 --- a/web/i18n/fr-FR/explore.ts +++ b/web/i18n/fr-FR/explore.ts @@ -37,6 +37,7 @@ const translation = { HR: 'RH', Agent: 'Agent', Workflow: 'Flux de travail', + Entertainment: 'Divertissement', }, } diff --git a/web/i18n/fr-FR/login.ts b/web/i18n/fr-FR/login.ts index a7a633f330..68a642b3ea 100644 --- a/web/i18n/fr-FR/login.ts +++ b/web/i18n/fr-FR/login.ts @@ -105,6 +105,11 @@ const translation = { licenseLost: 'Licence perdue', licenseExpiredTip: 'La licence Dify Enterprise de votre espace de travail a expiré. Veuillez contacter votre administrateur pour continuer à utiliser Dify.', licenseInactive: 'Licence inactive', + webapp: { + noLoginMethodTip: 'Veuillez contacter l\'administrateur système pour ajouter une méthode d\'authentification.', + noLoginMethod: 'Méthode d\'authentification non configurée pour l\'application web', + disabled: 'L\'authentification de l\'application web est désactivée. Veuillez contacter l\'administrateur du système pour l\'activer. Vous pouvez essayer d\'utiliser l\'application directement.', + }, } export default translation diff --git a/web/i18n/fr-FR/plugin.ts b/web/i18n/fr-FR/plugin.ts index 39fef6e91f..12e35c4e57 100644 --- a/web/i18n/fr-FR/plugin.ts +++ b/web/i18n/fr-FR/plugin.ts @@ -62,6 +62,7 @@ const translation = { settings: 'PARAMÈTRES UTILISATEUR', paramsTip2: 'Lorsque « Automatique » est désactivé, la valeur par défaut est utilisée.', paramsTip1: 'Contrôle les paramètres d’inférence LLM.', + toolSetting: 'Paramètres de l\'outil', }, modelNum: '{{num}} MODÈLES INCLUS', endpointDeleteTip: 'Supprimer le point de terminaison', @@ -180,6 +181,8 @@ const translation = { difyMarketplace: 'Marché Dify', empower: 'Renforcez le développement de votre IA', sortBy: 'Ville noire', + partnerTip: 'Vérifié par un partenaire Dify', + verifiedTip: 'Vérifié par Dify', }, task: { installError: '{{errorLength}} les plugins n’ont pas pu être installés, cliquez pour voir', @@ -190,7 +193,6 @@ const translation = { installing: 'Installation des plugins {{installingLength}}, 0 fait.', }, search: 'Rechercher', - submitPlugin: 'Soumettre le plugin', installAction: 'Installer', from: 'De', searchCategories: 'Catégories de recherche', @@ -204,6 +206,12 @@ const translation = { endpointsEnabled: '{{num}} ensembles de points de terminaison activés', searchTools: 'Outils de recherche...', installPlugin: 'Installer le plugin', + metadata: { + title: 'Plugins', + }, + difyVersionNotCompatible: 'La version actuelle de Dify n\'est pas compatible avec ce plugin, veuillez mettre à niveau vers la version minimale requise : {{minimalDifyVersion}}', + requestAPlugin: 'Demander un plugin', + publishPlugins: 'Publier des plugins', } export default translation diff --git a/web/i18n/fr-FR/share-app.ts b/web/i18n/fr-FR/share-app.ts index 44d03b1e35..2374da70e6 100644 --- a/web/i18n/fr-FR/share-app.ts +++ b/web/i18n/fr-FR/share-app.ts @@ -30,6 +30,12 @@ const translation = { }, tryToSolve: 'Essayez de résoudre', temporarySystemIssue: 'Désolé, problème temporaire du système.', + expand: 'Développer', + collapse: 'Réduire', + chatSettingsTitle: 'Nouvelle configuration de chat', + viewChatSettings: 'Voir les paramètres de chat', + newChatTip: 'Déjà dans une nouvelle discussion', + chatFormTip: 'Les paramètres de chat ne peuvent pas être modifiés une fois que le chat a commencé.', }, generation: { tabs: { @@ -68,6 +74,11 @@ const translation = { moreThanMaxLengthLine: 'Row {{rowIndex}}: {{varName}} value can not be more than {{maxLength}} characters', atLeastOne: 'Veuillez entrer au moins une ligne dans le fichier téléchargé.', }, + executions: '{{num}} EXÉCUTIONS', + execution: 'EXÉCUTION', + }, + login: { + backToHome: 'Retour à l\'accueil', }, } diff --git a/web/i18n/fr-FR/time.ts b/web/i18n/fr-FR/time.ts index e2410dd34b..e05e6dc6b3 100644 --- a/web/i18n/fr-FR/time.ts +++ b/web/i18n/fr-FR/time.ts @@ -1,3 +1,37 @@ -const translation = {} +const translation = { + daysInWeek: { + Sat: 'Sat', + Fri: 'Libre', + Wed: 'Mercredi', + Mon: 'Mon', + Thu: 'Jeudi', + Tue: 'Mardi', + Sun: 'Soleil', + }, + months: { + October: 'octobre', + July: 'Juillet', + June: 'Juin', + December: 'Décembre', + November: 'Novembre', + April: 'Avril', + September: 'Septembre', + February: 'Février', + May: 'Mai', + January: 'Janvier', + August: 'août', + March: 'Mars', + }, + operation: { + now: 'Maintenant', + pickDate: 'Choisir une date', + cancel: 'Annuler', + ok: 'D\'accord', + }, + title: { + pickTime: 'Choisir le temps', + }, + defaultPlaceholder: 'Choisissez un moment...', +} export default translation diff --git a/web/i18n/fr-FR/tools.ts b/web/i18n/fr-FR/tools.ts index faa5193a1c..8f2362daf1 100644 --- a/web/i18n/fr-FR/tools.ts +++ b/web/i18n/fr-FR/tools.ts @@ -14,7 +14,6 @@ const translation = { }, author: 'Par', auth: { - unauthorized: 'Pour Autoriser', authorized: 'Autorisé', setup: 'Mettez en place l\'autorisation à utiliser', setupModalTitle: 'Configurer l\'Autorisation', diff --git a/web/i18n/fr-FR/workflow.ts b/web/i18n/fr-FR/workflow.ts index c345eb32b9..438bd7a2bc 100644 --- a/web/i18n/fr-FR/workflow.ts +++ b/web/i18n/fr-FR/workflow.ts @@ -38,8 +38,6 @@ const translation = { setVarValuePlaceholder: 'Définir la valeur de la variable', needConnectTip: 'Cette étape n\'est connectée à rien', maxTreeDepth: 'Limite maximale de {{depth}} nœuds par branche', - needEndNode: 'Le bloc de fin doit être ajouté', - needAnswerNode: 'Le bloc de réponse doit être ajouté', workflowProcess: 'Processus de flux de travail', notRunning: 'Pas encore en cours d\'exécution', previewPlaceholder: 'Entrez le contenu dans la boîte ci-dessous pour commencer à déboguer le Chatbot', @@ -58,7 +56,6 @@ const translation = { learnMore: 'En savoir plus', copy: 'Copier', duplicate: 'Dupliquer', - addBlock: 'Ajouter un bloc', pasteHere: 'Coller ici', pointerMode: 'Mode pointeur', handMode: 'Mode main', @@ -106,6 +103,18 @@ const translation = { addFailureBranch: 'Ajouter une branche d’échec', loadMore: 'Charger plus de flux de travail', noHistory: 'Pas d’histoire', + exportPNG: 'Exporter en PNG', + exitVersions: 'Versions de sortie', + exportSVG: 'Exporter en SVG', + publishUpdate: 'Publier une mise à jour', + noExist: 'Aucune variable de ce type', + versionHistory: 'Historique des versions', + referenceVar: 'Variable de référence', + exportImage: 'Exporter l\'image', + exportJPEG: 'Exporter en JPEG', + needEndNode: 'Le nœud de fin doit être ajouté', + needAnswerNode: 'Le nœud de réponse doit être ajouté.', + addBlock: 'Ajouter un nœud', }, env: { envPanelTitle: 'Variables d\'Environnement', @@ -167,19 +176,19 @@ const translation = { stepForward_other: '{{count}} pas en avant', sessionStart: 'Début de la session', currentState: 'État actuel', - nodeTitleChange: 'Titre du bloc modifié', - nodeDescriptionChange: 'Description du bloc modifiée', - nodeDragStop: 'Bloc déplacé', - nodeChange: 'Bloc modifié', - nodeConnect: 'Bloc connecté', - nodePaste: 'Bloc collé', - nodeDelete: 'Bloc supprimé', - nodeAdd: 'Bloc ajouté', - nodeResize: 'Bloc redimensionné', noteAdd: 'Note ajoutée', noteChange: 'Note modifiée', noteDelete: 'Note supprimée', - edgeDelete: 'Bloc déconnecté', + nodeConnect: 'Node connecté', + nodeChange: 'Nœud changé', + nodeResize: 'Nœud redimensionné', + edgeDelete: 'Nœud déconnecté', + nodeDelete: 'Nœud supprimé', + nodePaste: 'Node collé', + nodeDragStop: 'Nœud déplacé', + nodeTitleChange: 'Titre du nœud modifié', + nodeAdd: 'Nœud ajouté', + nodeDescriptionChange: 'La description du nœud a changé', }, errorMsg: { fieldRequired: '{{field}} est requis', @@ -205,10 +214,9 @@ const translation = { testRunIteration: 'Itération de l\'exécution de test', back: 'Retour', iteration: 'Itération', + loop: 'Boucle', }, tabs: { - 'searchBlock': 'Rechercher un bloc', - 'blocks': 'Blocs', 'tools': 'Outils', 'allTool': 'Tous', 'builtInTool': 'Intégré', @@ -222,6 +230,8 @@ const translation = { 'searchTool': 'Outil de recherche', 'plugin': 'Plugin', 'agent': 'Stratégie d’agent', + 'blocks': 'Nœuds', + 'searchBlock': 'Nœud de recherche', }, blocks: { 'start': 'Début', @@ -243,6 +253,9 @@ const translation = { 'list-operator': 'Opérateur de liste', 'document-extractor': 'Extracteur de documents', 'agent': 'Agent', + 'loop-end': 'Sortir de la boucle', + 'loop': 'Boucle', + 'loop-start': 'Début de boucle', }, blocksAbout: { 'start': 'Définir les paramètres initiaux pour lancer un flux de travail', @@ -263,6 +276,8 @@ const translation = { 'list-operator': 'Utilisé pour filtrer ou trier le contenu d’un tableau.', 'document-extractor': 'Utilisé pour analyser les documents téléchargés en contenu texte facilement compréhensible par LLM.', 'agent': 'Appel de grands modèles de langage pour répondre à des questions ou traiter le langage naturel', + 'loop': 'Exécutez une boucle de logique jusqu\'à ce que la condition de terminaison soit remplie ou que le nombre maximum de boucles soit atteint.', + 'loop-end': 'Équivalent à "break". Ce nœud n\'a pas d\'éléments de configuration. Lorsque le corps de la boucle atteint ce nœud, la boucle se termine.', }, operator: { zoomIn: 'Zoomer', @@ -273,20 +288,21 @@ const translation = { }, panel: { userInputField: 'Champ de saisie de l\'utilisateur', - changeBlock: 'Changer de bloc', helpLink: 'Lien d\'aide', about: 'À propos', createdBy: 'Créé par', nextStep: 'Étape suivante', - addNextStep: 'Ajouter le prochain bloc dans ce flux de travail', - selectNextStep: 'Sélectionner le prochain bloc', runThisStep: 'Exécuter cette étape', checklist: 'Liste de contrôle', checklistTip: 'Assurez-vous que tous les problèmes sont résolus avant de publier', checklistResolved: 'Tous les problèmes ont été résolus', - organizeBlocks: 'Organiser les blocs', change: 'Modifier', optional: '(facultatif)', + moveToThisNode: 'Déplacer vers ce nœud', + organizeBlocks: 'Organiser les nœuds', + addNextStep: 'Ajoutez la prochaine étape dans ce flux de travail', + selectNextStep: 'Sélectionner la prochaine étape', + changeBlock: 'Changer de nœud', }, nodes: { common: { @@ -404,6 +420,34 @@ const translation = { variable: 'Variable', }, sysQueryInUser: 'sys.query dans le message utilisateur est requis', + jsonSchema: { + warningTips: { + saveSchema: 'Veuillez terminer la modification du champ actuel avant d\'enregistrer le schéma.', + }, + apply: 'Appliquer', + addField: 'Ajouter un champ', + generationTip: 'Vous pouvez utiliser un langage naturel pour créer rapidement un schéma JSON.', + promptPlaceholder: 'Décrivez votre schéma JSON...', + descriptionPlaceholder: 'Ajouter une description', + instruction: 'Instruction', + resetDefaults: 'Réinitialiser', + generatedResult: 'Résultat généré', + fieldNamePlaceholder: 'Nom du champ', + addChildField: 'Ajouter un champ enfant', + back: 'Retour', + showAdvancedOptions: 'Afficher les options avancées', + title: 'Schéma de sortie structuré', + generating: 'Génération de schéma JSON...', + stringValidations: 'Validations de chaîne', + import: 'Importer depuis JSON', + promptTooltip: 'Convertissez la description textuelle en une structure de schéma JSON standardisé.', + generate: 'Générer', + doc: 'En savoir plus sur la sortie structurée', + regenerate: 'Régénérer', + required: 'nécessaire', + generateJsonSchema: 'Générer un schéma JSON', + resultTip: 'Voici le résultat généré. Si vous n\'êtes pas satisfait, vous pouvez revenir en arrière et modifier votre demande.', + }, }, knowledgeRetrieval: { queryVariable: 'Variable de requête', @@ -416,6 +460,33 @@ const translation = { url: 'URL segmentée', metadata: 'Autres métadonnées', }, + metadata: { + options: { + disabled: { + subTitle: 'Ne pas activer le filtrage des métadonnées', + title: 'Handicapé', + }, + automatic: { + subTitle: 'Générer automatiquement des conditions de filtrage des métadonnées en fonction de la requête de l\'utilisateur', + title: 'Automatique', + desc: 'Générer automatiquement des conditions de filtrage de métadonnées en fonction de la variable de requête.', + }, + manual: { + subTitle: 'Ajouter manuellement des conditions de filtrage des métadonnées', + title: 'Manuel', + }, + }, + panel: { + placeholder: 'Entrez la valeur', + add: 'Ajouter une condition', + search: 'Rechercher des métadonnées', + conditions: 'Conditions', + datePlaceholder: 'Choisissez un moment...', + select: 'Sélectionner une variable...', + title: 'Conditions de filtrage des métadonnées', + }, + title: 'Filtrage des métadonnées', + }, }, http: { inputVars: 'Variables de saisie', @@ -505,6 +576,8 @@ const translation = { 'exists': 'Existe', 'all of': 'l’ensemble des', 'not exists': 'n’existe pas', + 'before': 'avant', + 'after': 'après', }, enterValue: 'Entrez la valeur', addCondition: 'Ajouter une condition', @@ -520,6 +593,7 @@ const translation = { }, select: 'Choisir', addSubVariable: 'Sous-variable', + condition: 'Condition', }, variableAssigner: { title: 'Attribuer des variables', @@ -562,6 +636,8 @@ const translation = { 'title': 'Opération', '/=': '/=', 'overwrite': 'Écraser', + 'remove-last': 'Supprimer le dernier', + 'remove-first': 'Retirer le premier', }, 'assignedVarsDescription': 'Les variables affectées doivent être accessibles en écriture, telles que des variables de conversation.', 'noVarTip': 'Cliquez sur le bouton « + » pour ajouter des variables', @@ -572,7 +648,6 @@ const translation = { 'selectAssignedVariable': 'Sélectionner la variable affectée...', }, tool: { - toAuthorize: 'Autoriser', inputVars: 'Variables de saisie', outputVars: { text: 'contenu généré par l\'outil', @@ -585,6 +660,7 @@ const translation = { }, json: 'JSON généré par un outil', }, + authorize: 'Autoriser', }, questionClassifiers: { model: 'modèle', @@ -766,6 +842,38 @@ const translation = { maxIterations: 'Nombre maximal d’itérations', toolNotAuthorizedTooltip: '{{outil}} Non autorisé', }, + loop: { + ErrorMethod: { + operationTerminated: 'Terminé', + removeAbnormalOutput: 'Supprimer la sortie anormale', + continueOnError: 'Continuer en cas d\'erreur', + }, + currentLoop: 'Boucle de courant', + loopMaxCount: 'Nombre maximum de boucles', + loop_one: '{{count}} Boucle', + output: 'Variable de sortie', + error_other: '{{count}} erreurs', + loopMaxCountError: 'Veuillez entrer un nombre maximal de boucles valide, compris entre 1 et {{maxCount}}.', + totalLoopCount: 'Nombre total de boucles : {{count}}', + initialLoopVariables: 'Variables de boucle initiales', + breakCondition: 'Condition de terminaison de boucle', + variableName: 'Nom de Variable', + finalLoopVariables: 'Variables de boucle finales', + inputMode: 'Mode d\'entrée', + setLoopVariables: 'Définir des variables dans la portée de la boucle', + loop_other: '{{count}} Boucles', + comma: ',', + loopNode: 'Nœud de boucle', + error_one: '{{count}} Erreur', + errorResponseMethod: 'Méthode de réponse d\'erreur', + input: 'Entrée', + currentLoopCount: 'Nombre de boucles actuel : {{count}}', + deleteDesc: 'Supprimer le nœud de boucle supprimera tous les nœuds enfants.', + exitConditionTip: 'Un nœud de boucle nécessite au moins une condition de sortie', + breakConditionTip: 'Seules les variables dans les boucles avec des conditions de terminaison et les variables de conversation peuvent être référencées.', + loopVariables: 'Variables de boucle', + deleteTitle: 'Supprimer le nœud de boucle ?', + }, }, tracing: { stopBy: 'Arrêté par {{user}}', @@ -777,6 +885,38 @@ const translation = { assignedVarsDescription: 'Les variables affectées doivent être des variables accessibles en écriture, telles que', conversationVars: 'Variables de conversation', }, + versionHistory: { + filter: { + all: 'Tout', + reset: 'Réinitialiser le filtre', + onlyYours: 'Rien que le tien', + empty: 'Aucune version correspondante trouvée', + onlyShowNamedVersions: 'Afficher uniquement les versions nommées', + }, + editField: { + releaseNotesLengthLimit: 'Les notes de version ne peuvent pas dépasser {{limit}} caractères.', + title: 'Titre', + titleLengthLimit: 'Le titre ne peut pas dépasser {{limit}} caractères.', + releaseNotes: 'Notes de version', + }, + action: { + updateSuccess: 'Version mise à jour', + deleteFailure: 'Échec de la suppression de la version', + restoreSuccess: 'Version restaurée', + deleteSuccess: 'Version supprimée', + updateFailure: 'Échec de la mise à jour de la version', + restoreFailure: 'Échec de la restauration de la version', + }, + title: 'Versions', + releaseNotesPlaceholder: 'Décrivez ce qui a changé', + nameThisVersion: 'Nommez cette version', + currentDraft: 'Projet actuel', + defaultName: 'Version sans titre', + editVersionInfo: 'Modifier les informations de version', + restorationTip: 'Après la restauration de la version, le brouillon actuel sera écrasé.', + deletionTip: 'La suppression est irreversible, veuillez confirmer.', + latest: 'Dernier', + }, } export default translation diff --git a/web/i18n/hi-IN/app-debug.ts b/web/i18n/hi-IN/app-debug.ts index 1b0633ef32..3f4b06c08b 100644 --- a/web/i18n/hi-IN/app-debug.ts +++ b/web/i18n/hi-IN/app-debug.ts @@ -312,6 +312,7 @@ const translation = { 'inputPlaceholder': 'कृपया इनपुट करें', 'content': 'सामग्री', 'required': 'आवश्यक', + 'hide': 'छुपाएँ', 'errorMsg': { varNameRequired: 'वेरिएबल नाम आवश्यक है', labelNameRequired: 'लेबल नाम आवश्यक है', diff --git a/web/i18n/hi-IN/app-overview.ts b/web/i18n/hi-IN/app-overview.ts index 0b514543ac..8a431e4bd9 100644 --- a/web/i18n/hi-IN/app-overview.ts +++ b/web/i18n/hi-IN/app-overview.ts @@ -53,7 +53,7 @@ const translation = { show: 'दिखाएं', hide: 'छुपाएं', subTitle: 'कार्यप्रवाह विवरण', - showDesc: 'WebApp में वर्कफ़्लो विवरण दिखाएँ या छुपाएँ', + showDesc: 'web app में वर्कफ़्लो विवरण दिखाएँ या छुपाएँ', }, chatColorTheme: 'चैटबॉट का रंग थीम', chatColorThemeDesc: 'चैटबॉट का रंग थीम निर्धारित करें', @@ -78,8 +78,8 @@ const translation = { sso: { title: 'वेबएप एसएसओ', label: 'SSO प्रमाणीकरण', - description: 'WebApp का उपयोग करने से पहले सभी उपयोगकर्ताओं को SSO के साथ लॉगिन करना आवश्यक है', - tooltip: 'WebApp SSO को सक्षम करने के लिए व्यवस्थापक से संपर्क करें', + description: 'web app का उपयोग करने से पहले सभी उपयोगकर्ताओं को SSO के साथ लॉगिन करना आवश्यक है', + tooltip: 'web app SSO को सक्षम करने के लिए व्यवस्थापक से संपर्क करें', }, modalTip: 'क्लाइंट-साइड वेब अनुप्रयोग सेटिंग्स.', }, diff --git a/web/i18n/hi-IN/app.ts b/web/i18n/hi-IN/app.ts index c9b035568b..3929dfeb6a 100644 --- a/web/i18n/hi-IN/app.ts +++ b/web/i18n/hi-IN/app.ts @@ -73,7 +73,7 @@ const translation = { appCreateDSLWarning: 'सावधानी: DSL संस्करण अंतर कुछ सुविधाओं को प्रभावित कर सकता है', appCreateDSLErrorPart2: 'क्या आप जारी रखना चाहते हैं?', learnMore: 'और जानो', - forBeginners: 'नौसिखियों के लिए', + forBeginners: 'नए उपयोगकर्ताओं के लिए बुनियादी ऐप प्रकार', foundResults: '{{गिनती}} परिणाम', forAdvanced: 'उन्नत उपयोगकर्ताओं के लिए', agentUserDescription: 'पुनरावृत्त तर्क और स्वायत्त उपकरण में सक्षम एक बुद्धिमान एजेंट कार्य लक्ष्यों को प्राप्त करने के लिए उपयोग करता है।', @@ -87,12 +87,12 @@ const translation = { noAppsFound: 'कोई ऐप्लिकेशन नहीं मिला', chooseAppType: 'ऐप प्रकार चुनें', agentShortDescription: 'तर्क और स्वायत्त उपकरण उपयोग के साथ बुद्धिमान एजेंट', - workflowShortDescription: 'सिंगल-टर्न ऑटोमेशन कार्यों के लिए ऑर्केस्ट्रेशन', + workflowShortDescription: 'बुद्धिमान स्वचालन के लिए एजेंटिक प्रवाह', chatbotUserDescription: 'सरल कॉन्फ़िगरेशन के साथ जल्दी से एलएलएम-आधारित चैटबॉट बनाएं। आप बाद में चैटफ्लो पर स्विच कर सकते हैं।', - advancedUserDescription: 'स्मृति क्षमताओं के साथ बहु-दौर जटिल संवाद कार्यों के लिए वर्कफ़्लो ऑर्केस्ट्रेशन।', - advancedShortDescription: 'स्मृति के साथ जटिल बहु-मोड़ संवादों के लिए वर्कफ़्लो', + advancedUserDescription: 'अतिरिक्त मेमोरी सुविधाओं और चैटबॉट इंटरफेस के साथ वर्कफ़्लो।', + advancedShortDescription: 'बहु-चरण वार्तालाप के लिए उन्नत वर्कफ़्लो', noTemplateFoundTip: 'विभिन्न कीवर्ड का उपयोग करके खोजने का प्रयास करें।', - workflowUserDescription: 'स्वचालन और बैच प्रसंस्करण जैसे एकल-दौर कार्यों के लिए वर्कफ़्लो ऑर्केस्ट्रेशन।', + workflowUserDescription: 'ड्रैग-एंड-ड्रॉप सरलता के साथ स्वायत्त AI वर्कफ़्लो का दृश्य निर्माण करें।', }, editApp: 'जानकारी संपादित करें', editAppTitle: 'ऐप जानकारी संपादित करें', @@ -159,11 +159,15 @@ const translation = { title: 'ओपिक', description: 'ओपिक एलएलएम अनुप्रयोगों के मूल्यांकन, परीक्षण और निगरानी के लिए एक ओपन-सोर्स प्लेटफॉर्म है।', }, + weave: { + title: 'बुनना', + description: 'वीव एक ओपन-सोर्स प्लेटफ़ॉर्म है जो LLM अनुप्रयोगों का मूल्यांकन, परीक्षण और निगरानी करने के लिए है।', + }, }, answerIcon: { - title: 'बदलने 🤖 के लिए WebApp चिह्न का उपयोग करें', + title: 'बदलने 🤖 के लिए web app चिह्न का उपयोग करें', descriptionInExplore: 'एक्सप्लोर में बदलने 🤖 के लिए वेबऐप आइकन का उपयोग करना है या नहीं', - description: 'साझा अनुप्रयोग में प्रतिस्थापित 🤖 करने के लिए WebApp चिह्न का उपयोग करना है या नहीं', + description: 'साझा अनुप्रयोग में प्रतिस्थापित 🤖 करने के लिए web app चिह्न का उपयोग करना है या नहीं', }, importFromDSLFile: 'डीएसएल फ़ाइल से', importFromDSLUrl: 'यूआरएल से', @@ -194,6 +198,54 @@ const translation = { placeholder: 'एक ऐप चुनें...', label: 'ऐप', }, + structOutput: { + structured: 'संरचित', + required: 'आवश्यक', + LLMResponse: 'LLM प्रतिक्रिया', + moreFillTip: 'अधिकतम 10 स्तरों की नेस्टिंग दिखाना', + modelNotSupported: 'मॉडल का समर्थन नहीं किया गया', + configure: 'कॉन्फ़िगर करें', + notConfiguredTip: 'संरचित आउटपुट को अभी तक कॉन्फ़िगर नहीं किया गया है', + structuredTip: 'संरचित आउटपुट एक विशेषता है जो यह सुनिश्चित करती है कि मॉडल हमेशा आपके प्रदान किए गए JSON स्कीमा के अनुसार प्रतिक्रियाएँ生成 करेगा।', + modelNotSupportedTip: 'वर्तमान मॉडल इस सुविधा का समर्थन नहीं करता है और स्वचालित रूप से प्रॉम्प्ट इंजेक्शन में डाउनग्रेड किया जाता है।', + }, + accessItemsDescription: { + anyone: 'कोई भी वेब ऐप तक पहुँच सकता है', + organization: 'संस्थान के किसी भी व्यक्ति को वेब ऐप तक पहुंच प्राप्त है', + specific: 'केवल विशेष समूह या सदस्य ही वेब ऐप तक पहुंच सकते हैं', + external: 'केवल प्रमाणित बाहरी उपयोगकर्ता वेब अनुप्रयोग तक पहुँच सकते हैं', + }, + accessControlDialog: { + accessItems: { + anyone: 'लिंक के साथ कोई भी', + specific: 'विशिष्ट समूह या सदस्य', + organization: 'केवल उद्यम के भीतर के सदस्य', + external: 'प्रमाणित बाहरी उपयोगकर्ता', + }, + operateGroupAndMember: { + searchPlaceholder: 'समूहों और सदस्यों की खोज करें', + allMembers: 'सभी सदस्य', + expand: 'व्याप्त करें', + noResult: 'कोई परिणाम नहीं', + }, + title: 'वेब एप्लिकेशन पहुँच नियंत्रण', + description: 'वेब ऐप एक्सेस अनुमतियाँ सेट करें', + groups_one: '{{count}} समूह', + groups_other: '{{count}} समूह', + members_one: '{{count}} सदस्य', + members_other: '{{count}} सदस्य', + noGroupsOrMembers: 'कोई समूह या सदस्य चयनित नहीं किया गया', + updateSuccess: 'सफलता से अपडेट किया गया', + accessLabel: 'किसके पास पहुँच है', + webAppSSONotEnabledTip: 'कृपया वेब ऐप प्रमाणीकरण विधि कॉन्फ़िगर करने के लिए उद्यम प्रशासक से संपर्क करें।', + }, + publishApp: { + title: 'वेब ऐप तक कौन पहुँच सकता है', + notSet: 'अनुबंधित नहीं किया गया', + notSetDesc: 'वर्तमान में कोई भी वेब ऐप तक पहुंच नहीं बना सकता। कृपया अनुमतियाँ सेट करें।', + }, + accessControl: 'वेब एप्लिकेशन पहुँच नियंत्रण', + noAccessPermission: 'वेब एप्लिकेशन तक पहुँचने की अनुमति नहीं है', } export default translation diff --git a/web/i18n/hi-IN/billing.ts b/web/i18n/hi-IN/billing.ts index b5ac02f635..1f8b29587c 100644 --- a/web/i18n/hi-IN/billing.ts +++ b/web/i18n/hi-IN/billing.ts @@ -77,6 +77,7 @@ const translation = { title: 'संदेश क्रेडिट्स', tooltip: 'विभिन्न योजनाओं के लिए संदेश आह्वान कोटा OpenAI मॉडलों का उपयोग करके (gpt4 को छोड़कर)। सीमा से अधिक संदेश आपके OpenAI API कुंजी का उपयोग करेंगे।', + titlePerMonth: '{{count,number}} संदेश/महीना', }, annotatedResponse: { title: 'एनोटेशन कोटा सीमाएं', @@ -87,30 +88,97 @@ const translation = { 'Dify की केवल ज्ञान आधार प्रसंस्करण क्षमताओं को आह्वान करने वाले API कॉल की संख्या को संदर्भित करता है।', receiptInfo: 'केवल टीम के मालिक और टीम एडमिन सब्सक्राइब कर सकते हैं और बिलिंग जानकारी देख सकते हैं', + freeTrialTipPrefix: 'साइन अप करें और प्राप्त करें एक', + teamMember_other: '{{count,number}} टीम सदस्य', + comparePlanAndFeatures: 'योजना और विशेषताओं की तुलना करें', + apiRateLimit: 'एपीआई दर सीमा', + documentsRequestQuota: '{{count,number}}/मिनट ज्ञान अनुरोध दर सीमा', + self: 'स्व-होस्टेड', + getStarted: 'शुरू करें', + annualBilling: 'वार्षिक बिलिंग', + documentsTooltip: 'ज्ञान डेटा स्रोत से आयात किए गए दस्तावेजों की संख्या पर कोटा।', + priceTip: 'प्रत्येक कार्यक्षेत्र/', + cloud: 'क्लाउड सेवा', + unlimitedApiRate: 'कोई एपीआई दर सीमा नहीं', + freeTrialTip: '200 ओपनएआई कॉल्स का मुफ्त परीक्षण।', + documents: '{{count,number}} ज्ञान दस्तावेज़', + freeTrialTipSuffix: 'कोई क्रेडिट कार्ड की आवश्यकता नहीं है', + apiRateLimitUnit: '{{count,number}}/दिन', + teamWorkspace: '{{count,number}} टीम कार्यक्षेत्र', + apiRateLimitTooltip: 'Dify API के माध्यम से की गई सभी अनुरोधों पर API दर सीमा लागू होती है, जिसमें टेक्स्ट जनरेशन, चैट वार्तालाप, कार्यप्रवाह निष्पादन और दस्तावेज़ प्रसंस्करण शामिल हैं।', + teamMember_one: '{{count,number}} टीम सदस्य', + documentsRequestQuotaTooltip: 'यह ज्ञान आधार में एक कार्यक्षेत्र द्वारा प्रति मिनट किए जा सकने वाले कुल कार्यों की संख्या को निर्दिष्ट करता है, जिसमें डेटासेट बनाना, हटाना, अपडेट करना, दस्तावेज़ अपलोड करना, संशोधन करना, संग्रहित करना और ज्ञान आधार अनुरोध शामिल हैं। इस मीट्रिक का उपयोग ज्ञान आधार अनुरोधों के प्रदर्शन का मूल्यांकन करने के लिए किया जाता है। उदाहरण के लिए, यदि एक सैंडबॉक्स उपयोगकर्ता एक मिनट के भीतर 10 लगातार हिट परीक्षण करता है, तो उनके कार्यक्षेत्र को अगले मिनट के लिए निम्नलिखित कार्यों को करने से अस्थायी रूप से प्रतिबंधित किया जाएगा: डेटासेट बनाना, हटाना, अपडेट करना और दस्तावेज़ अपलोड या संशोधन करना।', }, plans: { sandbox: { name: 'सैंडबॉक्स', description: '200 बार GPT मुफ्त ट्रायल', includesTitle: 'शामिल हैं:', + for: 'कोर क्षमताओं का मुफ्त परीक्षण', }, professional: { name: 'प्रोफेशनल', description: 'व्यक्तियों और छोटे टीमों के लिए अधिक शक्ति सस्ती दर पर खोलें।', includesTitle: 'मुफ्त योजना में सब कुछ, साथ में:', + for: 'स्वतंत्र डेवलपर्स/छोटे टीमों के लिए', }, team: { name: 'टीम', description: 'बिना सीमा के सहयोग करें और शीर्ष स्तरीय प्रदर्शन का आनंद लें।', includesTitle: 'प्रोफेशनल योजना में सब कुछ, साथ में:', + for: 'मध्यम आकार की टीमों के लिए', }, enterprise: { name: 'एंटरप्राइज़', description: 'बड़े पैमाने पर मिशन-क्रिटिकल सिस्टम के लिए पूर्ण क्षमताएं और समर्थन प्राप्त करें।', includesTitle: 'टीम योजना में सब कुछ, साथ में:', + features: { + 0: 'उद्योग स्तर के बड़े पैमाने पर वितरण समाधान', + 3: 'अनेक कार्यक्षेत्र और उद्यम प्रबंधक', + 8: 'प्रोफेशनल तकनीकी समर्थन', + 6: 'उन्नत सुरक्षा और नियंत्रण', + 2: 'विशेष उद्यम सुविधाएँ', + 1: 'Commercial License Authorization', + 4: 'SSO', + 5: 'डिफाई पार्टनर्स द्वारा बातचीत किए गए एसएलए', + 7: 'डीफाई द्वारा आधिकारिक रूप से अपडेट और रखरखाव', + }, + price: 'कस्टम', + btnText: 'बिक्री से संपर्क करें', + priceTip: 'वार्षिक बिलिंग केवल', + for: 'बड़े आकार की टीमों के लिए', + }, + community: { + features: { + 2: 'डिफी ओपन सोर्स लाइसेंस के अनुपालन में', + 0: 'सभी मुख्य सुविधाएं सार्वजनिक संग्रह के तहत जारी की गई हैं।', + 1: 'एकल कार्यक्षेत्र', + }, + description: 'व्यक्तिगत उपयोगकर्ताओं, छोटे टीमों, या गैर-व्यावसायिक परियोजनाओं के लिए', + for: 'व्यक्तिगत उपयोगकर्ताओं, छोटे टीमों, या गैर-व्यावसायिक परियोजनाओं के लिए', + includesTitle: 'निःशुल्क सुविधाएँ:', + btnText: 'समुदाय के साथ आरंभ करें', + name: 'समुदाय', + price: 'मुक्त', + }, + premium: { + features: { + 2: 'वेब ऐप लोगो और ब्रांडिंग कस्टमाइजेशन', + 3: 'प्राथमिकता ईमेल और चैट समर्थन', + 1: 'एकल कार्यक्षेत्र', + 0: 'विभिन्न क्लाउड प्रदाताओं द्वारा आत्म-प्रबंधित विश्वसनीयता', + }, + priceTip: 'क्लाउड मार्केटप्लेस के आधार पर', + name: 'प्रीमियम', + btnText: 'प्रीमियम प्राप्त करें', + price: 'स्केलेबल', + includesTitle: 'समुदाय से सब कुछ, इसके अलावा:', + for: 'मध्यम आकार के संगठनों और टीमों के लिए', + description: 'मध्यम आकार के संगठनों और टीमों के लिए', + comingSoon: 'माइक्रोसॉफ्ट एज़्योर और गूगल क्लाउड समर्थन जल्द ही आ रहा है', }, }, vectorSpace: { @@ -120,12 +188,26 @@ const translation = { apps: { fullTipLine1: 'अधिक ऐप्स बनाने के लिए', fullTipLine2: 'अपनी योजना अपग्रेड करें।', + fullTip1: 'अधिक ऐप्स बनाने के लिए अपग्रेड करें', + fullTip2: 'योजना की सीमा पहुँच गई', + contactUs: 'हमसे संपर्क करें', + fullTip1des: 'आप इस योजना पर ऐप्स बनाने की सीमा तक पहुँच चुके हैं।', + fullTip2des: 'अचल अनुप्रयोगों को साफ करने की सिफारिश की जाती है ताकि उपयोग को मुक्त किया जा सके, या हमसे संपर्क करें।', }, annotatedResponse: { fullTipLine1: 'अधिक बातचीत को एनोटेट करने के लिए', fullTipLine2: 'अपनी योजना अपग्रेड करें।', quotaTitle: 'एनोटेशन उत्तर कोटा', }, + usagePage: { + annotationQuota: 'एनोटेशन कोटा', + buildApps: 'ऐप बनाएं', + documentsUploadQuota: 'दस्तावेज़ अपलोड कोटा', + vectorSpace: 'ज्ञान डेटा भंडारण', + teamMembers: 'टीम के सदस्य', + vectorSpaceTooltip: 'उच्च गुणवत्ता वाले अनुक्रमण मोड के साथ दस्तावेज़ों के लिए ज्ञान डेटा स्टोरेज संसाधनों का उपभोग होगा। जब ज्ञान डेटा स्टोरेज सीमा तक पहुँच जाएगा, तो नए दस्तावेज़ नहीं अपलोड किए जाएंगे।', + }, + teamMembers: 'टीम के सदस्य', } export default translation diff --git a/web/i18n/hi-IN/common.ts b/web/i18n/hi-IN/common.ts index c5cecc1052..65565f9955 100644 --- a/web/i18n/hi-IN/common.ts +++ b/web/i18n/hi-IN/common.ts @@ -54,6 +54,10 @@ const translation = { in: 'में', copied: 'कॉपी किया गया', viewDetails: 'विवरण देखें', + more: 'अधिक', + downloadSuccess: 'डाउनलोड पूरा हुआ।', + downloadFailed: 'डाउनलोड विफल। कृपया बाद में पुनः प्रयास करें।', + format: 'फॉर्मेट', }, errorMsg: { fieldRequired: '{{field}} आवश्यक है', @@ -150,6 +154,8 @@ const translation = { newDataset: 'ज्ञान बनाएं', tools: 'उपकरण', exploreMarketplace: 'मार्केटप्लेस का अन्वेषण करें', + appDetail: 'ऐप विवरण', + account: 'खाता', }, userProfile: { settings: 'सेटिंग्स', @@ -162,6 +168,9 @@ const translation = { community: 'समुदाय', about: 'के बारे में', logout: 'लॉग आउट', + compliance: 'अनुपालन', + github: 'गिटहब', + support: 'समर्थन', }, settings: { accountGroup: 'खाता', @@ -212,6 +221,9 @@ const translation = { permanentlyDeleteButton: 'खाता स्थायी रूप से हटाएं', verificationPlaceholder: '6-अंकीय कोड पेस्ट करें', deleteSuccessTip: 'आपके खाते को हटाने का काम पूरा करने के लिए समय चाहिए. जब यह सब हो जाएगा तो हम आपको ईमेल करेंगे।', + workspaceIcon: 'कार्यस्थल आइकन', + editWorkspaceInfo: 'कार्यक्षेत्र की जानकारी संपादित करें', + workspaceName: 'कार्यस्थल का नाम', }, members: { team: 'टीम', @@ -476,7 +488,7 @@ const translation = { title: 'एपीआई एक्सटेंशन केंद्रीकृत एपीआई प्रबंधन प्रदान करते हैं, जो Dify के अनुप्रयोगों में आसान उपयोग के लिए कॉन्फ़िगरेशन को सरल बनाते हैं।', link: 'अपना खुद का एपीआई एक्सटेंशन कैसे विकसित करें, यह जानें।', - linkUrl: 'https://docs.dify.ai/features/extension/api_based_extension', + linkUrl: 'https://docs.dify.ai/en/guides/extension/api-based-extension/README', add: 'एपीआई एक्सटेंशन जोड़ें', selector: { title: 'एपीआई एक्सटेंशन', @@ -565,6 +577,7 @@ const translation = { inputPlaceholder: 'बॉट से बात करें', thought: 'विचार', thinking: 'सोचते हुए...', + resend: 'फिर से भेजें', }, promptEditor: { placeholder: @@ -655,10 +668,31 @@ const translation = { license: { expiring: 'एक दिन में समाप्त हो रहा है', expiring_plural: '{{गिनती}} दिनों में समाप्त हो रहा है', + unlimited: 'असीमित', }, pagination: { perPage: 'प्रति पृष्ठ आइटम', }, + theme: { + light: 'रोशनी', + theme: 'थीम', + auto: 'प्रणाली', + dark: 'अंधेरा', + }, + compliance: { + iso27001: 'आईएसओ 27001:2022 प्रमाणन', + gdpr: 'जीडीपीआर डीपीए', + soc2Type1: 'SOC 2 प्रकार I रिपोर्ट', + professionalUpgradeTooltip: 'केवल टीम योजना या उससे ऊपर के साथ उपलब्ध है।', + soc2Type2: 'SOC 2 प्रकार II रिपोर्ट', + sandboxUpgradeTooltip: 'केवल पेशेवर या टीम योजना के साथ उपलब्ध है।', + }, + imageInput: { + supportedFormats: 'PNG, JPG, JPEG, WEBP और GIF का समर्थन करता है', + browse: 'ब्राउज़ करें', + dropImageHere: 'अपनी छवि यहाँ छोड़ें, या', + }, + you: 'आप', } export default translation diff --git a/web/i18n/hi-IN/custom.ts b/web/i18n/hi-IN/custom.ts index ea281d776a..a24834c873 100644 --- a/web/i18n/hi-IN/custom.ts +++ b/web/i18n/hi-IN/custom.ts @@ -3,9 +3,11 @@ const translation = { upgradeTip: { prefix: 'अपग्रेड करें अपने प्लान को', suffix: 'स्वयं अपना ब्रांड चुनना।', + title: 'अपने योजना को अपग्रेड करें', + des: 'अपने ब्रांड को कस्टमाइज़ करने के लिए अपने योजना को अपग्रेड करें', }, webapp: { - title: 'WebApp का ब्रांड व्यक्तिकरण करें', + title: 'web app का ब्रांड व्यक्तिकरण करें', removeBrand: 'पावर्ड द्वारा डिफी हटाएं', changeLogo: 'पावर्ड द्वारा ब्रांड छवि बदले', changeLogoTip: 'SVG या PNG प्रारूप के साथ न्यूनतम आकार 40x40px होना चाहिए', diff --git a/web/i18n/hi-IN/dataset-creation.ts b/web/i18n/hi-IN/dataset-creation.ts index 19af14efad..1f8384354d 100644 --- a/web/i18n/hi-IN/dataset-creation.ts +++ b/web/i18n/hi-IN/dataset-creation.ts @@ -27,7 +27,7 @@ const translation = { }, uploader: { title: 'फ़ाइल अपलोड करें', - button: 'फ़ाइल खींचें और छोड़ें, या', + button: 'फ़ाइलों या फ़ोल्डरों को खींचें और छोड़ें, या', browse: 'ब्राउज़ करें', tip: 'समर्थित {{supportTypes}}। प्रत्येक अधिकतम {{size}}MB।', validation: { @@ -92,6 +92,14 @@ const translation = { chooseProvider: 'एक प्रदाता का चयन करें', jinaReaderTitle: 'पूरी साइट को मार्कडाउन में बदलें', jinaReaderNotConfiguredDescription: 'एक्सेस के लिए अपनी मुफ्त एपीआई कुंजी दर्ज करके जीना रीडर सेट करें।', + watercrawlTitle: 'Watercrawl के साथ वेब सामग्री निकालें', + waterCrawlNotConfigured: 'Watercrawl को कॉन्फ़िगर नहीं किया गया है', + configureFirecrawl: 'फायरक्रॉल को कॉन्फ़िगर करें', + watercrawlDoc: 'वाटरक्रॉल दस्तावेज़', + waterCrawlNotConfiguredDescription: 'इसे उपयोग करने के लिए वॉटरक्रॉल को एपीआई कुंजी के साथ कॉन्फ़िगर करें।', + watercrawlDocLink: 'https://docs.dify.ai/en/guides/knowledge-base/create-knowledge-and-upload-documents/import-content-data/sync-from-website', + configureJinaReader: 'जिना रीडर कॉन्फ़िगर करें', + configureWatercrawl: 'वाटरक्रॉल कॉन्फ़िगर करें', }, cancel: 'रद्द करना', }, @@ -220,6 +228,11 @@ const translation = { learnMore: 'और जानो', description: 'वर्तमान में, Dify के ज्ञानकोष में केवल सीमित डेटा स्रोत हैं। Dify नॉलेज बेस में डेटा स्रोत का योगदान करना सभी उपयोगकर्ताओं के लिए प्लेटफॉर्म के लचीलेपन और शक्ति को बढ़ाने में मदद करने का एक शानदार तरीका है। हमारी योगदान मार्गदर्शिका आरंभ करना आसान बनाती है। अधिक जानने के लिए कृपया नीचे दिए गए लिंक पर क्लिक करें।', }, + watercrawl: { + apiKeyPlaceholder: 'watercrawl.dev से API कुंजी', + configWatercrawl: 'वाटरक्रॉल कॉन्फ़िगर करें', + getApiKeyLinkText: 'watercrawl.dev से अपना एपीआई कुंजी प्राप्त करें', + }, } export default translation diff --git a/web/i18n/hi-IN/dataset-settings.ts b/web/i18n/hi-IN/dataset-settings.ts index e7a383690c..9a05847ac8 100644 --- a/web/i18n/hi-IN/dataset-settings.ts +++ b/web/i18n/hi-IN/dataset-settings.ts @@ -1,6 +1,6 @@ const translation = { title: 'ज्ञान सेटिंग्ज', - desc: 'यहां आप ज्ञान की संपत्ति और कार्य प्रक्रियाओं को modify कर सकते हैं。', + desc: 'यहां आप ज्ञान की संपत्ति और कार्य प्रक्रियाओं को modify कर सकते हैं.', form: { name: 'ज्ञान नाम', namePlaceholder: 'कृपया ज्ञान नाम दर्ज करें', @@ -30,6 +30,7 @@ const translation = { description: 'प्राप्ति पद्धति के बारे में。', longDescription: 'प्राप्ति पद्धति के बारे में, आप इसे किसी भी समय ज्ञान सेटिंग्ज में बदल सकते हैं।', + method: 'प्राप्ति विधि', }, save: 'सेवना', me: '(आप)', diff --git a/web/i18n/hi-IN/dataset.ts b/web/i18n/hi-IN/dataset.ts index b95f13088c..9be333cdec 100644 --- a/web/i18n/hi-IN/dataset.ts +++ b/web/i18n/hi-IN/dataset.ts @@ -175,6 +175,54 @@ const translation = { documentsDisabled: '{{num}} दस्तावेज़ अक्षम - 30 दिनों से अधिक समय से निष्क्रिय', allKnowledge: 'सर्व ज्ञान', allKnowledgeDescription: 'इस कार्यस्थान में सभी ज्ञान प्रदर्शित करने के लिए चयन करें. केवल कार्यस्थान स्वामी ही सभी ज्ञान का प्रबंधन कर सकता है.', + metadata: { + createMetadata: { + type: 'टाइप करें', + name: 'नाम', + namePlaceholder: 'ध्यानक का नाम जोड़ें', + title: 'नया मेटाडेटा', + back: 'पीछे', + }, + checkName: { + empty: 'मेटाडाटा का नाम खाली नहीं हो सकता', + invalid: 'मेटाडेटा नाम में केवल छोटे अक्षर, संख्या और अंडरस्कोर शामिल हो सकते हैं और इसे छोटे अक्षर से शुरू होना चाहिए।', + }, + batchEditMetadata: { + editMetadata: 'मेटाडेटा संपादित करें', + multipleValue: 'कई मान', + applyToAllSelectDocument: 'चुने गए सभी दस्तावेज़ों पर लागू करें', + editDocumentsNum: 'संपादित कर रहे हैं {{num}} दस्तावेज़', + applyToAllSelectDocumentTip: 'सभी चयनित दस्तावेज़ों के लिए ऊपर दिए गए संपादित और नए मेटाडेटा को स्वचालित रूप से बनाएं, अन्यथा मेटाडेटा का संपादन केवल उन दस्तावेज़ों पर लागू होगा जिनमें यह मौजूद है।', + }, + selectMetadata: { + manageAction: 'प्रबंधित करें', + search: 'खोज मेटाडेटा', + newAction: 'नया मेटाडेटा', + }, + datasetMetadata: { + addMetaData: 'मेटाडेटा जोड़ें', + rename: 'नाम बदलें', + name: 'नाम', + values: '{{num}} मान', + namePlaceholder: 'मेटाडेटा नाम', + deleteContent: 'क्या आप सुनिश्चित हैं कि आप मेटाडेटा "{{name}}" को हटाना चाहते हैं?', + deleteTitle: 'हटाने की पुष्टि करें', + description: 'आप इस ज्ञान में सभी मेटाडेटा प्रबंधित कर सकते हैं। संशोधन हर दस्तावेज़ में समन्वयित किए जाएंगे।', + disabled: 'अक्षम', + builtInDescription: 'बिल्ट-इन मेटाडेटा स्वचालित रूप से निकाला और उत्पन्न होता है। इसे उपयोग से पहले सक्षम करना आवश्यक है और इसे संपादित नहीं किया जा सकता है।', + builtIn: 'बिल्ट-इन', + }, + documentMetadata: { + documentInformation: 'दस्तावेज़ जानकारी', + technicalParameters: 'तकनीकी पैरामीटर', + startLabeling: 'लेबलिंग शुरू करें', + metadataToolTip: 'मेटाडेटा एक महत्वपूर्ण फ़िल्टर के रूप में कार्य करता है जो जानकारी प्राप्त करने की सटीकता और प्रासंगिकता को बढ़ाता है। आप इस दस्तावेज़ के लिए मेटाडेटा को यहाँ संशोधित और जोड़ सकते हैं।', + }, + chooseTime: 'एक समय चुनें...', + addMetadata: 'मेटाडेटा जोड़ें', + metadata: 'मेटाडेटा', + }, + embeddingModelNotAvailable: 'एम्बेडिंग मॉडल उपलब्ध नहीं है।', } export default translation diff --git a/web/i18n/hi-IN/education.ts b/web/i18n/hi-IN/education.ts new file mode 100644 index 0000000000..0577fa14b3 --- /dev/null +++ b/web/i18n/hi-IN/education.ts @@ -0,0 +1,47 @@ +const translation = { + toVerifiedTip: { + coupon: 'विशेष 100% कूपन', + front: 'आप अब शिक्षा सत्यापित स्थिति के लिए योग्य हैं। कृपया नीचे अपनी शिक्षा की जानकारी प्रदान करें ताकि प्रक्रिया को पूरा किया जा सके और एक प्राप्त हो सके', + end: 'Dify प्रोफेशनल योजना के लिए।', + }, + form: { + schoolName: { + placeholder: 'अपनी स्कूल का आधिकारिक, बिना संक्षिप्त नाम दर्ज करें', + title: 'आपके स्कूल का नाम', + }, + schoolRole: { + option: { + administrator: 'स्कूल प्रशासक', + student: 'छात्र', + teacher: 'शिक्षक', + }, + title: 'आपकी स्कूल की भूमिका', + }, + terms: { + desc: { + and: 'और', + termsOfService: 'सेवाओं की शर्तें', + privacyPolicy: 'गोपनीयता नीति', + end: '. प्रस्तुत करके:', + front: 'आपकी जानकारी और शिक्षा सत्यापित स्थिति का उपयोग हमारी शर्तों के अधीन है।', + }, + option: { + age: 'मैं पुष्टि करता हूँ कि मैं कम से कम 18 साल का हूँ', + inSchool: 'मैं पुष्टि करता हूँ कि मैं दी गई संस्थान में नामांकित या नियुक्त हूं। Dify नामांकन/नियुक्ति का प्रमाण मांग सकता है। यदि मैं अपनी पात्रता का गलत वर्णन करता हूं, तो मैं सहमत हूं कि मैं अपने शिक्षा स्थिति के आधार पर किसी भी शुल्क का भुगतान करूं जो प्रारंभ में माफ किया गया था।', + }, + title: 'नियम और शर्तें', + }, + }, + submitError: 'फॉर्म जमा करने में विफलता हुई। कृपया बाद में पुनः प्रयास करें।', + currentSigned: 'वर्तमान में साइन इन किया गया है के रूप में', + learn: 'शिक्षा को प्रमाणित कराने का तरीका सीखें', + toVerified: 'शिक्षा की पुष्टि कराएँ', + emailLabel: 'आपका वर्तमान ईमेल', + submit: 'सबमिट करें', + rejectTitle: 'आपकी डिफाई शैक्षणिक सत्यापन को अस्वीकृत कर दिया गया है', + successTitle: 'आपकी डिफाई शिक्षा को सत्यापित किया गया है', + successContent: 'हमने आपकी खाते के लिए Dify प्रोफेशनल योजना के लिए 100% छूट कूपन जारी किया है। यह कूपन एक वर्ष के लिए मान्य है, कृपया इसे मान्यता की अवधि के भीतर उपयोग करें।', + rejectContent: 'दुर्भाग्यवश, आप शिक्षा सत्यापित स्थिति के लिए योग्य नहीं हैं और इसलिए यदि आप इस ईमेल पते का उपयोग करते हैं, तो आप डिफाई प्रोफेशनल योजना के लिए विशेष 100% कूपन प्राप्त नहीं कर सकते।', +} + +export default translation diff --git a/web/i18n/hi-IN/explore.ts b/web/i18n/hi-IN/explore.ts index 145c4e0e4f..a9e850cb3b 100644 --- a/web/i18n/hi-IN/explore.ts +++ b/web/i18n/hi-IN/explore.ts @@ -38,6 +38,7 @@ const translation = { HR: 'मानव संसाधन', Workflow: 'कार्यप्रवाह', Agent: 'आढ़तिया', + Entertainment: 'मनोरंजन', }, } diff --git a/web/i18n/hi-IN/login.ts b/web/i18n/hi-IN/login.ts index 0be8cbc3ab..0c9f4451b6 100644 --- a/web/i18n/hi-IN/login.ts +++ b/web/i18n/hi-IN/login.ts @@ -110,6 +110,11 @@ const translation = { licenseLostTip: 'Dify लायसेंस सर्वर से कनेक्ट करने में विफल. Dify का उपयोग जारी रखने के लिए कृपया अपने व्यवस्थापक से संपर्क करें.', licenseInactiveTip: 'आपके कार्यस्थल के लिए डिफाई एंटरप्राइज लाइसेंस निष्क्रिय है। कृपया डिफाई का उपयोग जारी रखने के लिए अपने प्रशासक से संपर्क करें।', licenseExpiredTip: 'आपके कार्यस्थल के लिए डिफाई एंटरप्राइज लाइसेंस समाप्त हो गया है। कृपया डिफाई का उपयोग जारी रखने के लिए अपने प्रशासक से संपर्क करें।', + webapp: { + noLoginMethodTip: 'कृपया एक प्रमाणीकरण विधि जोड़ने के लिए सिस्टम प्रशासक से संपर्क करें।', + noLoginMethod: 'वेब ऐप के लिए प्रमाणीकरण विधि कॉन्फ़िगर नहीं की गई है', + disabled: 'वेब ऐप प्रमाणीकरण अक्षम है। कृपया इसे सक्षम करने के लिए सिस्टम प्रशासक से संपर्क करें। आप सीधे ऐप का उपयोग करने की कोशिश कर सकते हैं।', + }, } export default translation diff --git a/web/i18n/hi-IN/plugin.ts b/web/i18n/hi-IN/plugin.ts index 36b7588319..40bf490e78 100644 --- a/web/i18n/hi-IN/plugin.ts +++ b/web/i18n/hi-IN/plugin.ts @@ -62,6 +62,7 @@ const translation = { paramsTip2: 'जब \'स्वचालित\' बंद होता है, तो डिफ़ॉल्ट मान का उपयोग किया जाता है।', descriptionPlaceholder: 'उपकरण के उद्देश्य का संक्षिप्त विवरण, जैसे, किसी विशेष स्थान के लिए तापमान प्राप्त करना।', paramsTip1: 'एलएलएम अनुमान पैरामीटर को नियंत्रित करता है।', + toolSetting: 'टूल सेटिंग्स', }, switchVersion: 'स्विच संस्करण', endpointModalDesc: 'एक बार कॉन्फ़िगर होने के बाद, प्लगइन द्वारा API एंडपॉइंट्स के माध्यम से प्रदान की गई सुविधाओं का उपयोग किया जा सकता है।', @@ -180,6 +181,8 @@ const translation = { difyMarketplace: 'डिफाई मार्केटप्लेस', sortBy: 'काला शहर', discover: 'खोजें', + partnerTip: 'Dify भागीदार द्वारा सत्यापित', + verifiedTip: 'डिफाई द्वारा सत्यापित', }, task: { clearAll: 'सभी साफ करें', @@ -193,7 +196,6 @@ const translation = { fromMarketplace: 'मार्केटप्लेस से', searchPlugins: 'खोज प्लगइन्स', install: '{{num}} इंस्टॉलेशन', - submitPlugin: 'प्लगइन सबमिट करें', allCategories: 'सभी श्रेणियाँ', search: 'खोज', searchTools: 'खोज उपकरण...', @@ -204,6 +206,12 @@ const translation = { findMoreInMarketplace: 'मार्केटप्लेस में और खोजें', endpointsEnabled: '{{num}} एंडपॉइंट्स के सेट सक्षम किए गए', from: 'से', + metadata: { + title: 'प्लगइन्स', + }, + difyVersionNotCompatible: 'वर्तमान डिफाई संस्करण इस प्लगइन के साथ संगत नहीं है, कृपया आवश्यक न्यूनतम संस्करण में अपग्रेड करें: {{minimalDifyVersion}}', + requestAPlugin: 'एक प्लगइन का अनुरोध करें', + publishPlugins: 'प्लगइन प्रकाशित करें', } export default translation diff --git a/web/i18n/hi-IN/share-app.ts b/web/i18n/hi-IN/share-app.ts index 88890f86b8..a1e716b5bc 100644 --- a/web/i18n/hi-IN/share-app.ts +++ b/web/i18n/hi-IN/share-app.ts @@ -30,6 +30,12 @@ const translation = { }, tryToSolve: 'समाधान करने का प्रयास करें', temporarySystemIssue: 'अभी सिस्टम में समस्या है, कृपया पुनः प्रयास करें।', + expand: 'विस्तार करें', + collapse: 'संकुचित करें', + viewChatSettings: 'चैट सेटिंग्स देखें', + chatSettingsTitle: 'नया चैट सेटअप', + chatFormTip: 'चैट शुरू होने के बाद चैट सेटिंग्स को संशोधित नहीं किया जा सकता।', + newChatTip: 'पहले से ही एक नए चैट में', }, generation: { tabs: { @@ -71,6 +77,11 @@ const translation = { 'रॉ {{rowIndex}}: {{varName}} मान {{maxLength}} वर्णों से अधिक नहीं हो सकता', atLeastOne: 'कृपया अपलोड की गई फ़ाइल में कम से कम एक पंक्ति भरें।', }, + execution: 'अनु执行', + executions: '{{num}} फाँसी', + }, + login: { + backToHome: 'होम पर वापस', }, } diff --git a/web/i18n/hi-IN/time.ts b/web/i18n/hi-IN/time.ts index e2410dd34b..72f6cd56c4 100644 --- a/web/i18n/hi-IN/time.ts +++ b/web/i18n/hi-IN/time.ts @@ -1,3 +1,37 @@ -const translation = {} +const translation = { + daysInWeek: { + Fri: 'शुक्रवार', + Tue: 'मंगलवार', + Sun: 'सूर्य', + Wed: 'बुधवार', + Sat: 'शनिवार', + Mon: 'मोन', + Thu: 'गुरुवार', + }, + months: { + July: 'जुलाई', + August: 'अगस्त', + September: 'सितंबर', + March: 'मार्च', + May: 'मई', + October: 'अक्टूबर', + April: 'अप्रैल', + December: 'दिसंबर', + February: 'फरवरी', + June: 'जून', + November: 'नवंबर', + January: 'जनवरी', + }, + operation: { + now: 'अब', + pickDate: 'तारीख़ चुनें', + ok: 'ठीक है', + cancel: 'रद्द करें', + }, + title: { + pickTime: 'समय चुनें', + }, + defaultPlaceholder: 'एक समय चुनें...', +} export default translation diff --git a/web/i18n/hi-IN/tools.ts b/web/i18n/hi-IN/tools.ts index e9b8107867..105e7e5fa6 100644 --- a/web/i18n/hi-IN/tools.ts +++ b/web/i18n/hi-IN/tools.ts @@ -15,7 +15,6 @@ const translation = { }, author: 'द्वारा', auth: { - unauthorized: 'अधिकृत करने के लिए', authorized: 'अधिकृत', setup: 'उपयोग करने के लिए अधिकृति सेटअप करें', setupModalTitle: 'अधिकृति सेटअप करें', diff --git a/web/i18n/hi-IN/workflow.ts b/web/i18n/hi-IN/workflow.ts index c0e679ab8c..e413da9f7a 100644 --- a/web/i18n/hi-IN/workflow.ts +++ b/web/i18n/hi-IN/workflow.ts @@ -38,8 +38,6 @@ const translation = { setVarValuePlaceholder: 'वेरिएबल सेट करें', needConnectTip: 'यह चरण किसी से जुड़ा नहीं है', maxTreeDepth: 'प्रति शाखा अधिकतम {{depth}} नोड्स की सीमा', - needEndNode: 'अंत ब्लॉक जोड़ा जाना चाहिए', - needAnswerNode: 'उत्तर ब्लॉक जोड़ा जाना चाहिए', workflowProcess: 'कार्यप्रवाह प्रक्रिया', notRunning: 'अभी तक नहीं चल रहा', previewPlaceholder: @@ -60,7 +58,6 @@ const translation = { learnMore: 'अधिक जानें', copy: 'कॉपी करें', duplicate: 'डुप्लिकेट करें', - addBlock: 'ब्लॉक जोड़ें', pasteHere: 'यहां पेस्ट करें', pointerMode: 'पॉइंटर मोड', handMode: 'हैंड मोड', @@ -109,6 +106,18 @@ const translation = { addFailureBranch: 'असफल शाखा जोड़ें', noHistory: 'कोई इतिहास नहीं', loadMore: 'अधिक वर्कफ़्लोज़ लोड करें', + exitVersions: 'निकलने के संस्करण', + exportPNG: 'PNG के रूप में निर्यात करें', + exportJPEG: 'JPEG के रूप में निर्यात करें', + referenceVar: 'संदर्भ चर', + noExist: 'कोई ऐसा चर नहीं है', + exportImage: 'छवि निर्यात करें', + publishUpdate: 'अपडेट प्रकाशित करें', + exportSVG: 'SVG के रूप में निर्यात करें', + versionHistory: 'संस्करण इतिहास', + needAnswerNode: 'उत्तर नोड जोड़ा जाना चाहिए', + addBlock: 'नोड जोड़ें', + needEndNode: 'अंत नोड जोड़ा जाना चाहिए', }, env: { envPanelTitle: 'पर्यावरण चर', @@ -170,19 +179,19 @@ const translation = { stepForward_other: '{{count}} कदम आगे', sessionStart: 'सत्र प्रारंभ', currentState: 'वर्तमान स्थिति', - nodeTitleChange: 'ब्लॉक शीर्षक बदला गया', - nodeDescriptionChange: 'ब्लॉक विवरण बदला गया', - nodeDragStop: 'ब्लॉक स्थानांतरित किया गया', - nodeChange: 'ब्लॉक बदला गया', - nodeConnect: 'ब्लॉक कनेक्ट किया गया', - nodePaste: 'ब्लॉक पेस्ट किया गया', - nodeDelete: 'ब्लॉक हटाया गया', - nodeAdd: 'ब्लॉक जोड़ा गया', - nodeResize: 'ब्लॉक का आकार बदला गया', noteAdd: 'नोट जोड़ा गया', noteChange: 'नोट बदला गया', noteDelete: 'नोट हटाया गया', - edgeDelete: 'ब्लॉक डिस्कनेक्ट किया गया', + nodeConnect: 'नोड कनेक्टेड', + nodeResize: 'नोड का आकार बदला गया', + nodeDelete: 'नोड हटा दिया गया', + nodeDragStop: 'नोड स्थानांतरित किया गया', + nodeChange: 'नोड बदला गया', + nodeAdd: 'नोड जोड़ा गया', + nodeTitleChange: 'नोड का शीर्षक बदल दिया गया', + edgeDelete: 'नोड डिस्कनेक्ट हो गया', + nodePaste: 'नोड चिपका हुआ', + nodeDescriptionChange: 'नोड का वर्णन बदल गया', }, errorMsg: { fieldRequired: '{{field}} आवश्यक है', @@ -208,10 +217,9 @@ const translation = { testRunIteration: 'परीक्षण रन पुनरावृत्ति', back: 'वापस', iteration: 'पुनरावृत्ति', + loop: 'लूप', }, tabs: { - 'searchBlock': 'ब्लॉक खोजें', - 'blocks': 'ब्लॉक्स', 'tools': 'टूल्स', 'allTool': 'सभी', 'builtInTool': 'अंतर्निहित', @@ -225,6 +233,8 @@ const translation = { 'searchTool': 'खोज उपकरण', 'plugin': 'प्लगइन', 'agent': 'एजेंट रणनीति', + 'searchBlock': 'खोज नोड', + 'blocks': 'नोड्स', }, blocks: { 'start': 'प्रारंभ', @@ -246,6 +256,9 @@ const translation = { 'list-operator': 'सूची ऑपरेटर', 'document-extractor': 'डॉक्टर एक्सट्रैक्टर', 'agent': 'एजेंट', + 'loop-end': 'लूप से बाहर निकलें', + 'loop': 'लूप', + 'loop-start': 'लूप प्रारंभ', }, blocksAbout: { 'start': 'वर्कफ़्लो लॉन्च करने के लिए प्रारंभिक पैरामीटर को परिभाषित करें', @@ -274,6 +287,8 @@ const translation = { 'document-extractor': 'अपलोड किए गए दस्तावेज़ों को पाठ सामग्री में पार्स करने के लिए उपयोग किया जाता है जो एलएलएम द्वारा आसानी से समझा जा सकता है।', 'list-operator': 'सरणी सामग्री फ़िल्टर या सॉर्ट करने के लिए उपयोग किया जाता है.', 'agent': 'प्रश्नों का उत्तर देने या प्राकृतिक भाषा को संसाधित करने के लिए बड़े भाषा मॉडलों को आमंत्रित करना', + 'loop': 'एक लूप को निष्पादित करें जब तक समाप्ति की शर्त पूरी न हो जाए या अधिकतम लूप संख्या प्राप्त न हो जाए।', + 'loop-end': '"ब्रेक" के समान। इस नोड में कोई विन्यास आइटम नहीं हैं। जब लूप का शरीर इस नोड पर पहुँचता है, तो लूप समाप्त होता है।', }, operator: { zoomIn: 'ज़ूम इन', @@ -284,21 +299,22 @@ const translation = { }, panel: { userInputField: 'उपयोगकर्ता इनपुट फ़ील्ड', - changeBlock: 'ब्लॉक बदलें', helpLink: 'सहायता लिंक', about: 'के बारे में', createdBy: 'द्वारा बनाया गया ', nextStep: 'अगला कदम', - addNextStep: 'इस वर्कफ़्लो में अगला ब्लॉक जोड़ें', - selectNextStep: 'अगला ब्लॉक चुनें', runThisStep: 'इस कदम को चलाएं', checklist: 'चेकलिस्ट', checklistTip: 'प्रकाशित करने से पहले सुनिश्चित करें कि सभी समस्याएं हल हो गई हैं', checklistResolved: 'सभी समस्याएं हल हो गई हैं', - organizeBlocks: 'ब्लॉक्स को व्यवस्थित करें', change: 'बदलें', optional: '(वैकल्पिक)', + moveToThisNode: 'इस नोड पर जाएं', + changeBlock: 'नोड बदलें', + addNextStep: 'इस कार्यप्रवाह में अगला कदम जोड़ें', + selectNextStep: 'अगला कदम चुनें', + organizeBlocks: 'नोड्स का आयोजन करें', }, nodes: { common: { @@ -417,6 +433,34 @@ const translation = { variable: 'वेरिएबल', }, sysQueryInUser: 'उपयोगकर्ता संदेश में sys.query आवश्यक है', + jsonSchema: { + warningTips: { + saveSchema: 'कृपया स्कीमा को सहेजने से पहले वर्तमान फ़ील्ड को संपादित करना पूरा करें', + }, + apply: 'लागू करें', + promptPlaceholder: 'अपने JSON स्किमा का वर्णन करें...', + title: 'संरचित आउटपुट स्कीमा', + fieldNamePlaceholder: 'क्षेत्र नाम', + generate: 'जनरेट करें', + resultTip: 'यहाँ उत्पन्न परिणाम है। यदि आप संतुष्ट नहीं हैं, तो आप वापस जा सकते हैं और अपने प्रॉम्प्ट को संशोधित कर सकते हैं।', + generatedResult: 'जनित परिणाम', + import: 'JSON से आयात करें', + resetDefaults: 'रीसेट करें', + instruction: 'निर्देश', + regenerate: 'पुनर्जीवित करें', + generateJsonSchema: 'JSON स्कीमा उत्पन्न करें', + addField: 'क्षेत्र जोड़ें', + doc: 'संरचित आउटपुट के बारे में अधिक जानें', + back: 'पीछे', + promptTooltip: 'पाठ विवरण को एक मानकीकृत JSON स्कीमा संरचना में परिवर्तित करें।', + descriptionPlaceholder: 'विवरण जोड़ें', + generating: 'JSON स्कीमा उत्पन्न करना...', + showAdvancedOptions: 'उन्नत विकल्प दिखाएँ', + stringValidations: 'स्ट्रिंग मान्यता', + generationTip: 'आप प्राकृतिक भाषा का उपयोग करके जल्दी से एक JSON स्कीमा बना सकते हैं।', + required: 'आवश्यक', + addChildField: 'बच्चे का क्षेत्र जोड़ें', + }, }, knowledgeRetrieval: { queryVariable: 'प्रश्न वेरिएबल', @@ -429,6 +473,33 @@ const translation = { url: 'विभाजित URL', metadata: 'अन्य मेटाडेटा', }, + metadata: { + options: { + disabled: { + title: 'अक्षम', + subTitle: 'मेटाडेटा फ़िल्टरिंग को सक्षम नहीं करना', + }, + automatic: { + title: 'स्वचालित', + subTitle: 'उपयोगकर्ता प्रश्न के आधार पर स्वचालित रूप से मेटाडेटा फिल्टरिंग शर्तें उत्पन्न करें।', + desc: 'क्वेरी वेरिएबल के आधार पर स्वचालित रूप से मेटाडेटा फ़िल्टरिंग शर्तें उत्पन्न करें', + }, + manual: { + subTitle: 'हाथ से मेटाडेटा फ़िल्टरिंग स्थितियाँ जोड़ें', + title: 'मैनुअल', + }, + }, + panel: { + placeholder: 'मान डालें', + add: 'शर्त जोड़ें', + title: 'मेटाडेटा फ़िल्टर स्थितियाँ', + select: 'परिवर्तनशील को चुनें...', + datePlaceholder: 'एक समय चुनें...', + conditions: 'शर्तें', + search: 'खोज मेटाडेटा', + }, + title: 'मेटाडेटा फ़िल्टरिंग', + }, }, http: { inputVars: 'इनपुट वेरिएबल्स', @@ -520,6 +591,8 @@ const translation = { 'not exists': 'मौजूद नहीं है', 'exists': 'मौजूद है', 'not in': 'नहीं है', + 'before': 'पहले', + 'after': 'बाद में', }, enterValue: 'मान दर्ज करें', addCondition: 'शर्त जोड़ें', @@ -535,6 +608,7 @@ const translation = { }, select: 'चुनना', addSubVariable: 'उप चर', + condition: 'स्थिति', }, variableAssigner: { title: 'वेरिएबल्स असाइन करें', @@ -578,6 +652,8 @@ const translation = { 'extend': 'पसार', '-=': '-=', 'append': 'संलग्न', + 'remove-first': 'पहला हटाओ', + 'remove-last': 'अंतिम हटाएँ', }, 'setParameter': 'पैरामीटर सेट करें...', 'noVarTip': 'चर जोड़ने के लिए "+" बटन पर क्लिक करें', @@ -588,7 +664,6 @@ const translation = { 'noAssignedVars': 'कोई उपलब्ध असाइन किए गए चर नहीं', }, tool: { - toAuthorize: 'अधिकृत करने के लिए', inputVars: 'इनपुट वेरिएबल्स', outputVars: { text: 'उपकरण द्वारा उत्पन्न सामग्री', @@ -601,6 +676,7 @@ const translation = { }, json: 'उपकरण द्वारा उत्पन्न JSON', }, + authorize: 'अधिकृत करें', }, questionClassifiers: { model: 'मॉडल', @@ -786,6 +862,38 @@ const translation = { strategyNotSet: 'एजेंटिक रणनीति सेट नहीं की गई', strategyNotFoundDescAndSwitchVersion: 'स्थापित प्लगइन संस्करण इस रणनीति को प्रदान नहीं करता है। संस्करण बदलने के लिए क्लिक करें।', }, + loop: { + ErrorMethod: { + continueOnError: 'त्रुटि पर जारी रखें', + removeAbnormalOutput: 'असामान्य आउटपुट हटाएं', + operationTerminated: 'समाप्त', + }, + inputMode: 'इनपुट मोड', + output: 'आउटपुट वेरिएबल', + input: 'इनपुट', + loop_other: '{{count}} लूप्स', + currentLoop: 'वर्तमान लूप', + deleteTitle: 'लूप नोड हटाएँ?', + error_other: '{{count}} त्रुटियाँ', + loopMaxCount: 'अधिकतम लूप गणना', + comma: ',', + deleteDesc: 'लूप नोड को हटाने से सभी बाल नोड हट जाएंगे', + error_one: '{{count}} त्रुटि', + currentLoopCount: 'वर्तमान लूप गिनती: {{count}}', + loopNode: 'लूप नोड', + loop_one: '{{count}} लूप', + initialLoopVariables: 'प्रारंभिक लूप चर', + finalLoopVariables: 'अंतिम लूप वेरिएबल्स', + variableName: 'चर चर नाम', + errorResponseMethod: 'त्रुटि प्रतिक्रिया विधि', + totalLoopCount: 'कुल लूप गिनती: {{count}}', + breakCondition: 'लूप समाप्ति स्थिति', + loopMaxCountError: 'कृपया अधिकतम लूप संख्या दर्ज करें, जो 1 से {{maxCount}} के बीच हो', + setLoopVariables: 'लूप स्कोप के भीतर वेरिएबल सेट करें', + exitConditionTip: 'एक लूप नोड को कम से कम एक निकासी स्थिति की आवश्यकता होती है', + loopVariables: 'लूप वेरियेबल्स', + breakConditionTip: 'सिर्फ उन चर को संदर्भित किया जा सकता है जो लूप के भीतर हैं जिनमें समाप्ति की शर्तें और बातचीत के चर हैं।', + }, }, tracing: { stopBy: '{{user}} द्वारा रोका गया', @@ -797,6 +905,38 @@ const translation = { noVarsForOperation: 'चयनित कार्रवाई के साथ असाइनमेंट के लिए कोई चर उपलब्ध नहीं हैं.', noAssignedVars: 'कोई उपलब्ध असाइन किए गए चर नहीं', }, + versionHistory: { + filter: { + reset: 'फिल्टर रीसेट करें', + all: 'सब', + onlyShowNamedVersions: 'केवल नामित संस्करण दिखाएँ', + onlyYours: 'बस तुम्हारा', + empty: 'कोई मेल खाता हुआ संस्करण इतिहास नहीं मिला', + }, + editField: { + title: 'शीर्षक', + releaseNotesLengthLimit: 'रिलीज नोट्स {{limit}} अक्षरों से अधिक नहीं हो सकते हैं', + titleLengthLimit: 'शीर्षक {{limit}} अक्षरों से अधिक नहीं होना चाहिए', + releaseNotes: 'रिलीज़ नोट्स', + }, + action: { + deleteFailure: 'संस्करण को हटाने में विफल', + deleteSuccess: 'संस्करण हटाया गया', + restoreSuccess: 'संस्करण पुनर्स्थापित किया गया', + updateSuccess: 'संस्करण अपडेट किया गया', + updateFailure: 'संस्करण अपडेट करने में विफल', + restoreFailure: 'संस्करण को पुनर्स्थापित करने में विफल', + }, + latest: 'लेटेस्ट', + editVersionInfo: 'संस्करण की जानकारी संपादित करें', + nameThisVersion: 'इस संस्करण का नाम दें', + title: 'संस्करण', + releaseNotesPlaceholder: 'बताइए कि क्या बदला', + currentDraft: 'वर्तमान मसौदा', + restorationTip: 'संस्करण पुनर्स्थापन के बाद, वर्तमान ड्राफ्ट अधिलेखित किया जाएगा।', + defaultName: 'अविभाजित संस्करण', + deletionTip: 'हटाना अप्रतिबंधी है, कृपया पुष्टि करें।', + }, } export default translation diff --git a/web/i18n/index.ts b/web/i18n/index.ts index 6a0d82ea36..eb49759097 100644 --- a/web/i18n/index.ts +++ b/web/i18n/index.ts @@ -12,7 +12,7 @@ export const i18n = { export type Locale = typeof i18n['locales'][number] export const setLocaleOnClient = (locale: Locale, reloadPage = true) => { - Cookies.set(LOCALE_COOKIE_NAME, locale) + Cookies.set(LOCALE_COOKIE_NAME, locale, { expires: 365 }) changeLanguage(locale) reloadPage && location.reload() } diff --git a/web/i18n/it-IT/app-debug.ts b/web/i18n/it-IT/app-debug.ts index e4555b973a..c8b3c08302 100644 --- a/web/i18n/it-IT/app-debug.ts +++ b/web/i18n/it-IT/app-debug.ts @@ -314,6 +314,7 @@ const translation = { 'inputPlaceholder': 'Per favore inserisci', 'content': 'Contenuto', 'required': 'Richiesto', + 'hide': 'Nascondi', 'errorMsg': { varNameRequired: 'Il nome della variabile è richiesto', labelNameRequired: 'Il nome dell\'etichetta è richiesto', diff --git a/web/i18n/it-IT/app-overview.ts b/web/i18n/it-IT/app-overview.ts index a8fe7f639e..2c9a3b476f 100644 --- a/web/i18n/it-IT/app-overview.ts +++ b/web/i18n/it-IT/app-overview.ts @@ -33,27 +33,27 @@ const translation = { overview: { title: 'Panoramica', appInfo: { - explanation: 'AI WebApp pronta all\'uso', + explanation: 'AI web app pronta all\'uso', accessibleAddress: 'URL Pubblico', preview: 'Anteprima', regenerate: 'Rigenera', regenerateNotice: 'Vuoi rigenerare l\'URL pubblico?', - preUseReminder: 'Attiva WebApp prima di continuare.', + preUseReminder: 'Attiva web app prima di continuare.', settings: { entry: 'Impostazioni', - title: 'Impostazioni WebApp', - webName: 'Nome WebApp', - webDesc: 'Descrizione WebApp', + title: 'Impostazioni web app', + webName: 'Nome web app', + webDesc: 'Descrizione web app', webDescTip: 'Questo testo verrà visualizzato sul lato client, fornendo una guida di base su come utilizzare l\'applicazione', - webDescPlaceholder: 'Inserisci la descrizione della WebApp', + webDescPlaceholder: 'Inserisci la descrizione della web app', language: 'Lingua', workflow: { title: 'Fasi del Workflow', show: 'Mostra', hide: 'Nascondi', subTitle: 'Dettagli del flusso di lavoro', - showDesc: 'Mostrare o nascondere i dettagli del flusso di lavoro in WebApp', + showDesc: 'Mostrare o nascondere i dettagli del flusso di lavoro in web app', }, chatColorTheme: 'Tema colore chat', chatColorThemeDesc: 'Imposta il tema colore del chatbot', @@ -74,14 +74,14 @@ const translation = { 'Inserisci il testo del disclaimer personalizzato', customDisclaimerTip: 'Il testo del disclaimer personalizzato verrà visualizzato sul lato client, fornendo informazioni aggiuntive sull\'applicazione', - copyrightTip: 'Visualizzare le informazioni sul copyright nella webapp', + copyrightTip: 'Visualizzare le informazioni sul copyright nella web app', copyrightTooltip: 'Si prega di eseguire l\'upgrade al piano Professional o superiore', }, sso: { label: 'Autenticazione SSO', - title: 'WebApp SSO', - description: 'Tutti gli utenti devono effettuare l\'accesso con SSO prima di utilizzare WebApp', - tooltip: 'Contattare l\'amministratore per abilitare l\'SSO di WebApp', + title: 'web app SSO', + description: 'Tutti gli utenti devono effettuare l\'accesso con SSO prima di utilizzare web app', + tooltip: 'Contattare l\'amministratore per abilitare l\'SSO di web app', }, modalTip: 'Impostazioni dell\'app Web lato client.', }, @@ -105,7 +105,7 @@ const translation = { customize: { way: 'modo', entry: 'Personalizza', - title: 'Personalizza AI WebApp', + title: 'Personalizza AI web app', explanation: 'Puoi personalizzare il frontend della Web App per adattarla alle tue esigenze di scenario e stile.', way1: { diff --git a/web/i18n/it-IT/app.ts b/web/i18n/it-IT/app.ts index a33dd5c571..2bd5069b6c 100644 --- a/web/i18n/it-IT/app.ts +++ b/web/i18n/it-IT/app.ts @@ -78,13 +78,13 @@ const translation = { appCreateDSLErrorTitle: 'Incompatibilità di versione', appCreateDSLWarning: 'Attenzione: la differenza di versione DSL può influire su alcune funzionalità', appCreateDSLErrorPart4: 'Versione DSL supportata dal sistema:', - forBeginners: 'PER I PRINCIPIANTI', + forBeginners: 'Tipi di app più semplici', noAppsFound: 'Nessuna app trovata', noTemplateFoundTip: 'Prova a cercare utilizzando parole chiave diverse.', foundResults: '{{conteggio}} Risultati', chatbotShortDescription: 'Chatbot basato su LLM con configurazione semplice', forAdvanced: 'PER UTENTI AVANZATI', - workflowShortDescription: 'Orchestrazione per attività di automazione a turno singolo', + workflowShortDescription: 'Flusso agentico per automazioni intelligenti', foundResult: '{{conteggio}} Risultato', noIdeaTip: 'Non hai idee? Dai un\'occhiata ai nostri modelli', completionShortDescription: 'Assistente AI per le attività di generazione del testo', @@ -94,11 +94,11 @@ const translation = { chatbotUserDescription: 'Crea rapidamente un chatbot basato su LLM con una configurazione semplice. Puoi passare a Chatflow in un secondo momento.', agentShortDescription: 'Agente intelligente con ragionamento e uso autonomo degli strumenti', completionUserDescription: 'Crea rapidamente un assistente AI per le attività di generazione di testo con una configurazione semplice.', - advancedUserDescription: 'Orchestrazione del flusso di lavoro per attività di dialogo complesse a più round con funzionalità di memoria.', - workflowUserDescription: 'Orchestrazione del flusso di lavoro per attività a ciclo singolo come l\'automazione e l\'elaborazione batch.', + advancedUserDescription: 'Flusso di lavoro con funzioni di memoria e interfaccia di chatbot.', + workflowUserDescription: 'Crea flussi di lavoro AI autonomi visivamente con la semplicità del drag-and-drop.', agentUserDescription: 'Un agente intelligente in grado di ragionare in modo iterativo e di utilizzare autonomamente gli strumenti per raggiungere gli obiettivi del compito.', - advancedShortDescription: 'Flusso di lavoro per dialoghi complessi a più turni con memoria', - chooseAppType: 'Scegli il tipo di app', + advancedShortDescription: 'Flusso di lavoro migliorato per conversazioni multiple', + chooseAppType: 'Scegli un tipo di app', }, editApp: 'Modifica Info', editAppTitle: 'Modifica Info App', @@ -171,11 +171,15 @@ const translation = { description: 'Opik è una piattaforma open source per la valutazione, il test e il monitoraggio delle applicazioni LLM.', title: 'Opik', }, + weave: { + title: 'Intrecciare', + description: 'Weave è una piattaforma open-source per valutare, testare e monitorare le applicazioni LLM.', + }, }, answerIcon: { - description: 'Se utilizzare l\'icona WebApp per la sostituzione 🤖 nell\'applicazione condivisa', - title: 'Usa l\'icona WebApp per sostituire 🤖', - descriptionInExplore: 'Se utilizzare l\'icona WebApp per sostituirla 🤖 in Esplora', + description: 'Se utilizzare l\'icona web app per la sostituzione 🤖 nell\'applicazione condivisa', + title: 'Usa l\'icona web app per sostituire 🤖', + descriptionInExplore: 'Se utilizzare l\'icona web app per sostituirla 🤖 in Esplora', }, importFromDSLUrl: 'Dall\'URL', importFromDSLFile: 'Da file DSL', @@ -206,6 +210,53 @@ const translation = { placeholder: 'Seleziona un\'app...', label: 'APP', }, + structOutput: { + modelNotSupported: 'Modello non supportato', + configure: 'Configura', + LLMResponse: 'LLM Risposta', + structured: 'Strutturato', + moreFillTip: 'Mostrando un massimo di 10 livelli di annidamento', + structuredTip: 'Le Uscite Strutturate sono una funzione che garantisce che il modello generi sempre risposte che aderiscano al tuo Schema JSON fornito.', + notConfiguredTip: 'L\'output strutturato non è stato ancora configurato.', + modelNotSupportedTip: 'Il modello attuale non supporta questa funzione e viene automaticamente downgradato a iniezione di prompt.', + }, + accessItemsDescription: { + anyone: 'Chiunque può accedere all\'app web', + specific: 'Solo gruppi o membri specifici possono accedere all\'app web.', + organization: 'Qualsiasi persona nell\'organizzazione può accedere all\'app web', + external: 'Solo gli utenti esterni autenticati possono accedere all\'applicazione Web', + }, + accessControlDialog: { + accessItems: { + anyone: 'Chiunque con il link', + specific: 'Gruppi o membri specifici', + organization: 'Solo i membri all\'interno dell\'impresa', + external: 'Utenti esterni autenticati', + }, + operateGroupAndMember: { + searchPlaceholder: 'Cerca gruppi e membri', + allMembers: 'Tutti i membri', + expand: 'Espandere', + noResult: 'Nessun risultato', + }, + title: 'Controllo di accesso all\'app web', + description: 'Imposta le autorizzazioni di accesso all\'app web', + accessLabel: 'Chi ha accesso', + groups_one: '{{count}} GRUPPO', + groups_other: '{{count}} GRUPPI', + members_one: '{{count}} MEMBRO', + members_other: '{{count}} MEMBRI', + noGroupsOrMembers: 'Nessun gruppo o membro selezionato', + webAppSSONotEnabledTip: 'Si prega di contattare l\'amministratore dell\'impresa per configurare il metodo di autenticazione dell\'app web.', + updateSuccess: 'Aggiornamento avvenuto con successo', + }, + publishApp: { + title: 'Chi può accedere all\'app web', + notSet: 'Non impostato', + notSetDesc: 'Attualmente nessuno può accedere all\'app web. Si prega di impostare i permessi.', + }, + accessControl: 'Controllo di accesso all\'app web', + noAccessPermission: 'Nessun permesso per accedere all\'app web', } export default translation diff --git a/web/i18n/it-IT/billing.ts b/web/i18n/it-IT/billing.ts index 24f5772941..69adc34569 100644 --- a/web/i18n/it-IT/billing.ts +++ b/web/i18n/it-IT/billing.ts @@ -77,6 +77,7 @@ const translation = { title: 'Crediti Messaggi', tooltip: 'Quote di invocazione dei messaggi per vari piani utilizzando i modelli OpenAI (eccetto gpt4). I messaggi oltre il limite utilizzeranno la tua chiave API OpenAI.', + titlePerMonth: '{{count,number}} messaggi/mese', }, annotatedResponse: { title: 'Limiti di Quota di Annotazione', @@ -87,30 +88,97 @@ const translation = { 'Si riferisce al numero di chiamate API che invocano solo le capacità di elaborazione della base di conoscenza di Dify.', receiptInfo: 'Solo il proprietario del team e l\'amministratore del team possono abbonarsi e visualizzare le informazioni di fatturazione', + comparePlanAndFeatures: 'Confronta piani e caratteristiche', + teamWorkspace: '{{count,number}} Spazio di lavoro di squadra', + apiRateLimit: 'Limite di richiesta API', + unlimitedApiRate: 'Nessun limite di tasso API', + freeTrialTipPrefix: 'Iscriviti e ricevi un', + teamMember_one: '{{count,number}} membro del team', + documents: '{{count,number}} Documenti di Conoscenza', + apiRateLimitUnit: '{{count,number}}/giorno', + documentsRequestQuota: '{{count,number}}/min Limite di richiesta di conoscenza', + teamMember_other: '{{count,number}} membri del team', + freeTrialTip: 'prova gratuita di 200 chiamate OpenAI.', + priceTip: 'per spazio di lavoro/', + self: 'Auto-ospitato', + documentsTooltip: 'Quota sul numero di documenti importati dalla Fonte di Dati Conoscitiva.', + freeTrialTipSuffix: 'Nessuna carta di credito richiesta', + cloud: 'Servizio Cloud', + apiRateLimitTooltip: 'Il limite di utilizzo dell\'API si applica a tutte le richieste effettuate tramite l\'API Dify, comprese la generazione di testo, le conversazioni chat, le esecuzioni di flussi di lavoro e l\'elaborazione di documenti.', + getStarted: 'Inizia', + annualBilling: 'Fatturazione annuale', + documentsRequestQuotaTooltip: 'Specifica il numero totale di azioni che un\'area di lavoro può eseguire al minuto all\'interno della base di conoscenza, compresi la creazione, l\'eliminazione, gli aggiornamenti dei dataset, il caricamento di documenti, le modifiche, l\'archiviazione e le query sulla base di conoscenza. Questa metrica viene utilizzata per valutare le prestazioni delle richieste alla base di conoscenza. Ad esempio, se un utente di Sandbox esegue 10 test consecutivi in un minuto, la sua area di lavoro sarà temporaneamente limitata dall\'eseguire le seguenti azioni per il minuto successivo: creazione, eliminazione, aggiornamenti dei dataset e caricamento o modifica di documenti.', }, plans: { sandbox: { name: 'Sandbox', description: '200 prove gratuite di GPT', includesTitle: 'Include:', + for: 'Prova gratuita delle capacità principali', }, professional: { name: 'Professional', description: 'Per individui e piccoli team per sbloccare più potenza a prezzi accessibili.', includesTitle: 'Tutto nel piano gratuito, più:', + for: 'Per sviluppatori indipendenti / piccoli team', }, team: { name: 'Team', description: 'Collabora senza limiti e goditi prestazioni di alto livello.', includesTitle: 'Tutto nel piano Professional, più:', + for: 'Per team di medie dimensioni', }, enterprise: { name: 'Enterprise', description: 'Ottieni tutte le capacità e il supporto per sistemi mission-critical su larga scala.', includesTitle: 'Tutto nel piano Team, più:', + features: { + 6: 'Sicurezza e Controlli Avanzati', + 2: 'Funzionalità esclusive per le imprese', + 4: 'SSO', + 8: 'Supporto Tecnico Professionale', + 5: 'SLA negoziati da Dify Partners', + 0: 'Soluzioni di distribuzione scalabili di livello enterprise', + 7: 'Aggiornamenti e manutenzione di Dify ufficialmente', + 1: 'Autorizzazione alla Licenza Commerciale', + 3: 'Gestione di più spazi di lavoro e imprese', + }, + price: 'Personalizzato', + for: 'Per team di grandi dimensioni', + btnText: 'Contatta le vendite', + priceTip: 'Solo fatturazione annuale', + }, + community: { + features: { + 1: 'Spazio di Lavoro Unico', + 2: 'Rispetta la Licenza Open Source di Dify', + 0: 'Tutte le funzionalità principali rilasciate sotto il repository pubblico', + }, + name: 'Comunità', + btnText: 'Inizia con la comunità', + includesTitle: 'Caratteristiche Gratuite:', + description: 'Per utenti individuali, piccole squadre o progetti non commerciali', + price: 'Gratuito', + for: 'Per utenti individuali, piccole squadre o progetti non commerciali', + }, + premium: { + features: { + 0: 'Affidabilità autogestita da vari fornitori di cloud', + 3: 'Supporto prioritario via Email e Chat', + 2: 'Personalizzazione del logo e del marchio dell\'app web', + 1: 'Spazio di Lavoro Unico', + }, + name: 'Premium', + priceTip: 'Basato su Cloud Marketplace', + includesTitle: 'Tutto dalla Community, oltre a:', + description: 'Per organizzazioni e team di medie dimensioni', + for: 'Per organizzazioni e team di medie dimensioni', + btnText: 'Ottieni Premium in', + price: 'Scalabile', + comingSoon: 'Supporto di Microsoft Azure e Google Cloud in arrivo presto', }, }, vectorSpace: { @@ -120,12 +188,26 @@ const translation = { apps: { fullTipLine1: 'Aggiorna il tuo piano per', fullTipLine2: 'creare più app.', + fullTip1des: 'Hai raggiunto il limite di costruzione delle app su questo piano.', + fullTip2des: 'Si consiglia di disinstallare le applicazioni inattive per liberare spazio, o contattarci.', + contactUs: 'Contattaci', + fullTip2: 'Limite del piano raggiunto', + fullTip1: 'Aggiorna per creare più app', }, annotatedResponse: { fullTipLine1: 'Aggiorna il tuo piano per', fullTipLine2: 'annotare più conversazioni.', quotaTitle: 'Quota di Risposta Annotata', }, + usagePage: { + buildApps: 'Costruisci app', + vectorSpace: 'Archiviazione dei dati conoscitivi', + annotationQuota: 'Quota di annotazione', + teamMembers: 'Membri del team', + documentsUploadQuota: 'Quota di Caricamento Documenti', + vectorSpaceTooltip: 'I documenti con la modalità di indicizzazione ad alta qualità consumeranno risorse di Knowledge Data Storage. Quando il Knowledge Data Storage raggiunge il limite, nuovi documenti non verranno caricati.', + }, + teamMembers: 'Membri del team', } export default translation diff --git a/web/i18n/it-IT/common.ts b/web/i18n/it-IT/common.ts index cc8129625a..b47bb23854 100644 --- a/web/i18n/it-IT/common.ts +++ b/web/i18n/it-IT/common.ts @@ -54,6 +54,10 @@ const translation = { in: 'in', viewDetails: 'Visualizza dettagli', copied: 'Copiato', + downloadSuccess: 'Download completato.', + downloadFailed: 'Download non riuscito. Per favore riprova più tardi.', + more: 'Di più', + format: 'Formato', }, errorMsg: { fieldRequired: '{{field}} è obbligatorio', @@ -150,6 +154,8 @@ const translation = { newDataset: 'Crea Conoscenza', tools: 'Strumenti', exploreMarketplace: 'Esplora il Marketplace', + appDetail: 'Dettagli dell\'app', + account: 'Account', }, userProfile: { settings: 'Impostazioni', @@ -162,6 +168,9 @@ const translation = { community: 'Comunità', about: 'Informazioni', logout: 'Esci', + support: 'Supporto', + compliance: 'Conformità', + github: 'GitHub', }, settings: { accountGroup: 'ACCOUNT', @@ -214,6 +223,9 @@ const translation = { feedbackTitle: 'Valutazione', feedbackLabel: 'Dicci perché hai cancellato il tuo account?', feedbackPlaceholder: 'Opzionale', + workspaceIcon: 'Icona della workspace', + editWorkspaceInfo: 'Modifica informazioni dello spazio di lavoro', + workspaceName: 'Nome del Workspace', }, members: { team: 'Team', @@ -483,7 +495,7 @@ const translation = { title: 'Le estensioni API forniscono una gestione centralizzata delle API, semplificando la configurazione per un facile utilizzo nelle applicazioni di Dify.', link: 'Scopri come sviluppare la tua estensione API.', - linkUrl: 'https://docs.dify.ai/features/extension/api_based_extension', + linkUrl: 'https://docs.dify.ai/en/guides/extension/api-based-extension/README', add: 'Aggiungi Estensione API', selector: { title: 'Estensione API', @@ -571,9 +583,10 @@ const translation = { vectorHash: 'Hash del vettore:', hitScore: 'Punteggio di recupero:', }, - inputPlaceholder: 'Parla con il bot', + inputPlaceholder: 'Parla con il {{botName}}', thinking: 'Pensante...', thought: 'Pensiero', + resend: 'Reinvia', }, promptEditor: { placeholder: @@ -664,10 +677,31 @@ const translation = { license: { expiring_plural: 'Scadenza tra {{count}} giorni', expiring: 'Scadenza in un giorno', + unlimited: 'Illimitato', }, pagination: { perPage: 'Articoli per pagina', }, + theme: { + light: 'luce', + auto: 'sistema', + dark: 'scuro', + theme: 'Tema', + }, + compliance: { + gdpr: 'GDPR DPA', + professionalUpgradeTooltip: 'Disponibile solo con un piano Team o superiore.', + sandboxUpgradeTooltip: 'Disponibile solo con un piano Professionale o Team.', + soc2Type2: 'Rapporto SOC 2 di Tipo II', + iso27001: 'Certificazione ISO 27001:2022', + soc2Type1: 'Rapporto SOC 2 Tipo I', + }, + imageInput: { + supportedFormats: 'Supporta PNG, JPG, JPEG, WEBP e GIF', + browse: 'sfogliare', + dropImageHere: 'Trascina la tua immagine qui, oppure', + }, + you: 'Tu', } export default translation diff --git a/web/i18n/it-IT/custom.ts b/web/i18n/it-IT/custom.ts index 7eb2efcf36..c6164ce5da 100644 --- a/web/i18n/it-IT/custom.ts +++ b/web/i18n/it-IT/custom.ts @@ -3,9 +3,11 @@ const translation = { upgradeTip: { prefix: 'Aggiorna il tuo piano per', suffix: 'personalizzare il tuo marchio.', + title: 'Aggiorna il tuo piano', + des: 'Aggiorna il tuo piano per personalizzare il tuo marchio', }, webapp: { - title: 'Personalizza il marchio WebApp', + title: 'Personalizza il marchio web app', removeBrand: 'Rimuovi Powered by Dify', changeLogo: 'Cambia immagine del marchio Powered by', changeLogoTip: 'Formato SVG o PNG con una dimensione minima di 40x40px', diff --git a/web/i18n/it-IT/dataset-creation.ts b/web/i18n/it-IT/dataset-creation.ts index 28a1c7a376..ec9b5e3138 100644 --- a/web/i18n/it-IT/dataset-creation.ts +++ b/web/i18n/it-IT/dataset-creation.ts @@ -27,7 +27,7 @@ const translation = { }, uploader: { title: 'Carica file', - button: 'Trascina e rilascia il file, o', + button: 'Trascina e rilascia file o cartelle, oppure', browse: 'Sfoglia', tip: 'Supporta {{supportTypes}}. Max {{size}}MB ciascuno.', validation: { @@ -94,6 +94,14 @@ const translation = { jinaReaderNotConfiguredDescription: 'Configura Jina Reader inserendo la tua chiave API gratuita per l\'accesso.', useSitemap: 'Usa la mappa del sito', chooseProvider: 'Seleziona un fornitore', + watercrawlDoc: 'Documentazione di Watercrawl', + waterCrawlNotConfiguredDescription: 'Configura Watercrawl con la chiave API per utilizzarlo.', + configureFirecrawl: 'Configura Firecrawl', + watercrawlTitle: 'Estrai contenuti web con Watercrawl', + configureJinaReader: 'Configura Jina Reader', + configureWatercrawl: 'Configura Watercrawl', + waterCrawlNotConfigured: 'Watercrawl non è configurato', + watercrawlDocLink: 'https://docs.dify.ai/it/guides/knowledge-base/create-knowledge-and-upload-documents/import-content-data/sync-from-website', }, cancel: 'Annulla', }, @@ -223,6 +231,11 @@ const translation = { title: 'Connettersi ad altre origini dati?', description: 'Attualmente, la knowledge base di Dify ha solo fonti di dati limitate. Contribuire con una fonte di dati alla knowledge base di Dify è un modo fantastico per migliorare la flessibilità e la potenza della piattaforma per tutti gli utenti. La nostra guida ai contributi ti aiuta a iniziare. Clicca sul link sottostante per saperne di più.', }, + watercrawl: { + getApiKeyLinkText: 'Ottieni la tua chiave API da watercrawl.dev', + apiKeyPlaceholder: 'Chiave API da watercrawl.dev', + configWatercrawl: 'Configura Watercrawl', + }, } export default translation diff --git a/web/i18n/it-IT/dataset-settings.ts b/web/i18n/it-IT/dataset-settings.ts index c799872975..a03bfbcd74 100644 --- a/web/i18n/it-IT/dataset-settings.ts +++ b/web/i18n/it-IT/dataset-settings.ts @@ -32,6 +32,7 @@ const translation = { description: ' sul metodo di recupero.', longDescription: ' sul metodo di recupero, puoi cambiare questo in qualsiasi momento nelle impostazioni della Conoscenza.', + method: 'Metodo di recupero', }, save: 'Salva', retrievalSettings: 'Impostazioni di recupero', diff --git a/web/i18n/it-IT/dataset.ts b/web/i18n/it-IT/dataset.ts index dec41bec42..c2c4963371 100644 --- a/web/i18n/it-IT/dataset.ts +++ b/web/i18n/it-IT/dataset.ts @@ -175,6 +175,53 @@ const translation = { localDocs: 'Documenti locali', allKnowledge: 'Tutta la conoscenza', allKnowledgeDescription: 'Selezionare questa opzione per visualizzare tutte le informazioni in questa area di lavoro. Solo il proprietario dell\'area di lavoro può gestire tutte le conoscenze.', + metadata: { + createMetadata: { + title: 'Nuovi Metadati', + back: 'Indietro', + type: 'Tipo', + name: 'Nome', + namePlaceholder: 'Aggiungi nome dei metadati', + }, + checkName: { + invalid: 'Il nome dei metadati può contenere solo lettere minuscole, numeri e underscore e deve iniziare con una lettera minuscola.', + empty: 'Il nome dei metadati non può essere vuoto', + }, + batchEditMetadata: { + multipleValue: 'Valore Multiplo', + editDocumentsNum: 'Modifica {{num}} documenti', + applyToAllSelectDocument: 'Applica a tutti i documenti selezionati', + editMetadata: 'Modifica metadati', + applyToAllSelectDocumentTip: 'Creare automaticamente tutti i metadati modificati e nuovi sopra menzionati per tutti i documenti selezionati, altrimenti la modifica dei metadati si applicherà solo ai documenti che li possiedono.', + }, + selectMetadata: { + search: 'Cerca metadati', + newAction: 'Nuovi Metadati', + manageAction: 'Gestire', + }, + datasetMetadata: { + deleteTitle: 'Conferma per eliminare', + name: 'Nome', + addMetaData: 'Aggiungi metadata', + values: '{{num}} Valori', + disabled: 'Disabilitato', + rename: 'Rinomina', + namePlaceholder: 'Nome dei metadati', + deleteContent: 'Sei sicuro di voler eliminare i metadati "{{name}}"?', + builtInDescription: 'I metadati incorporati vengono estratti e generati automaticamente. Devono essere abilitati prima dell\'uso e non possono essere modificati.', + description: 'Puoi gestire tutti i metadati in questa conoscenza qui. Le modifiche saranno sincronizzate con ogni documento.', + }, + documentMetadata: { + documentInformation: 'Informazioni sul documento', + technicalParameters: 'Parametri tecnici', + startLabeling: 'Inizia a etichettare', + metadataToolTip: 'I metadati fungono da filtro critico che migliora l\'accuratezza e la rilevanza del recupero delle informazioni. Puoi modificare e aggiungere metadati per questo documento qui.', + }, + addMetadata: 'Aggiungi metadata', + metadata: 'Metadata', + chooseTime: 'Scegli un orario...', + }, + embeddingModelNotAvailable: 'Il modello di embedding non è disponibile.', } export default translation diff --git a/web/i18n/it-IT/education.ts b/web/i18n/it-IT/education.ts new file mode 100644 index 0000000000..1abb22280e --- /dev/null +++ b/web/i18n/it-IT/education.ts @@ -0,0 +1,47 @@ +const translation = { + toVerifiedTip: { + coupon: 'coupon esclusivo al 100%', + end: 'per il Piano Professionale Dify.', + front: 'Ora sei idoneo per lo stato di Educazione Verificata. Per favore, inserisci le tue informazioni educative qui sotto per completare il processo e ricevere un', + }, + form: { + schoolName: { + title: 'Il Nome della tua Scuola', + placeholder: 'Inserisci il nome ufficiale e completo della tua scuola', + }, + schoolRole: { + option: { + teacher: 'Insegnante', + student: 'Studente', + administrator: 'Amministratore scolastico', + }, + title: 'Il tuo ruolo scolastico', + }, + terms: { + desc: { + and: 'e', + privacyPolicy: 'Informativa sulla privacy', + end: '. Inviando:', + front: 'Le tue informazioni e l\'uso dello stato di Educazione Verificato sono soggetti a nostri', + termsOfService: 'Termini di servizio', + }, + option: { + inSchool: 'Confermo di essere iscritto o impiegato presso l\'istituzione fornita. Dify può richiedere una prova di iscrizione/impegno. Se rappresento erroneamente la mia idoneità, accetto di pagare eventuali tasse inizialmente esonerate in base al mio stato di istruzione.', + age: 'Confermo di avere almeno 18 anni', + }, + title: 'Termini e Accordi', + }, + }, + toVerified: 'Fai verificare la tua istruzione', + successTitle: 'Hai ottenuto l\'istruzione Dify verificata', + submitError: 'Invio del modulo non riuscito. Per favore riprova più tardi.', + submit: 'Invia', + currentSigned: 'ATTUALMENTE ACCEDUTO COME', + successContent: 'Abbiamo emesso un coupon sconto del 100% per il piano Dify Professionale sul tuo account. Il coupon è valido per un anno, ti preghiamo di utilizzarlo entro il periodo di validità.', + learn: 'Scopri come far verificare la tua istruzione', + emailLabel: 'La tua email attuale', + rejectContent: 'Sfortunatamente, non sei idoneo per lo status di Educazione Verificata e quindi non puoi ricevere il coupon esclusivo del 100% per il Piano Professionale Dify se usi questo indirizzo email.', + rejectTitle: 'La tua verifica educativa Dify è stata rifiutata.', +} + +export default translation diff --git a/web/i18n/it-IT/explore.ts b/web/i18n/it-IT/explore.ts index 60508028a1..d94df45d54 100644 --- a/web/i18n/it-IT/explore.ts +++ b/web/i18n/it-IT/explore.ts @@ -38,6 +38,7 @@ const translation = { HR: 'Risorse Umane', Workflow: 'Flusso di lavoro', Agent: 'Agente', + Entertainment: 'Intrattenimento', }, } diff --git a/web/i18n/it-IT/login.ts b/web/i18n/it-IT/login.ts index 350424259e..cbc05d60c1 100644 --- a/web/i18n/it-IT/login.ts +++ b/web/i18n/it-IT/login.ts @@ -115,6 +115,11 @@ const translation = { licenseExpiredTip: 'La licenza Dify Enterprise per la tua area di lavoro è scaduta. Contatta il tuo amministratore per continuare a utilizzare Dify.', licenseInactiveTip: 'La licenza Dify Enterprise per la tua area di lavoro è inattiva. Contatta il tuo amministratore per continuare a utilizzare Dify.', licenseInactive: 'Licenza inattiva', + webapp: { + noLoginMethod: 'Metodo di autenticazione non configurato per l\'app web', + noLoginMethodTip: 'Si prega di contattare l\'amministratore del sistema per aggiungere un metodo di autenticazione.', + disabled: 'L\'autenticazione dell\'app web è disabilitata. Si prega di contattare l\'amministratore di sistema per abilitarla. Puoi provare a utilizzare l\'app direttamente.', + }, } export default translation diff --git a/web/i18n/it-IT/plugin.ts b/web/i18n/it-IT/plugin.ts index 2c57e5b7af..f1ef0b9d53 100644 --- a/web/i18n/it-IT/plugin.ts +++ b/web/i18n/it-IT/plugin.ts @@ -62,6 +62,7 @@ const translation = { descriptionLabel: 'Descrizione dell\'utensile', auto: 'Automatico', paramsTip2: 'Quando \'Automatico\' è disattivato, viene utilizzato il valore predefinito.', + toolSetting: 'Impostazioni degli strumenti', }, modelNum: '{{num}} MODELLI INCLUSI', endpointModalTitle: 'Endpoint di configurazione', @@ -180,6 +181,8 @@ const translation = { sortBy: 'Città nera', and: 'e', viewMore: 'Vedi di più', + verifiedTip: 'Verificato da Dify', + partnerTip: 'Verificato da un partner Dify', }, task: { clearAll: 'Cancella tutto', @@ -200,10 +203,15 @@ const translation = { install: '{{num}} installazioni', findMoreInMarketplace: 'Scopri di più su Marketplace', installPlugin: 'Installa il plugin', - submitPlugin: 'Invia plugin', searchPlugins: 'Plugin di ricerca', search: 'Ricerca', installFrom: 'INSTALLA DA', + metadata: { + title: 'Plugin', + }, + difyVersionNotCompatible: 'L\'attuale versione di Dify non è compatibile con questo plugin, si prega di aggiornare alla versione minima richiesta: {{minimalDifyVersion}}', + requestAPlugin: 'Richiedi un plugin', + publishPlugins: 'Pubblicare plugin', } export default translation diff --git a/web/i18n/it-IT/share-app.ts b/web/i18n/it-IT/share-app.ts index 772a6e902d..4c6c18ff33 100644 --- a/web/i18n/it-IT/share-app.ts +++ b/web/i18n/it-IT/share-app.ts @@ -28,6 +28,12 @@ const translation = { }, tryToSolve: 'Prova a risolvere', temporarySystemIssue: 'Spiacente, problema temporaneo del sistema.', + expand: 'Espandi', + collapse: 'Riduci', + newChatTip: 'Già in una nuova chat', + chatSettingsTitle: 'Nuova configurazione della chat', + chatFormTip: 'Le impostazioni della chat non possono essere modificate dopo che la chat è iniziata.', + viewChatSettings: 'Visualizza le impostazioni della chat', }, generation: { tabs: { @@ -70,6 +76,11 @@ const translation = { 'Riga {{rowIndex}}: il valore di {{varName}} non può essere superiore a {{maxLength}} caratteri', atLeastOne: 'Per favore inserisci almeno una riga nel file caricato.', }, + execution: 'ESECUZIONE', + executions: '{{num}} ESECUZIONI', + }, + login: { + backToHome: 'Torna alla home', }, } diff --git a/web/i18n/it-IT/time.ts b/web/i18n/it-IT/time.ts index e2410dd34b..f330e8fd6c 100644 --- a/web/i18n/it-IT/time.ts +++ b/web/i18n/it-IT/time.ts @@ -1,3 +1,37 @@ -const translation = {} +const translation = { + daysInWeek: { + Tue: 'Martedì', + Thu: 'Giovedì', + Mon: 'Mon', + Fri: 'Venerdì', + Sat: 'Sat', + Wed: 'Mercoledì', + Sun: 'Sole', + }, + months: { + February: 'Febbraio', + April: 'Aprile', + January: 'Gennaio', + December: 'Dicembre', + March: 'Marzo', + May: 'Maggio', + August: 'Agosto', + June: 'Giugno', + July: 'Luglio', + October: 'Ottobre', + September: 'Settembre', + November: 'Novembre', + }, + operation: { + ok: 'OK', + now: 'Ora', + cancel: 'Annulla', + pickDate: 'Seleziona Data', + }, + title: { + pickTime: 'Scegli Tempo', + }, + defaultPlaceholder: 'Scegli un orario...', +} export default translation diff --git a/web/i18n/it-IT/tools.ts b/web/i18n/it-IT/tools.ts index 65899e6330..3c89d3a749 100644 --- a/web/i18n/it-IT/tools.ts +++ b/web/i18n/it-IT/tools.ts @@ -15,7 +15,6 @@ const translation = { }, author: 'Di', auth: { - unauthorized: 'Per Autorizzare', authorized: 'Autorizzato', setup: 'Configura l\'autorizzazione per utilizzare', setupModalTitle: 'Configura Autorizzazione', diff --git a/web/i18n/it-IT/workflow.ts b/web/i18n/it-IT/workflow.ts index 58d3455cd0..ae577460d0 100644 --- a/web/i18n/it-IT/workflow.ts +++ b/web/i18n/it-IT/workflow.ts @@ -38,8 +38,6 @@ const translation = { setVarValuePlaceholder: 'Imposta variabile', needConnectTip: 'Questo passaggio non è collegato a nulla', maxTreeDepth: 'Limite massimo di {{depth}} nodi per ramo', - needEndNode: 'Deve essere aggiunto il blocco di Fine', - needAnswerNode: 'Deve essere aggiunto il blocco di Risposta', workflowProcess: 'Processo di flusso di lavoro', notRunning: 'Non ancora in esecuzione', previewPlaceholder: @@ -60,7 +58,6 @@ const translation = { learnMore: 'Scopri di più', copy: 'Copia', duplicate: 'Duplica', - addBlock: 'Aggiungi Blocco', pasteHere: 'Incolla Qui', pointerMode: 'Modalità Puntatore', handMode: 'Modalità Mano', @@ -110,6 +107,18 @@ const translation = { addFailureBranch: 'Aggiungi ramo non riuscito', noHistory: 'Nessuna storia', loadMore: 'Carica più flussi di lavoro', + publishUpdate: 'Pubblica aggiornamento', + versionHistory: 'Cronologia delle versioni', + exitVersions: 'Uscita Versioni', + referenceVar: 'Variabile di riferimento', + exportSVG: 'Esporta come SVG', + exportImage: 'Esporta immagine', + exportJPEG: 'Esporta come JPEG', + noExist: 'Nessuna variabile del genere', + exportPNG: 'Esporta come PNG', + needEndNode: 'Deve essere aggiunto il nodo finale', + addBlock: 'Aggiungi nodo', + needAnswerNode: 'Deve essere aggiunto il nodo di risposta', }, env: { envPanelTitle: 'Variabili d\'Ambiente', @@ -172,19 +181,19 @@ const translation = { stepForward_other: '{{count}} passi avanti', sessionStart: 'Inizio sessione', currentState: 'Stato attuale', - nodeTitleChange: 'Titolo del blocco modificato', - nodeDescriptionChange: 'Descrizione del blocco modificata', - nodeDragStop: 'Blocco spostato', - nodeChange: 'Blocco modificato', - nodeConnect: 'Blocco collegato', - nodePaste: 'Blocco incollato', - nodeDelete: 'Blocco eliminato', - nodeAdd: 'Blocco aggiunto', - nodeResize: 'Blocco ridimensionato', noteAdd: 'Nota aggiunta', noteChange: 'Nota modificata', noteDelete: 'Nota eliminata', - edgeDelete: 'Blocco scollegato', + nodeDescriptionChange: 'Descrizione del nodo cambiata', + nodePaste: 'Nodo incollato', + nodeChange: 'Nodo cambiato', + nodeResize: 'Nodo ridimensionato', + nodeDelete: 'Nodo eliminato', + nodeTitleChange: 'Titolo del nodo cambiato', + edgeDelete: 'Nodo disconnesso', + nodeAdd: 'Nodo aggiunto', + nodeDragStop: 'Nodo spostato', + nodeConnect: 'Nodo connesso', }, errorMsg: { fieldRequired: '{{field}} è richiesto', @@ -210,10 +219,9 @@ const translation = { testRunIteration: 'Iterazione Esecuzione Test', back: 'Indietro', iteration: 'Iterazione', + loop: 'Anello', }, tabs: { - 'searchBlock': 'Cerca blocco', - 'blocks': 'Blocchi', 'tools': 'Strumenti', 'allTool': 'Tutti', 'builtInTool': 'Integrato', @@ -227,6 +235,8 @@ const translation = { 'searchTool': 'Strumento di ricerca', 'agent': 'Strategia dell\'agente', 'plugin': 'Plugin', + 'searchBlock': 'Cerca nodo', + 'blocks': 'Nodi', }, blocks: { 'start': 'Inizio', @@ -248,6 +258,9 @@ const translation = { 'document-extractor': 'Estrattore di documenti', 'list-operator': 'Operatore di elenco', 'agent': 'Agente', + 'loop-end': 'Uscire dal ciclo', + 'loop-start': 'Inizio ciclo', + 'loop': 'Anello', }, blocksAbout: { 'start': 'Definisci i parametri iniziali per l\'avvio di un flusso di lavoro', @@ -277,6 +290,8 @@ const translation = { 'list-operator': 'Utilizzato per filtrare o ordinare il contenuto della matrice.', 'document-extractor': 'Utilizzato per analizzare i documenti caricati in contenuti di testo facilmente comprensibili da LLM.', 'agent': 'Richiamo di modelli linguistici di grandi dimensioni per rispondere a domande o elaborare il linguaggio naturale', + 'loop-end': 'Equivalente a "break". Questo nodo non ha elementi di configurazione. Quando il corpo del ciclo raggiunge questo nodo, il ciclo termina.', + 'loop': 'Esegui un ciclo di logica fino a quando la condizione di terminazione non viene soddisfatta o il numero massimo di cicli viene raggiunto.', }, operator: { zoomIn: 'Zoom In', @@ -287,21 +302,22 @@ const translation = { }, panel: { userInputField: 'Campo di Input Utente', - changeBlock: 'Cambia Blocco', helpLink: 'Link di Aiuto', about: 'Informazioni', createdBy: 'Creato da ', nextStep: 'Prossimo Passo', - addNextStep: 'Aggiungi il prossimo blocco in questo flusso di lavoro', - selectNextStep: 'Seleziona Prossimo Blocco', runThisStep: 'Esegui questo passo', checklist: 'Checklist', checklistTip: 'Assicurati che tutti i problemi siano risolti prima di pubblicare', checklistResolved: 'Tutti i problemi sono risolti', - organizeBlocks: 'Organizza blocchi', change: 'Cambia', optional: '(opzionale)', + moveToThisNode: 'Sposta a questo nodo', + changeBlock: 'Cambia Nodo', + selectNextStep: 'Seleziona il prossimo passo', + organizeBlocks: 'Organizzare i nodi', + addNextStep: 'Aggiungi il prossimo passo in questo flusso di lavoro', }, nodes: { common: { @@ -421,6 +437,34 @@ const translation = { variable: 'Variabile', }, sysQueryInUser: 'sys.query nel messaggio utente è richiesto', + jsonSchema: { + warningTips: { + saveSchema: 'Si prega di completare la modifica del campo corrente prima di salvare lo schema.', + }, + descriptionPlaceholder: 'Aggiungi descrizione', + generate: 'Genera', + generateJsonSchema: 'Genera Schema JSON', + generationTip: 'Puoi usare il linguaggio naturale per creare rapidamente uno schema JSON.', + back: 'Indietro', + apply: 'Applica', + showAdvancedOptions: 'Mostra opzioni avanzate', + stringValidations: 'Validazioni delle stringhe', + regenerate: 'Rigenerare', + required: 'richiesto', + resetDefaults: 'Ripristina', + addField: 'Aggiungi campo', + promptPlaceholder: 'Descrivi il tuo schema JSON...', + title: 'Schema di Output Strutturato', + instruction: 'Istruzione', + addChildField: 'Aggiungi campo bambino', + fieldNamePlaceholder: 'Nome del campo', + promptTooltip: 'Converte la descrizione del testo in una struttura JSON Schema standardizzata.', + doc: 'Scopri di più sull\'output strutturato', + import: 'Importa da JSON', + resultTip: 'Ecco il risultato generato. Se non sei soddisfatto, puoi tornare indietro e modificare il tuo prompt.', + generating: 'Generazione dello schema JSON...', + generatedResult: 'Risultato generato', + }, }, knowledgeRetrieval: { queryVariable: 'Variabile Query', @@ -433,6 +477,32 @@ const translation = { url: 'URL segmentato', metadata: 'Altri metadati', }, + metadata: { + options: { + disabled: { + title: 'Disabilitato', + subTitle: 'Non abilitare il filtraggio dei metadati', + }, + automatic: { + subTitle: 'Genera automaticamente condizioni di filtraggio dei metadati in base alla query dell\'utente', + desc: 'Genera automaticamente condizioni di filtraggio dei metadati basate sulla variabile di query', + }, + manual: { + title: 'Manuale', + subTitle: 'Aggiungere manualmente le condizioni di filtraggio dei metadati', + }, + }, + panel: { + conditions: 'Condizioni', + select: 'Seleziona variabile...', + title: 'Condizioni di filtro dei metadati', + add: 'Aggiungi condizione', + datePlaceholder: 'Scegli un orario...', + placeholder: 'Inserisci valore', + search: 'Cerca metadati', + }, + title: 'Filtraggio dei metadati', + }, }, http: { inputVars: 'Variabili di Input', @@ -524,6 +594,7 @@ const translation = { 'not in': 'non in', 'exists': 'Esiste', 'not exists': 'non esiste', + 'after': 'dopo', }, enterValue: 'Inserisci valore', addCondition: 'Aggiungi Condizione', @@ -539,6 +610,7 @@ const translation = { }, addSubVariable: 'Variabile secondaria', select: 'Selezionare', + condition: 'Condizione', }, variableAssigner: { title: 'Assegna variabili', @@ -582,6 +654,8 @@ const translation = { 'over-write': 'Sovrascrivere', 'extend': 'Estendere', 'clear': 'Chiaro', + 'remove-last': 'Rimuovi ultimo', + 'remove-first': 'Rimuovi primo', }, 'setParameter': 'Imposta parametro...', 'variables': 'Variabili', @@ -592,7 +666,6 @@ const translation = { 'noVarTip': 'Fare clic sul pulsante "+" per aggiungere variabili', }, tool: { - toAuthorize: 'Per autorizzare', inputVars: 'Variabili di Input', outputVars: { text: 'contenuto generato dallo strumento', @@ -606,6 +679,7 @@ const translation = { }, json: 'json generato dallo strumento', }, + authorize: 'Autorizza', }, questionClassifiers: { model: 'modello', @@ -793,6 +867,38 @@ const translation = { pluginNotInstalled: 'Questo plugin non è installato', pluginNotFoundDesc: 'Questo plugin viene installato da GitHub. Vai su Plugin per reinstallare', }, + loop: { + ErrorMethod: { + operationTerminated: 'Terminato', + removeAbnormalOutput: 'Rimuovi l\'output anormale', + continueOnError: 'Continua con l\'errore', + }, + currentLoop: 'Anello Corrente', + breakConditionTip: 'Solo le variabili all\'interno dei cicli con condizioni di terminazione e le variabili di conversazione possono essere riferite.', + loopVariables: 'Variabili di ciclo', + inputMode: 'Modalità di input', + errorResponseMethod: 'Metodo di risposta all\'errore', + error_one: '{{count}} Errore', + loop_one: '{{count}} Ciclo', + loopMaxCount: 'Conteggio massimo dei loop', + breakCondition: 'Condizione di terminazione del ciclo', + comma: ',', + loopNode: 'Nodo Ciclico', + finalLoopVariables: 'Variabili del ciclo finale', + loopMaxCountError: 'Si prega di inserire un conteggio massimo di cicli valido, compreso tra 1 e {{maxCount}}', + currentLoopCount: 'Conteggio attuale del ciclo: {{count}}', + input: 'Input', + setLoopVariables: 'Imposta le variabili all\'interno dell\'ambito del ciclo', + deleteTitle: 'Elimina nodo ciclo?', + output: 'Variabile di Output', + initialLoopVariables: 'Variabili di loop iniziali', + deleteDesc: 'Cancellare il nodo di ciclo rimuoverà tutti i nodi figli', + loop_other: '{{count}} anelli', + variableName: 'Nome Variabile', + totalLoopCount: 'Conteggio totale dei cicli: {{count}}', + exitConditionTip: 'Un nodo di ciclo ha bisogno di almeno una condizione di uscita.', + error_other: '{{count}} Errori', + }, }, tracing: { stopBy: 'Interrotto da {{user}}', @@ -804,6 +910,38 @@ const translation = { assignedVarsDescription: 'Le variabili assegnate devono essere variabili scrivibili, ad esempio', conversationVars: 'Variabili di conversazione', }, + versionHistory: { + filter: { + reset: 'Ripristina filtro', + all: 'Tutto', + onlyYours: 'Solo tuo', + empty: 'Nessuna cronologia delle versioni corrispondente trovata', + onlyShowNamedVersions: 'Mostra solo le versioni con nome', + }, + editField: { + titleLengthLimit: 'Il titolo non può superare {{limit}} caratteri', + releaseNotes: 'Note di rilascio', + title: 'Titolo', + releaseNotesLengthLimit: 'Le note di rilascio non possono superare i {{limit}} caratteri', + }, + action: { + restoreSuccess: 'Versione ripristinata', + restoreFailure: 'Impossibile ripristinare la versione', + deleteSuccess: 'Versione eliminata', + updateSuccess: 'Versione aggiornata', + deleteFailure: 'Impossibile eliminare la versione', + updateFailure: 'Impossibile aggiornare la versione', + }, + latest: 'Ultimo', + defaultName: 'Versione senza titolo', + deletionTip: 'La cancellazione è irreversibile, si prega di confermare.', + nameThisVersion: 'Chiamare questa versione', + editVersionInfo: 'Modifica le informazioni sulla versione', + releaseNotesPlaceholder: 'Descrivi cosa è cambiato', + currentDraft: 'Bozza attuale', + restorationTip: 'Dopo il ripristino della versione, la bozza attuale verrà sovrascritta.', + title: 'Versioni', + }, } export default translation diff --git a/web/i18n/ja-JP/app-annotation.ts b/web/i18n/ja-JP/app-annotation.ts index 297e01d184..38b891d9d8 100644 --- a/web/i18n/ja-JP/app-annotation.ts +++ b/web/i18n/ja-JP/app-annotation.ts @@ -42,9 +42,9 @@ const translation = { }, batchModal: { title: '一括インポート', - csvUploadTitle: 'CSVファイルをここにドラッグ&ドロップするか、', + csvUploadTitle: 'CSV ファイルをここにドラッグ&ドロップするか、', browse: '参照', - tip: 'CSVファイルは以下の構造に準拠する必要があります:', + tip: 'CSV ファイルは以下の構造に準拠する必要があります:', question: '質問', answer: '回答', contentTitle: 'チャンクの内容', diff --git a/web/i18n/ja-JP/app-api.ts b/web/i18n/ja-JP/app-api.ts index 4d30a71a10..e344ad04a9 100644 --- a/web/i18n/ja-JP/app-api.ts +++ b/web/i18n/ja-JP/app-api.ts @@ -1,6 +1,6 @@ const translation = { - apiServer: 'APIサーバー', - apiKey: 'APIキー', + apiServer: 'API サーバー', + apiKey: 'API キー', status: 'ステータス', disabled: '無効', ok: '稼働中', @@ -15,8 +15,8 @@ const translation = { }, never: 'なし', apiKeyModal: { - apiSecretKey: 'APIシークレットキー', - apiSecretKeyTips: 'APIの悪用を防ぐために、APIキーを保護してください。フロントエンドのコードで平文として使用しないでください。:)', + apiSecretKey: 'API シークレットキー', + apiSecretKeyTips: 'API の悪用を防ぐために、API キーを保護してください。フロントエンドのコードで平文として使用しないでください。:)', createNewSecretKey: '新しいシークレットキーを作成', secretKey: 'シークレットキー', created: '作成日時', @@ -29,44 +29,44 @@ const translation = { ok: 'OK', }, completionMode: { - title: '補完アプリAPI', - info: '記事、要約、翻訳などの高品質なテキスト生成には、ユーザーの入力を使用した補完メッセージAPIを使用します。テキスト生成は、Dify Prompt Engineeringで設定されたモデルパラメータとプロンプトテンプレートに依存しています。', + title: '補完アプリ API', + info: '記事、要約、翻訳などの高品質なテキスト生成には、ユーザーの入力を使用した補完メッセージ API を使用します。テキスト生成は、Dify Prompt Engineering で設定されたモデルパラメータとプロンプトテンプレートに依存しています。', createCompletionApi: '補完メッセージの作成', createCompletionApiTip: '質疑応答モードをサポートするために、補完メッセージを作成します。', - inputsTips: '(オプション)Prompt Engの変数に対応するキーと値のペアとしてユーザー入力フィールドを提供します。キーは変数名で、値はパラメータの値です。フィールドのタイプがSelectの場合、送信される値は事前に設定された選択肢のいずれかである必要があります。', + inputsTips: '(オプション)Prompt Eng の変数に対応するキーと値のペアとしてユーザー入力フィールドを提供します。キーは変数名で、値はパラメータの値です。フィールドのタイプが Select の場合、送信される値は事前に設定された選択肢のいずれかである必要があります。', queryTips: 'ユーザーの入力テキスト内容。', blocking: 'ブロッキングタイプで、実行が完了して結果が返されるまで待機します。(処理が長い場合、リクエストは中断される場合があります)', streaming: 'ストリーミングの返却。SSE(Server-Sent Events)に基づいたストリーミングの返却の実装。', messageFeedbackApi: 'メッセージフィードバック(いいね)', messageFeedbackApiTip: 'エンドユーザーの代わりに受信したメッセージを「いいね」または「いいね」で評価します。このデータはログ&注釈ページで表示され、将来のモデルの微調整に使用されます。', - messageIDTip: 'メッセージID', - ratingTip: 'いいねまたはいいね、nullは元に戻す', + messageIDTip: 'メッセージ ID', + ratingTip: 'いいねまたはいいね、null は元に戻す', parametersApi: 'アプリケーションパラメータ情報の取得', parametersApiTip: '変数名、フィールド名、タイプ、デフォルト値を含む設定済みの入力パラメータを取得します。通常、これらのフィールドをフォームに表示したり、クライアントの読み込み後にデフォルト値を入力したりするために使用されます。', }, chatMode: { - title: 'チャットアプリAPI', - info: '質疑応答形式を使用した多目的の対話型アプリケーションには、チャットメッセージAPIを呼び出して対話を開始します。返されたconversation_idを渡すことで、継続的な会話を維持します。応答パラメータとテンプレートは、Dify Prompt Engの設定に依存します。', + title: 'チャットアプリ API', + info: '質疑応答形式を使用した多目的の対話型アプリケーションには、チャットメッセージ API を呼び出して対話を開始します。返された conversation_id を渡すことで、継続的な会話を維持します。応答パラメータとテンプレートは、Dify Prompt Eng の設定に依存します。', createChatApi: 'チャットメッセージの作成', createChatApiTip: '新しい会話メッセージを作成するか、既存の対話を継続します。', - inputsTips: '(オプション)Prompt Engの変数に対応するキーと値のペアとしてユーザー入力フィールドを提供します。キーは変数名で、値はパラメータの値です。フィールドのタイプがSelectの場合、送信される値は事前に設定された選択肢のいずれかである必要があります。', + inputsTips: '(オプション)Prompt Eng の変数に対応するキーと値のペアとしてユーザー入力フィールドを提供します。キーは変数名で、値はパラメータの値です。フィールドのタイプが Select の場合、送信される値は事前に設定された選択肢のいずれかである必要があります。', queryTips: 'ユーザーの入力/質問内容', blocking: 'ブロッキングタイプで、実行が完了して結果が返されるまで待機します。(処理が長い場合、リクエストは中断される場合があります)', streaming: 'ストリーミングの返却。SSE(Server-Sent Events)に基づいたストリーミングの返却の実装。', - conversationIdTip: '(オプション)会話ID:初回の会話の場合は空白のままにしておき、継続する場合はコンテキストからconversation_idを渡します。', + conversationIdTip: '(オプション)会話 ID:初回の会話の場合は空白のままにしておき、継続する場合はコンテキストから conversation_id を渡します。', messageFeedbackApi: 'メッセージ端末ユーザーフィードバック、いいね', messageFeedbackApiTip: 'エンドユーザーの代わりに受信したメッセージを「いいね」または「いいね」で評価します。このデータはログ&注釈ページで表示され、将来のモデルの微調整に使用されます。', - messageIDTip: 'メッセージID', - ratingTip: 'いいねまたはいいね、nullは元に戻す', + messageIDTip: 'メッセージ ID', + ratingTip: 'いいねまたはいいね、null は元に戻す', chatMsgHistoryApi: 'チャット履歴メッセージの取得', chatMsgHistoryApiTip: '最初のページは最新の「limit」バーを返します。逆順です。', - chatMsgHistoryConversationIdTip: '会話ID', - chatMsgHistoryFirstId: '現在のページの最初のチャットレコードのID。デフォルトはなし。', - chatMsgHistoryLimit: '1回のリクエストで返されるチャットの数', + chatMsgHistoryConversationIdTip: '会話 ID', + chatMsgHistoryFirstId: '現在のページの最初のチャットレコードの ID。デフォルトはなし。', + chatMsgHistoryLimit: '1 回のリクエストで返されるチャットの数', conversationsListApi: '会話リストの取得', - conversationsListApiTip: '現在のユーザーのセッションリストを取得します。デフォルトでは、最後の20のセッションが返されます。', - conversationsListFirstIdTip: '現在のページの最後のレコードのID、デフォルトはなし。', - conversationsListLimitTip: '1回のリクエストで返されるチャットの数', + conversationsListApiTip: '現在のユーザーのセッションリストを取得します。デフォルトでは、最後の 20 のセッションが返されます。', + conversationsListFirstIdTip: '現在のページの最後のレコードの ID、デフォルトはなし。', + conversationsListLimitTip: '1 回のリクエストで返されるチャットの数', conversationRenamingApi: '会話の名前変更', conversationRenamingApiTip: '会話の名前を変更します。名前はマルチセッションクライアントインターフェースに表示されます。', conversationRenamingNameTip: '新しい名前', diff --git a/web/i18n/ja-JP/app-debug.ts b/web/i18n/ja-JP/app-debug.ts index 23fc7adba7..f862f3f2f7 100644 --- a/web/i18n/ja-JP/app-debug.ts +++ b/web/i18n/ja-JP/app-debug.ts @@ -5,12 +5,12 @@ const translation = { }, orchestrate: 'オーケストレーション', promptMode: { - simple: 'エキスパートモードに切り替えて、PROMPT全体を編集します', + simple: 'エキスパートモードに切り替えて、PROMPT 全体を編集します', advanced: 'エキスパートモード', switchBack: '基本モードに戻る', advancedWarning: { - title: 'エキスパートモードに切り替えました。PROMPTを変更すると、基本モードに戻ることはできません。', - description: 'エキスパートモードでは、PROMPT全体を編集できます。', + title: 'エキスパートモードに切り替えました。PROMPT を変更すると、基本モードに戻ることはできません。', + description: 'エキスパートモードでは、PROMPT 全体を編集できます。', learnMore: '詳細はこちら', ok: 'OK', }, @@ -33,14 +33,14 @@ const translation = { userAction: 'ユーザー', }, notSetAPIKey: { - title: 'LLMプロバイダーキーが設定されていません', + title: 'LLM プロバイダーキーが設定されていません', trailFinished: 'トライアル終了', - description: 'LLMプロバイダーキーが設定されていません。デバッグする前に設定する必要があります。', + description: 'LLM プロバイダーキーが設定されていません。デバッグする前に設定する必要があります。', settingBtn: '設定に移動', }, trailUseGPT4Info: { - title: '現在、gpt-4はサポートされていません', - description: 'gpt-4を使用するには、APIキーを設定してください。', + title: '現在、gpt-4 はサポートされていません', + description: 'gpt-4 を使用するには、API キーを設定してください。', }, feature: { groupChat: { @@ -52,12 +52,12 @@ const translation = { }, conversationOpener: { title: '会話の開始', - description: 'チャットアプリでは、AIがユーザーに最初にアクティブに話しかける最初の文は、通常、歓迎メッセージとして使用されます。', + description: 'チャットアプリでは、AI がユーザーに最初にアクティブに話しかける最初の文は、通常、歓迎メッセージとして使用されます。', }, suggestedQuestionsAfterAnswer: { title: 'フォローアップ', description: '次の質問の提案を設定すると、ユーザーにより良いチャットが提供されます。', - resDes: 'ユーザーの次の質問に関する3つの提案。', + resDes: 'ユーザーの次の質問に関する 3 つの提案。', tryToAsk: '質問してみてください', }, moreLikeThis: { @@ -128,7 +128,7 @@ const translation = { }, tools: { title: 'ツール', - tips: 'ツールは、ユーザー入力または変数をリクエストパラメーターとして使用して外部データをコンテキストとしてクエリするための標準的なAPI呼び出し方法を提供します。', + tips: 'ツールは、ユーザー入力または変数をリクエストパラメーターとして使用して外部データをコンテキストとしてクエリするための標準的な API 呼び出し方法を提供します。', toolsInUse: '{{count}} 個のツールが使用中', modal: { title: 'ツール', @@ -162,7 +162,7 @@ const translation = { }, moderation: { title: 'コンテンツのモデレーション', - description: 'モデレーションAPIを使用するか、機密語リストを維持することで、モデルの出力を安全にします。', + description: 'モデレーション API を使用するか、機密語リストを維持することで、モデルの出力を安全にします。', contentEnableLabel: 'モデレート・コンテンツを有効にする', allEnabled: '入力/出力コンテンツが有効になっています', inputEnabled: '入力コンテンツが有効になっています', @@ -171,16 +171,16 @@ const translation = { title: 'コンテンツのモデレーション設定', provider: { title: 'プロバイダ', - openai: 'OpenAIモデレーション', + openai: 'OpenAI モデレーション', openaiTip: { - prefix: 'OpenAIモデレーションには、', - suffix: 'にOpenAI APIキーが設定されている必要があります。', + prefix: 'OpenAI モデレーションには、', + suffix: 'に OpenAI API キーが設定されている必要があります。', }, keywords: 'キーワード', }, keywords: { - tip: '1行ごとに1つ、行区切りで入力してください。1行あたり最大100文字。', - placeholder: '1行ごとに、行区切りで入力してください', + tip: '1 行ごとに 1 つ、行区切りで入力してください。1 行あたり最大 100 文字。', + placeholder: '1 行ごとに、行区切りで入力してください', line: '行', }, content: { @@ -188,14 +188,14 @@ const translation = { output: '出力コンテンツをモデレート', preset: 'プリセット返信', placeholder: 'ここにプリセット返信の内容を入力', - condition: '少なくとも1つの入力および出力コンテンツをモデレートする', - fromApi: 'プリセット返信はAPIによって返されます', + condition: '少なくとも 1 つの入力および出力コンテンツをモデレートする', + fromApi: 'プリセット返信は API によって返されます', errorMessage: 'プリセット返信は空にできません', - supportMarkdown: 'Markdownがサポートされています', + supportMarkdown: 'Markdown がサポートされています', }, openaiNotConfig: { - before: 'OpenAIモデレーションには、', - after: 'にOpenAI APIキーが設定されている必要があります。', + before: 'OpenAI モデレーションには、', + after: 'に OpenAI API キーが設定されている必要があります。', }, }, }, @@ -214,17 +214,21 @@ const translation = { modalTitle: '画像アップロード設置', }, bar: { - empty: 'Webアプリのユーザーエクスペリアンスを強化させる機能を有効にする', + empty: 'Web アプリのユーザーエクスペリアンスを強化させる機能を有効にする', enableText: '有効な機能', manage: '管理', }, + documentUpload: { + title: 'ドキュメント', + description: 'ドキュメント機能を有効にすると、AI モデルがファイルを処理し、その内容に基づいて質問に回答できるようになります。', + }, }, codegen: { title: 'コードジェネレーター', description: 'コードジェネレーターは、設定されたモデルを使用して指示に基づいて高品質なコードを生成します。明確で詳細な指示を提供してください。', instruction: '指示', instructionPlaceholder: '生成したいコードの詳細な説明を入力してください。', - noDataLine1: '左側に使用例を記入してください,', + noDataLine1: '左側に使用例を記入してください,', noDataLine2: 'コードのプレビューがこちらに表示されます。', generate: '生成', generatedCodeTitle: '生成されたコード', @@ -243,9 +247,10 @@ const translation = { instructionPlaceHolder: '具体的で明確な指示を入力してください。', generate: '生成', resTitle: '生成されたプロンプト', - noDataLine1: '左側に使用例を記入してください,', + noDataLine1: '左側に使用例を記入してください,', noDataLine2: 'オーケストレーションのプレビューがこちらに表示されます。', apply: '適用', + noData: '左側にユースケースを入力すると、こちらでプレビューができます。', loading: 'アプリケーションを処理中です', overwriteTitle: '既存の設定を上書きしますか?', overwriteMessage: 'このプロンプトを適用すると、既存の設定が上書きされます。', @@ -271,12 +276,12 @@ const translation = { instruction: 'ユーザーが簡単に旅行計画を立てられるように設計されたツール', }, SQLSorcerer: { - name: 'SQLソーサラー', - instruction: '日常言語をSQLクエリに変換する', + name: 'SQL ソーサラー', + instruction: '日常言語を SQL クエリに変換する', }, GitGud: { name: 'Git gud', - instruction: 'ユーザーが記述したバージョン管理アクションに対応するGitコマンドを生成する', + instruction: 'ユーザーが記述したバージョン管理アクションに対応する Git コマンドを生成する', }, meetingTakeaways: { name: '会議の要点', @@ -293,7 +298,7 @@ const translation = { message: '変更が破棄され、最後に公開された構成が復元されます。', }, errorMessage: { - nameOfKeyRequired: 'キーの名前: {{key}} が必要です', + nameOfKeyRequired: 'キーの名前:{{key}} が必要です', valueOfVarRequired: '{{key}} の値は空にできません', queryRequired: 'リクエストテキストが必要です。', waitForResponse: '前のメッセージへの応答が完了するまでお待ちください。', @@ -302,12 +307,9 @@ const translation = { waitForImgUpload: '画像のアップロードが完了するまでお待ちください', waitForFileUpload: 'ファイルのアップロードが完了するまでお待ちください', }, - warningMessage: { - timeoutExceeded: 'タイムアウトのため結果が表示されません。完全な結果を手にいれるためには、ログを参照してください。', - }, - chatSubTitle: '手順', + chatSubTitle: 'プロンプト', completionSubTitle: '接頭辞プロンプト', - promptTip: 'プロンプトは、AIの応答を指示と制約で誘導します。 {{input}} のような変数を挿入します。このプロンプトはユーザーには表示されません。', + promptTip: 'プロンプトは、AI の応答を指示と制約で誘導します。 {{input}} のような変数を挿入します。このプロンプトはユーザーには表示されません。', formattingChangedTitle: '書式が変更されました', formattingChangedText: '書式を変更すると、デバッグ領域がリセットされます。よろしいですか?', variableTitle: '変数', @@ -325,7 +327,7 @@ const translation = { }, varKeyError: { canNoBeEmpty: '{{key}} は必須です', - tooLong: '{{key}} が長すぎます。30文字を超えることはできません', + tooLong: '{{key}} が長すぎます。30 文字を超えることはできません', notValid: '{{key}} が無効です。文字、数字、アンダースコアのみを含めることができます', notStartWithNumber: '{{key}} は数字で始めることはできません', keyAlreadyExists: '{{key}} はすでに存在します', @@ -352,12 +354,12 @@ const translation = { 'maxLength': '最大長', 'options': 'オプション', 'addOption': 'オプションを追加', - 'apiBasedVar': 'APIベースの変数', + 'apiBasedVar': 'API ベースの変数', 'varName': '変数名', 'labelName': 'ラベル名', 'inputPlaceholder': '入力してください', - 'content': 'コンテンツ', 'required': '必須', + 'hide': '非表示', 'file': { supportFileTypes: 'サポートされたファイルタイプ', image: { @@ -375,7 +377,7 @@ const translation = { custom: { name: '他のファイルタイプ', description: '他のファイルタイプを指定する。', - createPlaceholder: '+ 拡張子, 例:.doc', + createPlaceholder: '+ 拡張子,例:.doc', }, }, 'uploadFileTypes': 'アップロードされたファイルのタイプ', @@ -387,7 +389,7 @@ const translation = { varNameRequired: '変数名は必須です', labelNameRequired: 'ラベル名は必須です', varNameCanBeRepeat: '変数名は繰り返すことができません', - atLeastOneOption: '少なくとも1つのオプションが必要です', + atLeastOneOption: '少なくとも 1 つのオプションが必要です', optionRepeat: '繰り返しオプションがあります', }, }, @@ -452,10 +454,8 @@ const translation = { noPrompt: 'プレプロンプト入力にいくつかのプロンプトを記入してみてください', userInputField: 'ユーザー入力フィールド', noVar: '変数の値を入力してください。新しいセッションが開始されるたびにプロンプトの単語が自動的に置換されます。', - chatVarTip: - '変数の値を入力してください。新しいセッションが開始されるたびにプロンプトの単語が自動的に置換されます。', - completionVarTip: - '変数の値を入力してください。質問が送信されるたびにプロンプトの単語が自動的に置換されます。', + chatVarTip: '変数の値を入力してください。新しいセッションが開始されるたびにプロンプトの単語が自動的に置換されます。', + completionVarTip: '変数の値を入力してください。質問が送信されるたびにプロンプトの単語が自動的に置換されます。', previewTitle: 'プロンプトのプレビュー', queryTitle: 'クエリ内容', queryPlaceholder: 'リクエストテキストを入力してください。', @@ -474,9 +474,10 @@ const translation = { title: 'マルチパスリトリーバル', description: 'ユーザーの意図に基づいて、すべてのナレッジをクエリし、複数のソースから関連するテキストを取得し、再順位付け後、ユーザークエリに最適な結果を選択します。再順位付けモデル API の構成が必要です。', }, + embeddingModelRequired: 'Embedding モデルが設定されていない', rerankModelRequired: '再順位付けモデルが必要です', params: 'パラメータ', - top_k: 'トップK', + top_k: 'トップ K', top_kTip: 'ユーザーの質問に最も類似したチャンクをフィルタリングするために使用されます。システムは、選択したモデルの max_tokens に応じて、動的に Top K の値を調整します。', score_threshold: 'スコア閾値', score_thresholdTip: 'チャンクフィルタリングの類似性閾値を設定するために使用されます。', @@ -518,7 +519,7 @@ const translation = { promptPlaceholder: 'ここにプロンプトを入力してください', tools: { name: 'ツール', - description: 'ツールを使用すると、インターネットの検索や科学的計算など、LLMの機能を拡張できます', + description: 'ツールを使用すると、インターネットの検索や科学的計算など、LLM の機能を拡張できます', enabled: '有効', }, }, diff --git a/web/i18n/ja-JP/app-log.ts b/web/i18n/ja-JP/app-log.ts index d4553e830c..43a6fb9151 100644 --- a/web/i18n/ja-JP/app-log.ts +++ b/web/i18n/ja-JP/app-log.ts @@ -1,6 +1,6 @@ const translation = { title: 'ログ', - description: 'ログは、アプリケーションの実行状態を記録します。ユーザーの入力やAIの応答などが含まれます。', + description: 'ログは、アプリケーションの実行状態を記録します。ユーザーの入力や AI の応答などが含まれます。', dateTimeFormat: 'MM/DD/YYYY hh:mm A', table: { header: { @@ -29,13 +29,13 @@ const translation = { noOutput: '出力がありません', element: { title: '誰かいますか?', - content: 'ここでは、エンドユーザーとAIアプリケーション間の相互作用を観察し、注釈を付けることで、AIの精度を継続的に向上させます。Webアプリを<shareLink>共有</shareLink>または<testLink>テスト</testLink>してみて、このページに戻ってください。', + content: 'ここでは、エンドユーザーと AI アプリケーション間の相互作用を観察し、注釈を付けることで、AI の精度を継続的に向上させます。Web アプリを<shareLink>共有</shareLink>または<testLink>テスト</testLink>してみて、このページに戻ってください。', }, }, }, detail: { time: '時間', - conversationId: '会話ID', + conversationId: '会話 ID', promptTemplate: 'プロンプトテンプレート', promptTemplateBeforeChat: 'チャット前のプロンプトテンプレート・システムメッセージとして', annotationTip: '{{user}} によってマークされた改善', @@ -48,7 +48,7 @@ const translation = { dislike: 'いいね解除', addAnnotation: '改善を追加', editAnnotation: '改善を編集', - annotationPlaceholder: '将来のモデルの微調整やテキスト生成品質の継続的改善のためにAIが返信することを期待する答えを入力してください。', + annotationPlaceholder: '将来のモデルの微調整やテキスト生成品質の継続的改善のために AI が返信することを期待する答えを入力してください。', }, variables: '変数', uploadImages: 'アップロードされた画像', @@ -57,10 +57,10 @@ const translation = { filter: { period: { today: '今日', - last7days: '過去7日間', - last4weeks: '過去4週間', - last3months: '過去3ヶ月', - last12months: '過去12ヶ月', + last7days: '過去 7 日間', + last4weeks: '過去 4 週間', + last3months: '過去 3 ヶ月', + last12months: '過去 12 ヶ月', monthToDate: '月初から今日まで', quarterToDate: '四半期初から今日まで', yearToDate: '年初から今日まで', diff --git a/web/i18n/ja-JP/app-overview.ts b/web/i18n/ja-JP/app-overview.ts index d67860ff37..d948bc3b28 100644 --- a/web/i18n/ja-JP/app-overview.ts +++ b/web/i18n/ja-JP/app-overview.ts @@ -1,9 +1,9 @@ const translation = { welcome: { firstStepTip: 'はじめるには、', - enterKeyTip: '以下にOpenAI APIキーを入力してください', - getKeyTip: 'OpenAIダッシュボードからAPIキーを取得してください', - placeholder: 'あなた様のOpenAI APIキー(例:sk-xxxx)', + enterKeyTip: '以下に OpenAI API キーを入力してください', + getKeyTip: 'OpenAI ダッシュボードから API キーを取得してください', + placeholder: 'OpenAI API キー(例:sk-xxxx)', }, apiKeyInfo: { cloud: { @@ -12,7 +12,7 @@ const translation = { description: 'トライアルクォータはテスト用に提供されます。トライアルクォータのコールが使い切られる前に、独自のモデルプロバイダを設定するか、追加のクォータを購入してください。', }, exhausted: { - title: 'トライアルクォータが使い切れました。APIキーを設定してください。', + title: 'トライアルクォータが使い切れました。API キーを設定してください。', description: 'トライアルクォータが使い切れました。独自のモデルプロバイダを設定するか、追加のクォータを購入してください。', }, }, @@ -25,36 +25,36 @@ const translation = { callTimes: 'コール回数', usedToken: '使用済みトークン', setAPIBtn: 'モデルプロバイダの設定へ', - tryCloud: 'またはDifyのクラウドバージョンを無料見積もりでお試しください', + tryCloud: 'または Dify のクラウドバージョンを無料見積もりでお試しください', }, overview: { title: '概要', appInfo: { - explanation: '使いやすいAI Webアプリ', - accessibleAddress: '公開URL', + explanation: '使いやすい AI Web アプリ', + accessibleAddress: '公開 URL', preview: 'プレビュー', regenerate: '再生成', - regenerateNotice: '公開URLを再生成しますか?', - preUseReminder: '続行する前にWebアプリを有効にしてください。', + regenerateNotice: '公開 URL を再生成しますか?', + preUseReminder: '続行する前に Web アプリを有効にしてください。', settings: { entry: '設定', - title: 'Webアプリの設定', - webName: 'Webアプリの名前', - webDesc: 'Webアプリの説明', + title: 'Web アプリの設定', + webName: 'Web アプリの名前', + webDesc: 'Web アプリの説明', webDescTip: 'このテキストはクライアント側に表示され、アプリケーションの使用方法の基本的なガイダンスを提供します。', - webDescPlaceholder: 'Webアプリの説明を入力してください', + webDescPlaceholder: 'Web アプリの説明を入力してください', language: '言語', workflow: { title: 'ワークフローステップ', show: '表示', hide: '非表示', subTitle: 'ワークフローの詳細', - showDesc: 'Webアプリでワークフローの詳細を表示または非表示にする', + showDesc: 'Web アプリでワークフローの詳細を表示または非表示にする', }, chatColorTheme: 'チャットボットのカラーテーマ', chatColorThemeDesc: 'チャットボットのカラーテーマを設定します', chatColorThemeInverted: '反転', - invalidHexMessage: '無効な16進数値', + invalidHexMessage: '無効な 16 進数値', invalidPrivacyPolicy: '無効なプライバシーポリシーのリンクです。http または https で始まる有効なリンクを使用してください', more: { entry: 'その他の設定を表示', @@ -62,18 +62,18 @@ const translation = { copyRightPlaceholder: '著作者または組織名を入力してください', privacyPolicy: 'プライバシーポリシー', privacyPolicyPlaceholder: 'プライバシーポリシーリンクを入力してください', - privacyPolicyTip: '訪問者がアプリケーションが収集するデータを理解し、Difyの<privacyPolicyLink>プライバシーポリシー</privacyPolicyLink>を参照できるようにします。', + privacyPolicyTip: '訪問者がアプリケーションが収集するデータを理解し、Dify の<privacyPolicyLink>プライバシーポリシー</privacyPolicyLink>を参照できるようにします。', customDisclaimer: 'カスタム免責事項', customDisclaimerPlaceholder: '免責事項を入力してください', customDisclaimerTip: 'アプリケーションの使用に関する免責事項を提供します。', copyrightTooltip: 'プロフェッショナルプラン以上にアップグレードしてください', - copyrightTip: 'ウェブアプリに著作権情報を表示する', + copyrightTip: 'Web アプリに著作権情報を表示する', }, sso: { - title: 'WebアプリのSSO', - tooltip: '管理者に問い合わせて、WebアプリのSSOを有効にします', - label: 'SSO認証', - description: 'すべてのユーザーは、Webアプリを使用する前にSSOでログインする必要があります', + title: 'Web アプリの SSO', + tooltip: '管理者に問い合わせて、Web アプリの SSO を有効にします', + label: 'SSO 認証', + description: 'すべてのユーザーは、Web アプリを使用する前に SSO でログインする必要があります', }, modalTip: 'クライアント側の Web アプリ設定。', }, @@ -81,45 +81,45 @@ const translation = { entry: '埋め込み', title: 'ウェブサイトに埋め込む', explanation: 'チャットアプリをウェブサイトに埋め込む方法を選択します。', - iframe: 'ウェブサイトの任意の場所にチャットアプリを追加するには、このiframeをHTMLコードに追加してください。', - scripts: 'ウェブサイトの右下にチャットアプリを追加するには、このコードをHTMLに追加してください。', - chromePlugin: 'Dify Chatbot Chrome拡張機能をインストール', + iframe: 'ウェブサイトの任意の場所にチャットアプリを追加するには、この iframe を HTML コードに追加してください。', + scripts: 'ウェブサイトの右下にチャットアプリを追加するには、このコードを HTML に追加してください。', + chromePlugin: 'Dify Chatbot Chrome 拡張機能をインストール', copied: 'コピーしました', copy: 'コピー', }, qrcode: { - title: '共有用QRコード', + title: '共有用 QR コード', scan: 'アプリケーションの共有をスキャン', - download: 'QRコードをダウンロード', + download: 'QR コードをダウンロード', }, customize: { way: '方法', entry: 'カスタマイズ', - title: 'AI Webアプリのカスタマイズ', - explanation: 'シナリオとスタイルのニーズに合わせてWebアプリのフロントエンドをカスタマイズできます。', + title: 'AI Web アプリのカスタマイズ', + explanation: 'シナリオとスタイルのニーズに合わせて Web アプリのフロントエンドをカスタマイズできます。', way1: { - name: 'クライアントコードをフォークして修正し、Vercelにデプロイします(推奨)', + name: 'クライアントコードをフォークして修正し、Vercel にデプロイします(推奨)', step1: 'クライアントコードをフォークして修正します', - step1Tip: 'ここをクリックしてソースコードをGitHubアカウントにフォークし、コードを修正します', + step1Tip: 'ここをクリックしてソースコードを GitHub アカウントにフォークし、コードを修正します', step1Operation: 'Dify-WebClient', - step2: 'Vercelにデプロイします', - step2Tip: 'ここをクリックしてリポジトリをVercelにインポートし、デプロイします', + step2: 'Vercel にデプロイします', + step2Tip: 'ここをクリックしてリポジトリを Vercel にインポートし、デプロイします', step2Operation: 'リポジトリをインポート', step3: '環境変数を設定します', - step3Tip: 'Vercelに次の環境変数を追加します', + step3Tip: 'Vercel に次の環境変数を追加します', }, way2: { - name: 'クライアントサイドのコードを記述してAPIを呼び出し、サーバーにデプロイします', + name: 'クライアントサイドのコードを記述して API を呼び出し、サーバーにデプロイします', operation: 'ドキュメント', }, }, launch: '発射', }, apiInfo: { - title: 'バックエンドサービスAPI', - explanation: 'あなた様のアプリケーションに簡単に統合できます', - accessibleAddress: 'サービスAPIエンドポイント', - doc: 'APIリファレンス', + title: 'バックエンドサービス API', + explanation: 'あなたのアプリケーションに簡単に統合できます', + accessibleAddress: 'サービス API エンドポイント', + doc: 'API リファレンス', }, status: { running: '稼働中', @@ -132,15 +132,15 @@ const translation = { tokenPS: 'トークン/秒', totalMessages: { title: 'トータルメッセージ数', - explanation: '日次AIインタラクション数。', + explanation: '日次 AI インタラクション数。', }, totalConversations: { title: '総会話数', - explanation: '日次AI会話数;プロンプトエンジニアリング/デバッグは除外。', + explanation: '日次 AI 会話数;プロンプトエンジニアリング/デバッグは除外。', }, activeUsers: { title: 'アクティブユーザー数', - explanation: 'AIとのQ&Aに参加しているユニークユーザー数;工学的/デバッグ目的のプロンプトは除外されます。', + explanation: 'AI との Q&A に参加しているユニークユーザー数;工学的/デバッグ目的のプロンプトは除外されます。', }, tokenUsage: { title: 'トークン使用量', @@ -149,7 +149,7 @@ const translation = { }, avgSessionInteractions: { title: '平均セッションインタラクション数', - explanation: 'ユーザーとAIの連続的なコミュニケーション数;対話型アプリケーション向け。', + explanation: 'ユーザーと AI の連続的なコミュニケーション数;対話型アプリケーション向け。', }, avgUserInteractions: { title: '平均ユーザーインタラクション数', @@ -157,15 +157,15 @@ const translation = { }, userSatisfactionRate: { title: 'ユーザー満足度率', - explanation: '1,000件のメッセージあたりの「いいね」の数。これは、ユーザーが非常に満足している回答の割合を示します。', + explanation: '1,000 件のメッセージあたりの「いいね」の数。これは、ユーザーが非常に満足している回答の割合を示します。', }, avgResponseTime: { title: '平均応答時間', - explanation: 'AIが処理/応答する時間(ミリ秒);テキストベースのアプリケーション向け。', + explanation: 'AI が処理/応答する時間(ミリ秒);テキストベースのアプリケーション向け。', }, tps: { title: 'トークン出力速度', - explanation: 'LLMのパフォーマンスを測定します。リクエストの開始から出力の完了までのLLMのトークン出力速度を数えます。', + explanation: 'LLM のパフォーマンスを測定します。リクエストの開始から出力の完了までの LLM のトークン出力速度を数えます。', }, }, } diff --git a/web/i18n/ja-JP/app.ts b/web/i18n/ja-JP/app.ts index 56560da045..b501bc129e 100644 --- a/web/i18n/ja-JP/app.ts +++ b/web/i18n/ja-JP/app.ts @@ -10,23 +10,26 @@ const translation = { advanced: 'チャットフロー', }, duplicate: '複製', + mermaid: { + handDrawn: '手描き', + classic: 'クラシック', + }, duplicateTitle: 'アプリを複製する', export: 'DSL をエクスポート', exportFailed: 'DSL のエクスポートに失敗しました。', importDSL: 'DSL ファイルをインポート', createFromConfigFile: 'DSL ファイルから作成する', - importFromDSL: 'DSLからインポート', - importFromDSLFile: 'DSLファイルから', - importFromDSLUrl: 'URLから', - importFromDSLUrlPlaceholder: 'DSLリンクをここに貼り付けます', + importFromDSL: 'DSL からインポート', + importFromDSLFile: 'DSL ファイルから', + importFromDSLUrl: 'URL から', + importFromDSLUrlPlaceholder: 'DSL リンクをここに貼り付けます', deleteAppConfirmTitle: 'このアプリを削除しますか?', deleteAppConfirmContent: - 'アプリを削除すると、元に戻すことはできません。ユーザーはもはやあなた様のアプリにアクセスできず、すべてのプロンプトの設定とログが永久に削除されます。', + 'アプリを削除すると、元に戻すことはできません。他のユーザーはもはやこのアプリにアクセスできず、すべてのプロンプトの設定とログが永久に削除されます。', appDeleted: 'アプリが削除されました', appDeleteFailed: 'アプリの削除に失敗しました', join: 'コミュニティに参加する', - communityIntro: - 'さまざまなチャンネルでチームメンバーや貢献者、開発者と議論します。', + communityIntro: 'さまざまなチャンネルでチームメンバーや貢献者、開発者と議論します。', roadmap: 'ロードマップを見る', newApp: { startFromBlank: '最初から作成', @@ -47,7 +50,7 @@ const translation = { advancedFor: '上級ユーザー向け', advancedDescription: 'ワークフロー オーケストレートは、ワークフロー形式でチャットボットをオーケストレートし、組み込みのプロンプトを編集する機能を含む高度なカスタマイズを提供します。経験豊富なユーザー向けです。', captionName: 'アプリのアイコンと名前', - appNamePlaceholder: 'アプリに名前を付ける', + appNamePlaceholder: 'アプリ名を入力してください', captionDescription: '説明', appDescriptionPlaceholder: 'アプリの説明を入力してください', useTemplate: 'このテンプレートを使用する', @@ -70,33 +73,33 @@ const translation = { appCreateFailed: 'アプリの作成に失敗しました', Confirm: '確認する', caution: '注意', - appCreateDSLErrorPart2: '続行しますか?', - appCreateDSLErrorPart4: 'システムがサポートするDSLバージョン:', - appCreateDSLErrorPart3: '現在のアプリケーションの DSL バージョン:', + appCreateDSLErrorPart2: '続行しますか?', + appCreateDSLErrorPart4: 'システムがサポートする DSL バージョン:', + appCreateDSLErrorPart3: '現在のアプリケーションの DSL バージョン:', appCreateDSLErrorTitle: 'バージョンの非互換性', - appCreateDSLWarning: '注意:DSLのバージョンの違いは、特定の機能に影響を与える可能性があります', + appCreateDSLWarning: '注意:DSL のバージョンの違いは、特定の機能に影響を与える可能性があります', appCreateDSLErrorPart1: 'DSL バージョンに大きな違いが検出されました。インポートを強制すると、アプリケーションが誤動作する可能性があります。', optional: '随意', - forBeginners: '初心者向け', + forBeginners: '初心者向けの基本的なアプリタイプ', noTemplateFoundTip: '別のキーワードを使用して検索してみてください。', agentShortDescription: '推論と自律的なツールの使用を備えたインテリジェントエージェント', foundResults: '{{カウント}}業績', noTemplateFound: 'テンプレートが見つかりません', noAppsFound: 'アプリが見つかりませんでした', - workflowShortDescription: 'シングルターンの自動化タスクのオーケストレーション', - completionShortDescription: 'テキスト生成タスクのためのAIアシスタント', - advancedUserDescription: 'メモリ機能を備えたマルチラウンドの複雑な対話タスクのワークフローオーケストレーション。', + workflowShortDescription: 'インテリジェントな自動化のためのエージェントフロー', + completionShortDescription: '複数ターンチャット向けに強化されたワークフロー', + advancedUserDescription: '追加のメモリ機能とチャットボットインターフェースを備えたワークフロー', advancedShortDescription: 'メモリを使用した複雑なマルチターン対話のワークフロー', agentUserDescription: 'タスクの目標を達成するために反復的な推論と自律的なツールを使用できるインテリジェントエージェント。', foundResult: '{{カウント}}結果', forAdvanced: '上級ユーザー向け', - chooseAppType: 'アプリの種類を選択', + chooseAppType: 'アプリタイプを選択', learnMore: '詳細情報', - noIdeaTip: 'アイデアがありませんか?テンプレートをご覧ください', - chatbotShortDescription: '簡単なセットアップのLLMベースのチャットボット', - chatbotUserDescription: '簡単な設定でLLMベースのチャットボットを迅速に構築します。Chatflowは後で切り替えることができます。', - workflowUserDescription: '自動化やバッチ処理などの単一ラウンドのタスクのためのワークフローオーケストレーション。', - completionUserDescription: '簡単な構成でテキスト生成タスク用のAIアシスタントをすばやく構築します。', + noIdeaTip: 'アイデアがありませんか?テンプレートをご覧ください', + chatbotShortDescription: '簡単なセットアップの LLM ベースのチャットボット', + chatbotUserDescription: '簡単な設定で LLM ベースのチャットボットを迅速に構築します。Chatflow は後で切り替えることができます。', + workflowUserDescription: 'ドラッグ&ドロップの簡易性で自律型 AI ワークフローを視覚的に構築', + completionUserDescription: '簡単な構成でテキスト生成タスク用の AI アシスタントをすばやく構築します。', }, editApp: '情報を編集する', editAppTitle: 'アプリ情報を編集する', @@ -126,15 +129,16 @@ const translation = { }, tracing: { title: 'アプリのパフォーマンスの追跡', - description: 'サードパーティのLLMOpsサービスとトレースアプリケーションのパフォーマンス設定を行います。', + description: 'サードパーティの LLMOps サービスとトレースアプリケーションのパフォーマンス設定を行います。', config: '設定', + view: '見る', collapse: '折りたたむ', expand: '展開', tracing: '追跡', disabled: '無効しました', disabledTip: 'まずはサービスの設定から始めましょう。', enabled: '有効しました', - tracingDescription: 'LLMの呼び出し、コンテキスト、プロンプト、HTTPリクエストなど、アプリケーション実行の全ての文脈をサードパーティのトレースプラットフォームで取り込みます。', + tracingDescription: 'LLM の呼び出し、コンテキスト、プロンプト、HTTP リクエストなど、アプリケーション実行の全ての文脈をサードパーティのトレースプラットフォームで取り込みます。', configProviderTitle: { configured: '設定しました', notConfigured: 'トレース機能を有効化するためには、サービスの設定が必要です。', @@ -142,37 +146,36 @@ const translation = { }, langsmith: { title: 'LangSmith', - description: 'LLMを利用したアプリケーションのライフサイクル全段階を支援する、オールインワンの開発者向けプラットフォームです。', + description: 'LLM を利用したアプリケーションのライフサイクル全段階を支援する、オールインワンの開発者向けプラットフォームです。', }, langfuse: { title: 'Langfuse', - description: 'トレース、評価、プロンプトの管理、そしてメトリクスを駆使して、LLMアプリケーションのデバッグや改善に役立てます。', + description: 'トレース、評価、プロンプトの管理、そしてメトリクスを駆使して、LLM アプリケーションのデバッグや改善に役立てます。', + }, + opik: { + title: 'オピック', + description: 'Opik は、LLM アプリケーションを評価、テスト、監視するためのオープンソース プラットフォームです。', }, inUse: '使用中', configProvider: { title: '配置 ', - placeholder: 'あなた様の{{key}}を入力してください', + placeholder: '{{key}}を入力してください', project: 'プロジェクト', publicKey: '公開キー', secretKey: '秘密キー', - viewDocsLink: '{{key}}のドキュメントを見る', + viewDocsLink: '{{key}}に関するドキュメントを見る', removeConfirmTitle: '{{key}}の設定を削除しますか?', removeConfirmContent: '現在の設定は使用中です。これを削除すると、トレース機能が無効になります。', }, - view: '見る', - opik: { - title: 'オピック', - description: 'Opik は、LLM アプリケーションを評価、テスト、監視するためのオープンソース プラットフォームです。', + weave: { + title: '織る', + description: 'Weave は、LLM アプリケーションを評価、テスト、および監視するためのオープンソースプラットフォームです。', }, }, answerIcon: { - title: 'Webアプリアイコンを使用して🤖を置き換える', - description: '共有アプリケーションの中で Webアプリアイコンを使用して🤖を置き換えるかどうか', - descriptionInExplore: 'ExploreでWebアプリアイコンを使用して🤖を置き換えるかどうか', - }, - mermaid: { - handDrawn: '手描き', - classic: 'クラシック', + title: 'Web アプリアイコンを使用して🤖を置き換える', + description: '共有アプリケーションの中で Web アプリアイコンを使用して🤖を置き換えるかどうか', + descriptionInExplore: 'Explore で Web アプリアイコンを使用して🤖を置き換えるかどうか', }, newAppFromTemplate: { sidebar: { @@ -194,6 +197,54 @@ const translation = { noParams: 'パラメータは必要ありません', placeholder: 'アプリを選択...', }, + structOutput: { + moreFillTip: '最大 10 レベルのネストを表示します', + required: '必須', + LLMResponse: 'LLM のレスポンス', + configure: '設定', + notConfiguredTip: '構造化出力が未設定です', + structured: '構造化出力', + structuredTip: '構造化出力は、モデルが常に指定された JSON スキーマに準拠した応答を生成することを保証する機能です。', + modelNotSupported: 'モデルが対応していません', + modelNotSupportedTip: '現在のモデルはこの機能に対応しておらず、自動的にプロンプトインジェクションに切り替わります。', + }, + accessControl: 'Web アプリアクセス制御', + accessItemsDescription: { + anyone: '誰でもこの web アプリにアクセスできます(ログイン不要)', + specific: '特定のプラットフォーム内メンバーのみがこの Web アプリにアクセスできます', + organization: 'プラットフォーム内の全メンバーがこの Web アプリにアクセスできます', + external: '認証済みの外部ユーザーのみがこの Web アプリにアクセスできます', + }, + accessControlDialog: { + title: 'アクセス権限', + description: 'Web アプリのアクセス権限を設定します', + accessLabel: '誰がアクセスできますか', + accessItems: { + anyone: 'リンクを知っているすべてのユーザー', + specific: '特定のプラットフォーム内メンバー', + organization: 'プラットフォーム内の全メンバー', + external: '認証済みの外部ユーザー', + }, + groups_one: '{{count}} グループ', + groups_other: '{{count}} グループ', + members_one: '{{count}} メンバー', + members_other: '{{count}} メンバー', + noGroupsOrMembers: 'グループまたはメンバーが選択されていません', + webAppSSONotEnabledTip: 'Web アプリの外部認証方式を設定するには、組織の管理者にお問い合わせください。', + operateGroupAndMember: { + searchPlaceholder: 'グループやメンバーを検索', + allMembers: 'すべてのメンバー', + expand: '展開', + noResult: '結果がありません', + }, + updateSuccess: '更新が成功しました', + }, + publishApp: { + title: 'Web アプリへのアクセス権', + notSet: '未設定', + notSetDesc: '現在この Web アプリには誰もアクセスできません。権限を設定してください。', + }, + noAccessPermission: 'Web アプリにアクセス権限がありません', } export default translation diff --git a/web/i18n/ja-JP/billing.ts b/web/i18n/ja-JP/billing.ts index 891779d0b6..ade390917d 100644 --- a/web/i18n/ja-JP/billing.ts +++ b/web/i18n/ja-JP/billing.ts @@ -16,11 +16,11 @@ const translation = { viewBilling: '請求とサブスクリプションの管理', buyPermissionDeniedTip: 'サブスクリプションするには、エンタープライズ管理者に連絡してください', plansCommon: { - title: 'あなたのAIの旅を支える価格設定', + title: 'あなたの AI の旅を支える価格設定', freeTrialTipPrefix: 'サインアップ後、', - freeTrialTip: '200回のOpenAIコールの無料に受け取る', + freeTrialTip: '200 回の OpenAI コールの無料に受け取る', freeTrialTipSuffix: '。クレジットカード不要', - yearlyTip: '10ヶ月分支払って、1年間楽しもう!', + yearlyTip: '10 ヶ月分支払って、1 年間楽しもう!', mostPopular: '人気', cloud: 'クラウドサービス', self: 'セルフホストサービス', @@ -53,7 +53,11 @@ const translation = { vectorSpace: '{{size}}の知識データストレージ', vectorSpaceTooltip: '高品質インデックスモードのドキュメントは、知識データストレージのリソースを消費します。知識データストレージの上限に達すると、新しいドキュメントはアップロードされません。', documentsRequestQuota: '{{count,number}}/分のナレッジ リクエストのレート制限', - documentsRequestQuotaTooltip: 'ナレッジベース内でワークスペースが1分間に実行できる操作の総数を示します。これには、データセットの作成、削除、更新、ドキュメントのアップロード、修正、アーカイブ、およびナレッジベースクエリが含まれます。この指標は、ナレッジベースリクエストのパフォーマンスを評価するために使用されます。例えば、Sandbox ユーザーが1分間に10回連続でヒットテストを実行した場合、そのワークスペースは次の1分間、データセットの作成、削除、更新、ドキュメントのアップロードや修正などの操作を一時的に実行できなくなります。', + documentsRequestQuotaTooltip: 'ナレッジベース内でワークスペースが 1 分間に実行できる操作の総数を示します。これには、データセットの作成、削除、更新、ドキュメントのアップロード、修正、アーカイブ、およびナレッジベースクエリが含まれます。この指標は、ナレッジベースリクエストのパフォーマンスを評価するために使用されます。例えば、Sandbox ユーザーが 1 分間に 10 回連続でヒットテストを実行した場合、そのワークスペースは次の 1 分間、データセットの作成、削除、更新、ドキュメントのアップロードや修正などの操作を一時的に実行できなくなります。', + apiRateLimit: 'API レート制限', + apiRateLimitUnit: '{{count,number}}/日', + unlimitedApiRate: '無制限の API コール', + apiRateLimitTooltip: 'API レート制限は、テキスト生成、チャットボット、ワークフロー、ドキュメント処理など、Dify API 経由のすべてのリクエストに適用されます。', documentProcessingPriority: '文書処理', documentProcessingPriorityUpgrade: 'より高い精度と高速な速度でデータを処理します。', priority: { @@ -72,45 +76,45 @@ const translation = { emailSupport: 'メールサポート', priorityEmail: '優先メール&チャットサポート', logoChange: 'ロゴ変更', - SSOAuthentication: 'SSO認証', + SSOAuthentication: 'SSO 認証', personalizedSupport: '個別サポート', - dedicatedAPISupport: '専用APIサポート', + dedicatedAPISupport: '専用 API サポート', customIntegration: 'カスタム統合とサポート', - ragAPIRequest: 'RAG APIリクエスト', + ragAPIRequest: 'RAG API リクエスト', bulkUpload: 'ドキュメントの一括アップロード', agentMode: 'エージェントモード', workflow: 'ワークフロー', - llmLoadingBalancing: 'LLMロードバランシング', - llmLoadingBalancingTooltip: 'APIレート制限を効果的に回避するために、モデルに複数のAPIキーを追加する。', + llmLoadingBalancing: 'LLM ロードバランシング', + llmLoadingBalancingTooltip: 'API レート制限を効果的に回避するために、モデルに複数の API キーを追加する。', }, comingSoon: '近日公開', member: 'メンバー', memberAfter: 'メンバー', messageRequest: { - title: '{{count,number}}メッセージ', - titlePerMonth: '{{count,number}}メッセージ/月', - tooltip: 'Open Alモデルを使用するさまざまなプランのメッセージ呼び出しクォータ。上限を超えるメッセージは、Open AI APIキーを使用します。', + title: '{{count,number}}メッセージクレジット', + titlePerMonth: '{{count,number}}メッセージクレジット/月', + tooltip: 'メッセージクレジットは、Dify でさまざまな OpenAI モデルを簡単にお試しいただくためのものです。モデルタイプに応じてクレジットが消費され、使い切った後はご自身の OpenAI API キーに切り替えていただけます。', }, annotatedResponse: { title: '{{count,number}}の注釈クォータ制限', tooltip: '手動での回答の編集と注釈により、カスタマイズ可能な高品質の質問応答機能をアプリに提供します。(チャットアプリのみに適用)', }, - ragAPIRequestTooltip: 'Difyのナレッジベース処理機能のみを呼び出すAPI呼び出しの数を指します。', + ragAPIRequestTooltip: 'Dify のナレッジベース処理機能のみを呼び出す API 呼び出しの数を指します。', receiptInfo: 'チームオーナーとチーム管理者のみが購読および請求情報を表示できます', }, plans: { sandbox: { - name: 'Sandbox(サンドボックス)', + name: 'Sandbox', for: '主要機能の無料体験', description: '主要機能を無料で体験', }, professional: { - name: 'Professional(プロフェッショナル)', + name: 'Professional', for: '個人開発者/小規模チーム向け', description: '個人開発者・小規模チームに最適', }, team: { - name: 'Team(チーム)', + name: 'Team', for: '中規模チーム向け', description: '成長期のチームに必要な機能を備えたプラン', }, @@ -120,11 +124,11 @@ const translation = { description: 'オープンソース版の無料プラン', price: '無料', btnText: 'コミュニティ版を始めましょう', - includesTitle: '無料機能:', + includesTitle: '無料機能:', features: [ 'パブリックリポジトリの全コア機能', 'シングルワークスペース', - 'Difyオープンソースライセンス準拠', + 'Dify オープンソースライセンス準拠', ], }, premium: { @@ -134,12 +138,12 @@ const translation = { price: '従量制', priceTip: 'クラウドマーケットプレイス基準', btnText: 'プレミアム版を取得', - includesTitle: 'コミュニティ版機能に加えて:', + includesTitle: 'コミュニティ版機能に加えて:', comingSoon: 'Microsoft Azure & Google Cloud 近日対応', features: [ 'クラウドプロバイダーによる自己管理', 'シングルワークスペース', - 'Webアプリのロゴ&ブランドカスタマイズ', + 'Web アプリのロゴ&ブランドカスタマイズ', '優先メール/チャットサポート', ], }, @@ -150,14 +154,14 @@ const translation = { price: 'カスタム', priceTip: '年間契約専用', btnText: '営業に相談', - includesTitle: 'プレミアム版機能に加えて:', + includesTitle: 'プレミアム版機能に加えて:', features: [ 'エンタープライズ向け拡張ソリューション', '商用ライセンス認可', '企業専用機能', 'マルチワークスペース管理', 'シングルサインオン(SSO)', - 'DifyパートナーによるSLA保証', + 'Dify パートナーによる SLA 保証', '高度なセキュリティ管理', '公式メンテナンス&アップデート', 'プロフェッショナル技術支援', @@ -169,14 +173,18 @@ const translation = { fullSolution: 'より多くのスペースを得るためにプランをアップグレードしてください。', }, apps: { - fullTipLine1: 'より多くのアプリを作成するには、', - fullTipLine2: 'プランをアップグレードしてください。', + fullTip1: 'アップグレードして制限を解除する', + fullTip1des: 'このプランのアプリ数の上限に達しました。', + fullTip2: 'プラン制限に達しました。', + fullTip2des: '非アクティブなアプリを削除するか、アップグレードプランをご検討ください。', + contactUs: 'こちらからお問い合わせください', }, annotatedResponse: { fullTipLine1: 'より多くの会話を注釈するには、', fullTipLine2: 'プランをアップグレードしてください。', quotaTitle: '注釈返信クォータ', }, + teamMembers: 'チームメンバー', } export default translation diff --git a/web/i18n/ja-JP/common.ts b/web/i18n/ja-JP/common.ts index 8a47801f58..85f5863761 100644 --- a/web/i18n/ja-JP/common.ts +++ b/web/i18n/ja-JP/common.ts @@ -1,4 +1,10 @@ const translation = { + theme: { + theme: 'テーマ', + light: '明るい', + dark: '暗い', + auto: 'システム', + }, api: { success: '成功', actionSuccess: 'アクションが成功しました', @@ -56,6 +62,8 @@ const translation = { viewDetails: '詳細を見る', copied: 'コピーしました', in: '中', + format: 'フォーマット', + more: 'もっと', }, errorMsg: { fieldRequired: '{{field}}は必要です', @@ -107,9 +115,9 @@ const translation = { temperature: '温度', temperatureTip: 'ランダム性を制御します:温度を下げると、よりランダムな完成品が得られます。温度がゼロに近づくにつれて、モデルは決定的で反復的になります。', - top_p: '上位P', + top_p: '上位 P', top_pTip: - 'ニュークリアスサンプリングによる多様性の制御:0.5は、すべての尤度加重オプションの半分が考慮されることを意味します。', + 'ニュークリアスサンプリングによる多様性の制御:0.5 は、すべての尤度加重オプションの半分が考慮されることを意味します。', presence_penalty: '存在ペナルティ', presence_penaltyTip: 'これまでのテキストにトークンが表示されるかどうかに基づいて、新しいトークンにいくらペナルティを科すかを制御します。\nモデルが新しいトピックについて話す可能性が高まります。', @@ -118,11 +126,11 @@ const translation = { 'これまでのテキスト内のトークンの既存の頻度に基づいて、新しいトークンにどれだけペナルティを科すかを制御します。\nモデルが同じ行を文字通りに繰り返す可能性が低くなります。', max_tokens: '最大トークン', max_tokensTip: - '返信の最大長をトークン単位で制限するために使用されます。\n大きな値はプロンプトの単語、チャットログ、およびナレッジのために残されたスペースを制限する可能性があります。\nそれを2/3以下に設定することをお勧めします。\ngpt-4-1106-preview、gpt-4-vision-previewの最大トークン(入力128k出力4k)以下に設定することをお勧めします。', - maxTokenSettingTip: '最大トークン設定が高いため、プロンプト、クエリ、およびデータのスペースが制限される可能性があります。現在のモデルの最大トークンの80%以下に設定してください。', - setToCurrentModelMaxTokenTip: '最大トークンが現在のモデルの最大トークンの80%に更新されました {{maxToken}}.', + '返信の最大長をトークン単位で制限するために使用されます。\n大きな値はプロンプトの単語、チャットログ、およびナレッジのために残されたスペースを制限する可能性があります。\nそれを 2/3 以下に設定することをお勧めします。\ngpt-4-1106-preview、gpt-4-vision-preview の最大トークン(入力 128k 出力 4k)以下に設定することをお勧めします。', + maxTokenSettingTip: '最大トークン設定が高いため、プロンプト、クエリ、およびデータのスペースが制限される可能性があります。現在のモデルの最大トークンの 80% 以下に設定してください。', + setToCurrentModelMaxTokenTip: '最大トークンが現在のモデルの最大トークンの 80% に更新されました {{maxToken}}.', stop_sequences: '停止シーケンス', - stop_sequencesTip: 'APIが進行中のトークンの生成を停止する最大4つのシーケンス。返されたテキストには停止シーケンスは含まれません。', + stop_sequencesTip: 'API が進行中のトークンの生成を停止する最大 4 つのシーケンス。返されたテキストには停止シーケンスは含まれません。', stop_sequencesPlaceholder: 'シーケンスを入力してタブキーを押してください', }, tone: { @@ -139,10 +147,12 @@ const translation = { status: 'ベータ版', explore: '探索', apps: 'スタジオ', + appDetail: 'アプリの詳細', + account: 'アカウント', plugins: 'プラグイン', - pluginsTips: 'サードパーティのプラグインを統合するか、ChatGPT互換のAIプラグインを作成します。', + pluginsTips: 'サードパーティのプラグインを統合するか、ChatGPT 互換の AI プラグインを作成します。', datasets: 'ナレッジ', - datasetsTips: '近日公開:独自のテキストデータをインポートするか、Webhookを介してリアルタイムにデータを記述してLLMコンテキストを強化します。', + datasetsTips: '近日公開:独自のテキストデータをインポートするか、Webhook を介してリアルタイムにデータを記述して LLM コンテキストを強化します。', newApp: '新しいアプリ', newDataset: 'ナレッジの作成', tools: 'ツール', @@ -159,8 +169,9 @@ const translation = { communityFeedback: 'フィードバック', roadmap: 'ロードマップ', community: 'コミュニティ', - about: 'Difyについて', + about: 'Dify について', logout: 'ログアウト', + github: 'GitHub', }, compliance: { soc2Type1: 'SOC 2 Type I 報告書', @@ -181,7 +192,7 @@ const translation = { provider: 'モデルプロバイダー', dataSource: 'データソース', plugin: 'プラグイン', - apiBasedExtension: 'API拡張', + apiBasedExtension: 'API 拡張', generalGroup: '一般', }, account: { @@ -195,9 +206,9 @@ const translation = { currentPassword: '現在のパスワード', newPassword: '新しいパスワード', confirmPassword: 'パスワードを確認', - notEqual: '2つのパスワードが異なります。', - langGeniusAccount: 'Difyアカウント', - langGeniusAccountTip: 'Difyアカウントと関連するユーザーデータ。', + notEqual: '2 つのパスワードが異なります。', + langGeniusAccount: 'アカウント関連データ', + langGeniusAccountTip: 'アカウントに関連するユーザーデータ。', editName: '名前を編集', showAppLength: '{{length}}アプリを表示', delete: 'アカウントを削除', @@ -205,14 +216,14 @@ const translation = { deleteConfirmTip: '確認のため、登録したメールから次の内容をに送信してください ', account: 'アカウント', myAccount: 'マイアカウント', - studio: 'Difyスタジオ', + studio: 'スタジオ', deletePrivacyLinkTip: 'お客様のデータの取り扱い方法の詳細については、当社の', deletePrivacyLink: 'プライバシーポリシー。', deleteSuccessTip: 'アカウントの削除が完了するまでに時間が必要です。すべて完了しましたら、メールでお知らせします。', deleteLabel: '確認するには、以下にメールアドレスを入力してください', deletePlaceholder: 'メールアドレスを入力してください', verificationLabel: '認証コード', - verificationPlaceholder: '6桁のコードを貼り付けます', + verificationPlaceholder: '6 桁のコードを貼り付けます', permanentlyDeleteButton: 'アカウントを完全に削除', feedbackTitle: 'フィードバック', feedbackLabel: 'アカウントを削除した理由を教えてください。', @@ -241,7 +252,7 @@ const translation = { datasetOperator: 'ナレッジ管理員', datasetOperatorTip: 'ナレッジベースのみを管理できる', inviteTeamMember: 'チームメンバーを招待する', - inviteTeamMemberTip: '彼らはサインイン後、直接あなた様のチームデータにアクセスできます。', + inviteTeamMemberTip: '彼らはサインイン後、直接あなたのチームデータにアクセスできます。', emailNotSetup: 'メールサーバーがセットアップされていないので、招待メールを送信することはできません。代わりに招待後に発行される招待リンクをユーザーに通知してください。', email: 'メール', emailInvalid: '無効なメール形式', @@ -249,7 +260,7 @@ const translation = { sendInvite: '招待を送る', invitedAsRole: '{{role}}ユーザーとして招待されました', invitationSent: '招待が送信されました', - invitationSentTip: '招待が送信され、彼らはDifyにサインインしてあなた様のチームデータにアクセスできます。', + invitationSentTip: '招待が送信され、彼らは Dify にサインインしてあなたのチームデータにアクセスできます。', invitationLink: '招待リンク', failedInvitationEmails: '以下のユーザーは正常に招待されませんでした', ok: 'OK', @@ -261,14 +272,14 @@ const translation = { setEditor: 'エディターに設定', disInvite: '招待をキャンセル', deleteMember: 'メンバーを削除', - you: '(あなた様)', + you: '(あなた)', }, integrations: { connected: '接続済み', google: 'Google', - googleAccount: 'Googleアカウントでログイン', + googleAccount: 'Google アカウントでログイン', github: 'GitHub', - githubAccount: 'GitHubアカウントでログイン', + githubAccount: 'GitHub アカウントでログイン', connect: '接続', }, language: { @@ -276,29 +287,29 @@ const translation = { timezone: 'タイムゾーン', }, provider: { - apiKey: 'APIキー', - enterYourKey: 'ここにAPIキーを入力してください', - invalidKey: '無効なOpenAI APIキー', + apiKey: 'API キー', + enterYourKey: 'ここに API キーを入力してください', + invalidKey: '無効な OpenAI API キー', validatedError: '検証に失敗しました:', validating: 'キーの検証中...', - saveFailed: 'APIキーの保存に失敗しました', - apiKeyExceedBill: 'このAPI KEYには使用可能なクォータがありません。詳細は', + saveFailed: 'API キーの保存に失敗しました', + apiKeyExceedBill: 'この API KEY には使用可能なクォータがありません。詳細は', addKey: 'キーを追加', comingSoon: '近日公開', editKey: '編集', - invalidApiKey: '無効なAPIキー', + invalidApiKey: '無効な API キー', azure: { - apiBase: 'APIベース', - apiBasePlaceholder: 'Azure OpenAIエンドポイントのAPIベースURL。', - apiKey: 'APIキー', - apiKeyPlaceholder: 'ここにAPIキーを入力してください', - helpTip: 'Azure OpenAIサービスを学ぶ', + apiBase: 'API ベース', + apiBasePlaceholder: 'Azure OpenAI エンドポイントの API ベース URL。', + apiKey: 'API キー', + apiKeyPlaceholder: 'ここに API キーを入力してください', + helpTip: 'Azure OpenAI サービスを学ぶ', }, openaiHosted: { - openaiHosted: 'ホステッドOpenAI', + openaiHosted: 'ホステッド OpenAI', onTrial: 'トライアル中', exhausted: 'クォータが使い果たされました', - desc: 'Difyが提供するOpenAIホスティングサービスを使用すると、GPT-3.5などのモデルを使用できます。トライアルクォータが使い果たされる前に、他のモデルプロバイダを設定する必要があります。', + desc: 'Dify が提供する OpenAI ホスティングサービスを使用すると、GPT-3.5 などのモデルを使用できます。トライアルクォータが使い果たされる前に、他のモデルプロバイダを設定する必要があります。', callTimes: '通話回数', usedUp: 'トライアルクォータが使い果たされました。独自のモデルプロバイダを追加してください。', useYourModel: '現在、独自のモデルプロバイダを使用しています。', @@ -317,12 +328,12 @@ const translation = { }, anthropic: { using: '埋め込み機能は使用中です', - enableTip: 'Anthropicモデルを有効にするには、まずOpenAIまたはAzure OpenAIサービスにバインドする必要があります。', + enableTip: 'Anthropic モデルを有効にするには、まず OpenAI または Azure OpenAI サービスにバインドする必要があります。', notEnabled: '有効にされていません', - keyFrom: 'AnthropicからAPIキーを取得してください', + keyFrom: 'Anthropic から API キーを取得してください', }, encrypted: { - front: 'API KEYは', + front: 'API KEY は', back: '技術を使用して暗号化および保存されます。', }, }, @@ -350,8 +361,8 @@ const translation = { tip: '会話でのテキスト-to-音声入力に使用するデフォルトモデルを設定します。', }, rerankModel: { - key: 'Rerankモデル', - tip: 'Rerankモデルは、ユーザークエリとの意味的一致に基づいて候補文書リストを再配置し、意味的ランキングの結果を向上させます。', + key: 'Rerank モデル', + tip: 'Rerank モデルは、ユーザークエリとの意味的一致に基づいて候補文書リストを再配置し、意味的ランキングの結果を向上させます。', }, apiKey: 'API-キー', quota: 'クォータ', @@ -363,7 +374,7 @@ const translation = { tip: 'このモデルは削除されました。別のモデルを追加するか、別のモデルを選択してください。', emptyTip: '利用可能なモデルはありません', emptySetting: '設定に移動して構成してください', - rerankTip: 'Rerankモデルを設定してください', + rerankTip: 'Rerank モデルを設定してください', }, card: { quota: 'クォータ', @@ -374,17 +385,17 @@ const translation = { tokens: 'トークン', buyQuota: 'クォータを購入', priorityUse: '優先利用', - removeKey: 'APIキーを削除', + removeKey: 'API キーを削除', tip: '有料クォータは優先して使用されます。有料クォータを使用し終えた後、トライアルクォータが利用されます。', }, item: { deleteDesc: '{{modelName}}はシステムが推測するモデルとして利用されています。削除すると、一部の機能が使用不可能になる可能性があります。ご確認ください。', freeQuota: '無料のクォータ', }, - addApiKey: 'APIキーを追加', - invalidApiKey: '無効なAPIキー', + addApiKey: 'API キーを追加', + invalidApiKey: '無効な API キー', encrypted: { - front: 'APIキーは', + front: 'API キーは', back: ' の技術で暗号化されて保存されます。', }, freeQuota: { @@ -418,17 +429,17 @@ const translation = { providerManaged: 'プロバイダ管理', providerManagedDescription: 'モデルプロバイダによって提供される認証情報を使用します。', defaultConfig: 'デフォルトの設定', - apiKeyStatusNormal: 'APIキーの状態は正常', + apiKeyStatusNormal: 'API キーの状態は正常', apiKeyRateLimit: 'レート制限に到達しました。{{seconds}}秒後に再度利用可能です', addConfig: '設定を追加', editConfig: '設定を編集', - loadBalancingLeastKeyWarning: '負荷分散を利用するには、最低2つのキーを有効化する必要があります。', - loadBalancingInfo: 'デフォルトでは、負荷分散はラウンドロビン方式を採用しています。レート制限が発生した場合、1分間のクールダウン期間が適用されます。', + loadBalancingLeastKeyWarning: '負荷分散を利用するには、最低 2 つのキーを有効化する必要があります。', + loadBalancingInfo: 'デフォルトでは、負荷分散はラウンドロビン方式を採用しています。レート制限が発生した場合、1 分間のクールダウン期間が適用されます。', upgradeForLoadBalancing: '負荷分散を利用するには、プランのアップグレードが必要です。', emptyProviderTitle: 'モデルプロバイダーが設定されていません', discoverMore: 'もっと発見する', installProvider: 'モデルプロバイダーをインストールする', - configureTip: 'APIキーを設定するか、使用するモデルを追加してください', + configureTip: 'API キーを設定するか、使用するモデルを追加してください', toBeConfigured: '設定中', emptyProviderTip: '最初にモデルプロバイダーをインストールしてください。', }, @@ -437,8 +448,8 @@ const translation = { connect: '接続', configure: '設定', notion: { - title: 'ノーション', - description: 'ナレッジデータソースとしてノーションを使用します。', + title: 'Notion', + description: 'ナレッジデータソースとして Notion を使用します。', connectedWorkspace: '接続済みワークスペース', addWorkspace: 'ワークスペースの追加', connected: '接続済み', @@ -466,36 +477,36 @@ const translation = { }, plugin: { serpapi: { - apiKey: 'APIキー', - apiKeyPlaceholder: 'APIキーを入力してください', - keyFrom: 'SerpAPIアカウントページからSerpAPIキーを取得してください', + apiKey: 'API キー', + apiKeyPlaceholder: 'API キーを入力してください', + keyFrom: 'SerpAPI アカウントページから SerpAPI キーを取得してください', }, }, apiBasedExtension: { - title: 'API拡張機能は、Difyのアプリケーション全体での簡単な使用のための設定を簡素化し、集中的なAPI管理を提供します。', - link: '独自のAPI拡張機能を開発する方法について学ぶ。', - linkUrl: 'https://docs.dify.ai/features/extension/api_based_extension', - add: 'API拡張機能を追加', + title: 'API 拡張機能は、Dify のアプリケーション全体での簡単な使用のための設定を簡素化し、集中的な API 管理を提供します。', + link: '独自の API 拡張機能を開発する方法について学ぶ。', + linkUrl: 'https://docs.dify.ai/en/guides/extension/api-based-extension/README', + add: 'API 拡張機能を追加', selector: { - title: 'API拡張機能', - placeholder: 'API拡張機能を選択してください', - manage: 'API拡張機能を管理', + title: 'API 拡張機能', + placeholder: 'API 拡張機能を選択してください', + manage: 'API 拡張機能を管理', }, modal: { - title: 'API拡張機能を追加', - editTitle: 'API拡張機能を編集', + title: 'API 拡張機能を追加', + editTitle: 'API 拡張機能を編集', name: { title: '名前', placeholder: '名前を入力してください', }, apiEndpoint: { - title: 'APIエンドポイント', - placeholder: 'APIエンドポイントを入力してください', + title: 'API エンドポイント', + placeholder: 'API エンドポイントを入力してください', }, apiKey: { - title: 'APIキー', - placeholder: 'APIキーを入力してください', - lengthError: 'APIキーの長さは5文字未満にできません', + title: 'API キー', + placeholder: 'API キーを入力してください', + lengthError: 'API キーの長さは 5 文字未満にできません', }, }, type: 'タイプ', @@ -509,7 +520,7 @@ const translation = { appMenus: { overview: '監視', promptEng: 'オーケストレート', - apiAccess: 'APIアクセス', + apiAccess: 'API アクセス', logAndAnn: 'ログ&アナウンス', logs: 'ログ', }, @@ -554,14 +565,15 @@ const translation = { citation: { title: '引用', linkToDataset: 'ナレッジへのリンク', - characters: '文字数:', - hitCount: '検索回数:', - vectorHash: 'ベクトルハッシュ:', - hitScore: '検索スコア:', + characters: '文字数:', + hitCount: '検索回数:', + vectorHash: 'ベクトルハッシュ:', + hitScore: '検索スコア:', }, - inputPlaceholder: 'ボットと話す', + inputPlaceholder: '{{botName}} と話す', thought: '思考', thinking: '考え中...', + resend: '再送信してください', }, promptEditor: { placeholder: 'ここにプロンプトワードを入力してください。変数を挿入するには「{」を、プロンプトコンテンツブロックを挿入するには「/」を入力します。', @@ -584,7 +596,7 @@ const translation = { modal: { title: '例', user: 'こんにちは', - assistant: 'こんにちは! 今日はどのようにお手伝いできますか?', + assistant: 'こんにちは!今日はどのようにお手伝いできますか?', edit: '会話の役割名を編集', }, }, @@ -638,7 +650,7 @@ const translation = { fileUploader: { uploadFromComputer: 'ローカルアップロード', pasteFileLink: 'ファイルリンクの貼り付け', - pasteFileLinkInputPlaceholder: 'URLを入力...', + pasteFileLinkInputPlaceholder: 'URL を入力...', uploadFromComputerLimit: 'アップロードファイルは{{size}}を超えてはなりません', uploadFromComputerUploadError: 'ファイルのアップロードに失敗しました。再度アップロードしてください。', uploadFromComputerReadError: 'ファイルの読み取りに失敗しました。もう一度やり直してください。', @@ -647,11 +659,18 @@ const translation = { }, license: { expiring_plural: '有効期限 {{count}} 日', - expiring: '1日で有効期限が切れます', + expiring: '1 日で有効期限が切れます', + unlimited: '無制限', }, pagination: { perPage: 'ページあたりのアイテム数', }, + you: 'あなた', + imageInput: { + browse: 'ブラウズする', + supportedFormats: 'PNG、JPG、JPEG、WEBP、および GIF をサポートしています。', + dropImageHere: 'ここに画像をドロップするか、', + }, } export default translation diff --git a/web/i18n/ja-JP/custom.ts b/web/i18n/ja-JP/custom.ts index d9750713c5..a7dcf4e7a8 100644 --- a/web/i18n/ja-JP/custom.ts +++ b/web/i18n/ja-JP/custom.ts @@ -7,14 +7,14 @@ const translation = { suffix: 'ブランドをカスタマイズしましょう。', }, webapp: { - title: 'WebAppブランドのカスタマイズ', - removeBrand: 'Powered by Difyを削除', - changeLogo: 'Powered byブランド画像を変更', - changeLogoTip: '最小サイズ40x40pxのSVGまたはPNG形式', + title: 'WebApp ブランドのカスタマイズ', + removeBrand: 'Powered by Dify を削除', + changeLogo: 'Powered by ブランド画像を変更', + changeLogoTip: '最小サイズ 40x40px の SVG または PNG 形式', }, app: { title: 'アプリヘッダーブランドのカスタマイズ', - changeLogoTip: '最小サイズ80x80pxのSVGまたはPNG形式', + changeLogoTip: '最小サイズ 80x80px の SVG または PNG 形式', }, upload: 'アップロード', uploading: 'アップロード中', diff --git a/web/i18n/ja-JP/dataset-creation.ts b/web/i18n/ja-JP/dataset-creation.ts index 401f0b9084..15bee57c0b 100644 --- a/web/i18n/ja-JP/dataset-creation.ts +++ b/web/i18n/ja-JP/dataset-creation.ts @@ -11,31 +11,31 @@ const translation = { unavailable: 'このナレッジベースは利用できません', }, firecrawl: { - configFirecrawl: '🔥Firecrawlの設定', - apiKeyPlaceholder: 'firecrawl.devからのAPIキー', - getApiKeyLinkText: 'firecrawl.devからAPIキーを取得する', + configFirecrawl: '🔥Firecrawl の設定', + apiKeyPlaceholder: 'firecrawl.dev からの API キー', + getApiKeyLinkText: 'firecrawl.dev から API キーを取得する', }, jinaReader: { - getApiKeyLinkText: '無料のAPIキーを jina.ai で取得', + getApiKeyLinkText: '無料の API キーを jina.ai で取得', apiKeyPlaceholder: 'jina.ai からの API キー', - configJinaReader: 'Jina Readerの設定', + configJinaReader: 'Jina Reader の設定', }, stepOne: { filePreview: 'ファイルプレビュー', pagePreview: 'ページプレビュー', dataSourceType: { file: 'テキストファイルからインポート', - notion: 'Notionから同期', + notion: 'Notion から同期', web: 'ウェブサイトから同期', }, uploader: { title: 'テキストファイルをアップロード', - button: 'ファイルをドラッグ&ドロップするか', + button: 'ファイルまたはフォルダをドラッグアンドドロップする', browse: '参照', - tip: '{{supportTypes}}をサポートしています。1つあたりの最大サイズは{{size}}MBです。', + tip: '{{supportTypes}}をサポートしています。1 つあたりの最大サイズは{{size}}MB です。', validation: { typeError: 'サポートされていないファイルタイプです', - size: 'ファイルサイズが大きすぎます。最大サイズは{{size}}MBです', + size: 'ファイルサイズが大きすぎます。最大サイズは{{size}}MB です', count: '複数のファイルはサポートされていません', filesNumber: 'バッチアップロードの制限({{filesNumber}}個)に達しました。', }, @@ -43,8 +43,8 @@ const translation = { change: '変更', failed: 'アップロードに失敗しました', }, - notionSyncTitle: 'Notionが接続されていません', - notionSyncTip: 'Notionと同期するには、まずNotionへの接続が必要です。', + notionSyncTitle: 'Notion が接続されていません', + notionSyncTip: 'Notion と同期するには、まず Notion への接続が必要です。', connect: '接続する', cancel: 'キャンセル', button: '次へ', @@ -55,44 +55,50 @@ const translation = { input: 'ナレッジベースの名称', placeholder: '入力してください', nameNotEmpty: '名前は空にできません', - nameLengthInvalid: '名前は1〜40文字である必要があります', + nameLengthInvalid: '名前は 1〜40 文字である必要があります', cancelButton: 'キャンセル', confirmButton: '作成', failed: '作成に失敗しました', }, website: { chooseProvider: 'プロバイダーを選択する', - fireCrawlNotConfigured: 'Firecrawlが設定されていません', + fireCrawlNotConfigured: 'Firecrawl が設定されていません', fireCrawlNotConfiguredDescription: 'Firecrawl を使用するには、Firecrawl の API キーを設定してください。', jinaReaderNotConfigured: 'Jina Reader が設定されていません', - jinaReaderNotConfiguredDescription: '無料のAPIキーを入力して、Jina Readerを設定します。', + jinaReaderNotConfiguredDescription: '無料の API キーを入力して、Jina Reader を設定します。', configure: '設定', configureFirecrawl: '配置 Firecrawl', configureJinaReader: '配置 Jina Reader', run: '実行', - firecrawlTitle: '🔥Firecrawlを使っでウエブコンテンツを抽出', - firecrawlDoc: 'Firecrawlドキュメント', - firecrawlDocLink: 'https://docs.dify.ai/guides/knowledge-base/sync-from-website', - jinaReaderTitle: 'サイト全体をMarkdownに変換する', - jinaReaderDoc: 'Jina Readerの詳細', + firecrawlTitle: '🔥Firecrawl を使っでウエブコンテンツを抽出', + firecrawlDoc: 'Firecrawl ドキュメント', + firecrawlDocLink: 'https://docs.dify.ai/en/guides/knowledge-base/create-knowledge-and-upload-documents/import-content-data/sync-from-website', + jinaReaderTitle: 'サイト全体を Markdown に変換する', + jinaReaderDoc: 'Jina Reader の詳細', jinaReaderDocLink: 'https://jina.ai/reader', - useSitemap: 'sitemap(サイトマップ)を使用する', - useSitemapTooltip: 'サイトマップに沿ってサイトをクロールします。そうでない場合、Jina Readerはページの関連性に基づいて繰り返しクロールし、ページ数は少なくなりますが、高品質のページが得られます。', + useSitemap: 'sitemap(サイトマップ) を使用する', + useSitemapTooltip: 'サイトマップに沿ってサイトをクロールします。そうでない場合、Jina Reader はページの関連性に基づいて繰り返しクロールし、ページ数は少なくなりますが、高品質のページが得られます。', options: 'オプション', crawlSubPage: 'サブページをクロールする', limit: '制限', maxDepth: '最大深度', excludePaths: 'パスを除外する', includeOnlyPaths: 'パスのみを含める', - extractOnlyMainContent: 'メインコンテンツのみを抽出する(ヘッダー、ナビ、フッターなどは抽出しない)', - exceptionErrorTitle: 'Firecrawl ジョブの実行中に例外が発生しました:', + extractOnlyMainContent: 'メインコンテンツのみを抽出する (ヘッダー、ナビ、フッターなどは抽出しない)', + exceptionErrorTitle: 'Firecrawl ジョブの実行中に例外が発生しました:', unknownError: '不明なエラー', - totalPageScraped: 'スクレイピングされた総ページ数:', + totalPageScraped: 'スクレイピングされた総ページ数:', selectAll: 'すべて選択', resetAll: 'すべてリセット', scrapTimeInfo: '{{time}} 秒以内に合計 {{total}} ページをスクレイピングしました', preview: 'プレビュー', - maxDepthTooltip: '入力されたURLを基にしたクローリング作業での設定可能な最大深度について説明します。深度0は入力されたURL自体のページを対象としたスクレイピングを意味します。深度1では、元のURLの直下にあるページ(URLに続く最初の"/"以降の内容)もスクレイピングの対象になります。この深度は指定した数値まで増加させることができ、それに応じてスクレイピングの範囲も広がっていきます。', + maxDepthTooltip: '入力された URL を基にしたクローリング作業での設定可能な最大深度について説明します。深度 0 は入力された URL 自体のページを対象としたスクレイピングを意味します。深度 1 では、元の URL の直下にあるページ(URL に続く最初の"/"以降の内容)もスクレイピングの対象になります。この深度は指定した数値まで増加させることができ、それに応じてスクレイピングの範囲も広がっていきます。', + waterCrawlNotConfiguredDescription: 'API キーを使って Watercrawl を設定します。', + configureWatercrawl: 'ウォータークローラーを設定する', + watercrawlDoc: 'ウォータークローリングの文書', + watercrawlTitle: 'Watercrawl を使用してウェブコンテンツを抽出する', + waterCrawlNotConfigured: 'Watercrawl は設定されていません', + watercrawlDocLink: 'https://docs.dify.ai/ja/guides/knowledge-base/create-knowledge-and-upload-documents/import-content-data/sync-from-website', }, }, stepTwo: { @@ -104,22 +110,22 @@ const translation = { general: '汎用', generalTip: '汎用テキスト分割モードです。検索とコンテキスト抽出に同じチャンクを使用します。', parentChild: '親子', - parentChildTip: '親子分割モード(階層分割モード)では、子チャンクを検索に、親チャンクをコンテキスト抽出に使用します。', + parentChildTip: '親子分割モード (階層分割モード) では、子チャンクを検索に、親チャンクをコンテキスト抽出に使用します。', parentChunkForContext: 'コンテキスト用親チャンク', childChunkForRetrieval: '検索用子チャンク', paragraph: '段落', paragraphTip: '区切り文字と最大チャンク長に基づいてテキストを段落に分割し、分割されたテキストを検索用の親チャンクとして使用します。', fullDoc: '全文', - fullDocTip: 'ドキュメント全体を親チャンクとして使用し、直接検索します。パフォーマンス上の理由から、10000トークンを超えるテキストは自動的に切り捨てられます。', + fullDocTip: 'ドキュメント全体を親チャンクとして使用し、直接検索します。パフォーマンス上の理由から、10000 トークンを超えるテキストは自動的に切り捨てられます。', separator: 'チャンク識別子', separatorPlaceholder: '例えば改行(\\\\n)や特殊なセパレータ(例:「***」)', maxLength: '最大チャンク長', overlap: 'チャンクのオーバーラップ', - overlapTip: 'チャンクのオーバーラップを設定することで、それらの間の意味的な関連性を維持し、検索効果を向上させることができます。最大チャンクサイズの10%〜25%を設定することをおすすめします。', + overlapTip: 'チャンクのオーバーラップを設定することで、それらの間の意味的な関連性を維持し、検索効果を向上させることができます。最大チャンクサイズの 10%〜25% を設定することをおすすめします。', overlapCheck: 'チャンクのオーバーラップは最大チャンク長を超えてはいけません', rules: 'テキストの前処理ルール', removeExtraSpaces: '連続するスペース、改行、タブを置換する', - removeUrlEmails: 'すべてのURLとメールアドレスを削除する', + removeUrlEmails: 'すべての URL とメールアドレスを削除する', removeStopwords: '「a」「an」「the」などのストップワードを削除する', preview: 'プレビュー', previewChunk: 'チャンクをプレビュー', @@ -128,15 +134,15 @@ const translation = { qualified: '高品質', highQualityTip: '高品質モードで埋め込みを終了したら、経済的モードに戻すことはできません。', recommend: '推奨', - qualifiedTip: '埋め込みモデルを呼び出してドキュメントを処理し、より正確な検索を行うと、LLMが高品質の回答を生成するのに役立ちます。', - warning: 'モデルプロバイダのAPIキーを設定してください。', + qualifiedTip: '埋め込みモデルを呼び出してドキュメントを処理し、より正確な検索を行うと、LLM が高品質の回答を生成するのに役立ちます。', + warning: 'モデルプロバイダの API キーを設定してください。', click: '設定に移動', economical: '経済的', - economicalTip: '検索時にチャンクあたり10個のキーワードを使用することで、精度は低下しますが、トークン消費を抑えられます。', + economicalTip: '検索時にチャンクあたり 10 個のキーワードを使用することで、精度は低下しますが、トークン消費を抑えられます。', QATitle: '質問と回答形式でセグメント化', QATip: 'このオプションを有効にすると、追加のトークンが消費されます', QALanguage: '使用言語', - useQALanguage: 'Q&A形式で分割', + useQALanguage: 'Q&A 形式で分割', estimateCost: '見積もり', estimateSegment: '推定チャンク数', segmentCount: 'チャンク', @@ -153,28 +159,28 @@ const translation = { save: '保存して処理', cancel: 'キャンセル', sideTipTitle: 'なぜチャンクと前処理が必要なのか', - sideTipP1: 'テキストデータを処理する際、チャンクとクリーニングは2つの重要な前処理ステップです。', + sideTipP1: 'テキストデータを処理する際、チャンクとクリーニングは 2 つの重要な前処理ステップです。', sideTipP2: 'セグメンテーションは長いテキストを段落に分割し、モデルがより理解しやすくします。これにより、モデルの結果の品質と関連性が向上します。', sideTipP3: 'クリーニングは不要な文字や書式を削除し、ナレッジベースをよりクリーンで解析しやすいものにします。', sideTipP4: '適切なチャンクとクリーニングはモデルのパフォーマンスを向上させ、より正確で価値のある結果を提供します。', previewTitle: 'プレビュー', previewTitleButton: 'プレビュー', - previewButton: 'Q&A形式に切り替える', + previewButton: 'Q&A 形式に切り替える', previewSwitchTipStart: '現在のチャンクプレビューはテキスト形式です。質問と回答形式のプレビューに切り替えると、', previewSwitchTipEnd: ' 追加のトークンが消費されます', characters: '文字', indexSettingTip: 'インデックス方法を変更するには、', retrievalSettingTip: '検索方法を変更するには、', datasetSettingLink: 'ナレッジベース設定', - separatorTip: '区切り文字は、テキストを区切るために使用される文字です。\\n\\n と \\n は、段落と行を区切るために一般的に使用される区切り記号です。カンマ (\\n\\n,\\n) と組み合わせると、最大チャンク長を超えると、段落は行で区切られます。自分で定義した特別な区切り文字を使用することもできます(例:***)。', + separatorTip: '区切り文字は、テキストを区切るために使用される文字です。\\n\\n と \\n は、段落と行を区切るために一般的に使用される区切り記号です。カンマ (\\n\\n,\\n) と組み合わせると、最大チャンク長を超えると、段落は行で区切られます。自分で定義した特別な区切り文字を使用することもできます (例:***)。', maxLengthCheck: 'チャンクの最大長は {{limit}} 未満にする必要があります', previewChunkTip: 'プレビューを読み込むには、左側の \'チャンクをプレビュー\' ボタンをクリックしてください', - previewChunkCount: '推定チャンク数: {{count}}', + previewChunkCount: '推定チャンク数:{{count}}', switch: '切り替え', - qaSwitchHighQualityTipTitle: 'Q&A形式には高品質なインデックスが必要です', - qaSwitchHighQualityTipContent: '現在、高品質なインデックス作成のみがQ&A形式の分割をサポートしています。高品質モードに切り替えますか?', + qaSwitchHighQualityTipTitle: 'Q&A 形式には高品質なインデックスが必要です', + qaSwitchHighQualityTipContent: '現在、高品質なインデックス作成のみが Q&A 形式の分割をサポートしています。高品質モードに切り替えますか?', notAvailableForParentChild: '親子インデックスでは利用できません', - notAvailableForQA: 'Q&Aインデックスでは利用できません', + notAvailableForQA: 'Q&A インデックスでは利用できません', parentChildDelimiterTip: '区切り文字とは、テキストを分割するために使用される文字です。\\n\\n は、元のドキュメントを大きな親チャンクに分割する際におすすめです。独自の区切り文字も使用できます。', parentChildChunkDelimiterTip: '区切り文字とは、テキストを分割するために使用される文字です。\\n は、親チャンクを小さな子チャンクに分割する際におすすめです。独自の区切り文字も使用できます。', }, @@ -189,7 +195,7 @@ const translation = { resume: '処理を再開', navTo: 'ドキュメントに移動', sideTipTitle: '次は何ですか', - sideTipContent: 'ドキュメントのインデックスが完了したら、ナレッジベースをアプリケーションのコンテキストとして統合することができます。プロンプトオーケストレーションページでコンテキスト設定を見つけることができます。また、独立したChatGPTインデックスプラグインとしてリリースすることもできます。', + sideTipContent: 'ドキュメントのインデックスが完了したら、ナレッジベースをアプリケーションのコンテキストとして統合することができます。プロンプトオーケストレーションページでコンテキスト設定を見つけることができます。また、独立した ChatGPT インデックスプラグインとしてリリースすることもできます。', modelTitle: '埋め込みを停止してもよろしいですか?', modelContent: '後で処理を再開する必要がある場合は、中断した場所から続行します。', modelButtonConfirm: '確認', @@ -197,9 +203,14 @@ const translation = { }, otherDataSource: { title: '他のデータソースと接続しますか?', - description: '現在、Difyのナレッジベースには利用できるデータソースが限られています。Difyのナレッジベースにデータソースを提供いただくことは、プラットフォームの柔軟性と能力を向上させる上で非常に有益です。貢献ガイドをご用意していますので、ぜひご協力ください。詳細については、以下のリンクをクリックしてください。', + description: '現在、Dify のナレッジベースには利用できるデータソースが限られています。Dify のナレッジベースにデータソースを提供いただくことは、プラットフォームの柔軟性と能力を向上させる上で非常に有益です。貢献ガイドをご用意していますので、ぜひご協力ください。詳細については、以下のリンクをクリックしてください。', learnMore: '詳細はこちら', }, + watercrawl: { + getApiKeyLinkText: 'watercrawl.dev から API キーを取得してください。', + configWatercrawl: 'ウォータークローラーを設定する', + apiKeyPlaceholder: 'watercrawl.dev からの API キー', + }, } export default translation diff --git a/web/i18n/ja-JP/dataset-documents.ts b/web/i18n/ja-JP/dataset-documents.ts index 3943146b7e..ecdbbf512c 100644 --- a/web/i18n/ja-JP/dataset-documents.ts +++ b/web/i18n/ja-JP/dataset-documents.ts @@ -1,11 +1,11 @@ const translation = { list: { title: 'ドキュメント', - desc: 'すべてのファイルがここに表示され、ナレッジベース全体がDifyの引用やチャットプラグインを介してリンクされるか、インデックス化されることができます。', + desc: 'すべてのファイルがここに表示され、ナレッジベース全体が Dify の引用やチャットプラグインを介してリンクされるか、インデックス化されることができます。', learnMore: '詳細はこちら', addFile: 'ファイルを追加', addPages: 'ページを追加', - addUrl: 'URLを追加', + addUrl: 'URL を追加', table: { header: { fileName: 'ファイル名', @@ -51,10 +51,10 @@ const translation = { empty: { title: 'まだドキュメントがありません', upload: { - tip: 'ファイルをアップロードしたり、ウェブサイトから同期したり、NotionやGitHubなどのウェブアプリから同期することができます。', + tip: 'ファイルをアップロードしたり、ウェブサイトから同期したり、Notion や GitHub などの Web アプリから同期することができます。', }, sync: { - tip: 'Difyは定期的にNotionからファイルをダウンロードし、処理を完了します。', + tip: 'Dify は定期的に Notion からファイルをダウンロードし、処理を完了します。', }, }, delete: { @@ -63,9 +63,9 @@ const translation = { }, batchModal: { title: '一括追加', - csvUploadTitle: 'CSVファイルをここにドラッグアンドドロップするか、', + csvUploadTitle: 'CSV ファイルをここにドラッグアンドドロップするか、', browse: '参照', - tip: 'CSVファイルは次の構造に準拠する必要があります:', + tip: 'CSV ファイルは次の構造に準拠する必要があります:', question: '質問', answer: '回答', contentTitle: 'チャンクの内容', @@ -82,8 +82,8 @@ const translation = { }, metadata: { title: 'メタデータ', - desc: 'ドキュメントのメタデータにラベルを付けることで、AIがタイムリーにアクセスできるようになり、ユーザーに参照元が公開されます。', - dateTimeFormat: 'YYYY年M月D日 hh:mm A', + desc: 'ドキュメントのメタデータにラベルを付けることで、AI がタイムリーにアクセスできるようになり、ユーザーに参照元が公開されます。', + dateTimeFormat: 'YYYY 年 M 月 D 日 hh:mm A', docTypeSelectTitle: 'ドキュメントタイプを選択してください', docTypeChangeTitle: 'ドキュメントタイプを変更', docTypeSelectWarning: @@ -95,8 +95,8 @@ const translation = { }, source: { upload_file: 'ファイルをアップロード', - notion: 'Notionから同期', - github: 'GitHubから同期', + notion: 'Notion から同期', + github: 'GitHub から同期', }, type: { book: '書籍', @@ -105,10 +105,10 @@ const translation = { socialMediaPost: 'ソーシャルメディアの投稿', personalDocument: '個人のドキュメント', businessDocument: 'ビジネスドキュメント', - IMChat: 'IMチャット', - wikipediaEntry: 'Wikipediaのエントリー', - notion: 'Notionから同期', - github: 'GitHubから同期', + IMChat: 'IM チャット', + wikipediaEntry: 'Wikipedia のエントリー', + notion: 'Notion から同期', + github: 'GitHub から同期', technicalParameters: '技術的なパラメータ', }, field: { @@ -151,7 +151,7 @@ const translation = { platform: 'プラットフォーム', authorUsername: '著者/ユーザー名', publishDate: '公開日', - postURL: '投稿URL', + postURL: '投稿 URL', topicsTags: 'トピック/タグ', }, personalDocument: { @@ -182,7 +182,7 @@ const translation = { wikipediaEntry: { title: 'タイトル', language: '言語', - webpageURL: 'ウェブページURL', + webpageURL: 'ウェブページ URL', editorContributor: '編集者/寄稿者', lastEditDate: '最終編集日', summaryIntroduction: '概要/紹介', @@ -355,11 +355,11 @@ const translation = { newChildChunk: '新しい子チャンク', keywords: 'キーワード', addKeyWord: 'キーワードを追加', - keywordError: 'キーワードの最大長は20です', + keywordError: 'キーワードの最大長は 20 です', characters_one: '文字', characters_other: '文字', hitCount: '検索回数', - vectorHash: 'ベクトルハッシュ: ', + vectorHash: 'ベクトルハッシュ:', questionPlaceholder: 'ここに質問を追加', questionEmpty: '質問は空にできません', answerPlaceholder: 'ここに回答を追加', @@ -367,7 +367,7 @@ const translation = { contentPlaceholder: 'ここに内容を追加', contentEmpty: '内容は空にできません', newTextSegment: '新しいテキストチャンク', - newQaSegment: '新しいQ&Aチャンク', + newQaSegment: '新しい Q&A チャンク', addChunk: 'チャンクを追加', addChildChunk: '子チャンクを追加', addAnother: '続けて追加', diff --git a/web/i18n/ja-JP/dataset-hit-testing.ts b/web/i18n/ja-JP/dataset-hit-testing.ts index 7b00455636..9e6f5e6c05 100644 --- a/web/i18n/ja-JP/dataset-hit-testing.ts +++ b/web/i18n/ja-JP/dataset-hit-testing.ts @@ -13,7 +13,7 @@ const translation = { input: { title: 'ソーステキスト', placeholder: 'テキストを入力してください。短い記述文がおすすめです。', - countWarning: '最大200文字まで入力できます。', + countWarning: '最大 200 文字まで入力できます。', indexWarning: '高品質のナレッジのみ。', testing: 'テスト中', }, diff --git a/web/i18n/ja-JP/dataset-settings.ts b/web/i18n/ja-JP/dataset-settings.ts index 6b809ddd43..87a48dd005 100644 --- a/web/i18n/ja-JP/dataset-settings.ts +++ b/web/i18n/ja-JP/dataset-settings.ts @@ -7,20 +7,20 @@ const translation = { nameError: '名前は空にできません', desc: 'ナレッジベースの説明', descInfo: 'ナレッジベースの内容を概説するための明確なテキストの説明を書いてください。この説明は、複数のナレッジから推論を選択する際の基準として使用されます。', - descPlaceholder: 'このデータセットの内容を記述してください。詳細に記述することで、AIがデータセットの内容に迅速にアクセスできるようになります。空欄の場合、LangGeniusはデフォルトの検索方法を使用します。', + descPlaceholder: 'このデータセットの内容を記述してください。詳細に記述することで、AI がデータセットの内容に迅速にアクセスできるようになります。空欄の場合、LangGenius はデフォルトの検索方法を使用します。', helpText: '適切なデータセットの説明を作成する方法を学びましょう。', descWrite: '良いナレッジベースの説明の書き方を学ぶ。', permissions: '権限', permissionsOnlyMe: '自分のみ', permissionsAllMember: 'すべてのチームメンバー', permissionsInvitedMembers: '一部のチームメンバー', - me: '(あなた様)', + me: '(あなた)', indexMethod: 'インデックス方法', indexMethodHighQuality: '高品質', - indexMethodHighQualityTip: 'より正確な検索のため、埋め込みモデルを呼び出してドキュメントを処理することで、LLMは高品質な回答を生成できます。', + indexMethodHighQualityTip: 'より正確な検索のため、埋め込みモデルを呼び出してドキュメントを処理することで、LLM は高品質な回答を生成できます。', upgradeHighQualityTip: '高品質モードにアップグレードすると、経済的モードには戻せません。', indexMethodEconomy: '経済的', - indexMethodEconomyTip: 'チャンクあたり10個のキーワードを検索に使用します。トークンは消費しませんが、検索精度は低下します。', + indexMethodEconomyTip: 'チャンクあたり 10 個のキーワードを検索に使用します。トークンは消費しませんが、検索精度は低下します。', embeddingModel: '埋め込みモデル', embeddingModelTip: '埋め込みモデルを変更するには、', embeddingModelTipLink: '設定', @@ -32,10 +32,10 @@ const translation = { longDescription: ' 検索方法についての詳細については、いつでもナレッジベースの設定で変更できます。', }, save: '保存', - externalKnowledgeID: '外部ナレッジベースID', + externalKnowledgeID: '外部ナレッジベース ID', retrievalSettings: '取得設定', - externalKnowledgeAPI: '外部ナレッジベースAPI', - indexMethodChangeToEconomyDisabledTip: 'HQからECOへのダウングレードはできません。', + externalKnowledgeAPI: '外部ナレッジベース API', + indexMethodChangeToEconomyDisabledTip: 'HQ から ECO へのダウングレードはできません。', searchModel: 'モデル検索', }, } diff --git a/web/i18n/ja-JP/dataset.ts b/web/i18n/ja-JP/dataset.ts index 4e367f7809..2bdc4a8d28 100644 --- a/web/i18n/ja-JP/dataset.ts +++ b/web/i18n/ja-JP/dataset.ts @@ -9,33 +9,33 @@ const translation = { fullDoc: '全体', }, externalTag: '外部', - externalAPI: '外部API', - externalAPIPanelTitle: '外部ナレッジベース連携API', - externalKnowledgeId: '外部ナレッジベースID', + externalAPI: '外部 API', + externalAPIPanelTitle: '外部ナレッジベース連携 API', + externalKnowledgeId: '外部ナレッジベース ID', externalKnowledgeName: '外部ナレッジベース名', externalKnowledgeDescription: 'ナレッジベースの説明', - externalKnowledgeIdPlaceholder: 'ナレッジベースIDを入力', + externalKnowledgeIdPlaceholder: 'ナレッジベース ID を入力', externalKnowledgeNamePlaceholder: 'ナレッジベース名を入力', externalKnowledgeDescriptionPlaceholder: 'このナレッジベースの説明(任意)', learnHowToWriteGoodKnowledgeDescription: '効果的なナレッジベースの説明の書き方', - externalAPIPanelDescription: '外部ナレッジベース連携APIは、Dify外のナレッジベースと連携し、そこからナレッジベースを取得するために使用します。', - externalAPIPanelDocumentation: '外部ナレッジベース連携APIの作成方法', + externalAPIPanelDescription: '外部ナレッジベース連携 API は、Dify 外のナレッジベースと連携し、そこからナレッジベースを取得するために使用します。', + externalAPIPanelDocumentation: '外部ナレッジベース連携 API の作成方法', localDocs: 'ローカルドキュメント', documentCount: ' ドキュメント', wordCount: ' k 単語', appCount: ' リンクされたアプリ', createDataset: 'ナレッジベースを作成', - createNewExternalAPI: '新しい外部ナレッジベース連携APIを作成', - noExternalKnowledge: '外部ナレッジベース連携APIがありません。ここをクリックして作成してください', - createExternalAPI: '外部ナレッジベース連携APIを追加', - editExternalAPIFormTitle: '外部ナレッジベース連携APIを編集', + createNewExternalAPI: '新しい外部ナレッジベース連携 API を作成', + noExternalKnowledge: '外部ナレッジベース連携 API がありません。ここをクリックして作成してください', + createExternalAPI: '外部ナレッジベース連携 API を追加', + editExternalAPIFormTitle: '外部ナレッジベース連携 API を編集', editExternalAPITooltipTitle: '連携中のナレッジベース', editExternalAPIConfirmWarningContent: { - front: 'この外部ナレッジベース連携APIは', + front: 'この外部ナレッジベース連携 API は', end: '件の外部ナレッジベースと連携しており、この変更はすべてに適用されます。変更を保存しますか?', }, editExternalAPIFormWarning: { - front: 'この外部APIは', + front: 'この外部 API は', end: '件の外部ナレッジベースと連携しています', }, deleteExternalAPIConfirmWarningContent: { @@ -44,45 +44,45 @@ const translation = { end: 'しますか?', }, content: { - front: 'この外部ナレッジベース連携APIは', - end: '件の外部ナレッジベースと連携しています。このAPIを削除すると、すべて無効になります。このAPIを削除しますか?', + front: 'この外部ナレッジベース連携 API は', + end: '件の外部ナレッジベースと連携しています。この API を削除すると、すべて無効になります。この API を削除しますか?', }, - noConnectionContent: 'このAPIを削除しますか?', + noConnectionContent: 'この API を削除しますか?', }, selectExternalKnowledgeAPI: { - placeholder: '外部ナレッジベース連携APIを選択', + placeholder: '外部ナレッジベース連携 API を選択', }, connectDataset: '外部ナレッジベースと連携', connectDatasetIntro: { title: '外部ナレッジベースとの連携方法', content: { - front: '外部ナレッジベースと連携するには、まず外部APIを作成する必要があります。以下の手順を参照し、', - link: '外部APIの作成方法', - end: 'をご確認ください。次に、対応するナレッジベースIDを左側のフォームに入力してください。すべての情報が正しければ、連携ボタンをクリックすると、自動的にナレッジベースの検索テストに移動します。', + front: '外部ナレッジベースと連携するには、まず外部 API を作成する必要があります。以下の手順を参照し、', + link: '外部 API の作成方法', + end: 'をご確認ください。次に、対応するナレッジベース ID を左側のフォームに入力してください。すべての情報が正しければ、連携ボタンをクリックすると、自動的にナレッジベースの検索テストに移動します。', }, learnMore: '詳細はこちら', }, connectHelper: { - helper1: 'APIとナレッジベースIDを使って外部ナレッジベースと連携します。現在、', + helper1: 'API とナレッジベース ID を使って外部ナレッジベースと連携します。現在、', helper2: '検索機能のみがサポートされています。', helper3: 'この機能を使用する前に、', helper4: 'ヘルプドキュメント', helper5: 'をよくお読みください。', }, - createDatasetIntro: '独自のテキストデータをインポートするか、LLMコンテキストの強化のためにWebhookを介してリアルタイムでデータを書き込むことができます。', + createDatasetIntro: '独自のテキストデータをインポートするか、LLM コンテキストの強化のために Webhook を介してリアルタイムでデータを書き込むことができます。', deleteDatasetConfirmTitle: 'このナレッジベースを削除しますか?', deleteDatasetConfirmContent: - 'ナレッジベースを削除すると元に戻すことはできません。ユーザーはもはやあなた様のナレッジベースにアクセスできず、すべてのプロンプトの設定とログが永久に削除されます。', + 'ナレッジベースを削除すると元に戻すことはできません。ユーザーはもはやあなたのナレッジベースにアクセスできず、すべてのプロンプトの設定とログが永久に削除されます。', datasetUsedByApp: 'このナレッジベースは一部のアプリによって使用されています。アプリはこのナレッジベースを使用できなくなり、すべてのプロンプト設定とログは永久に削除されます。', datasetDeleted: 'ナレッジベースが削除されました', datasetDeleteFailed: 'ナレッジベースの削除に失敗しました', didYouKnow: 'ご存知ですか?', - intro1: 'ナレッジベースはDifyアプリケーションに統合することができます', + intro1: 'ナレッジベースは Dify アプリケーションに統合することができます', intro2: 'コンテキストとして', intro3: '、', intro4: 'または', intro5: '作成することができます', - intro6: '単体のChatGPTインデックスプラグインとして公開するために', + intro6: '単体の ChatGPT インデックスプラグインとして公開するために', unavailable: '利用不可', unavailableTip: '埋め込みモデルが利用できません。デフォルトの埋め込みモデルを設定する必要があります', datasets: 'ナレッジベース', @@ -93,13 +93,13 @@ const translation = { }, externalAPIForm: { name: '名前', - endpoint: 'APIエンドポイント', - apiKey: 'APIキー', + endpoint: 'API エンドポイント', + apiKey: 'API キー', save: '保存', cancel: 'キャンセル', edit: '編集', encrypted: { - front: 'APIトークンは', + front: 'API トークンは', end: '技術で暗号化され、安全に保存されます。', }, }, @@ -114,19 +114,19 @@ const translation = { }, hybrid_search: { title: 'ハイブリッド検索', - description: '全文検索とベクトル検索を同時に実行し、ユーザーのクエリに最適なマッチを選択するためにRerank付けを行います。RerankモデルAPIの設定が必要です。', + description: '全文検索とベクトル検索を同時に実行し、ユーザーのクエリに最適なマッチを選択するために Rerank 付けを行います。Rerank モデル API の設定が必要です。', recommend: '推奨', }, invertedIndex: { title: '転置インデックス', - description: '効率的な検索に使用される構造です。各用語が含まれるドキュメントまたはWebページを指すように、用語ごとに整理されています。', + description: '効率的な検索に使用される構造です。各用語が含まれるドキュメントまたは Web ページを指すように、用語ごとに整理されています。', }, change: '変更', changeRetrievalMethod: '検索方法の変更', }, docsFailedNotice: 'ドキュメントのインデックス作成に失敗しました', retry: '再試行', - documentsDisabled: '{{num}}件のドキュメントが無効 - 30日以上非アクティブ', + documentsDisabled: '{{num}}件のドキュメントが無効 - 30 日以上非アクティブ', enable: '有効化', indexingTechnique: { high_quality: '高品質', @@ -139,12 +139,12 @@ const translation = { invertedIndex: '転置', }, defaultRetrievalTip: 'デフォルトでは、マルチパス検索が使用されます。複数のナレッジベースから情報を取得した後、再ランキングを行います。', - mixtureHighQualityAndEconomicTip: '高品質なナレッジベースとコスト重視のナレッジベースを混在させるには、Rerankモデルが必要です。', - inconsistentEmbeddingModelTip: '選択されたナレッジベースの埋め込みモデルに一貫性がない場合、Rerankモデルが必要です。', - mixtureInternalAndExternalTip: '内部と外部のナレッジベースを混在させる場合、Rerankモデルが必要です。', - allExternalTip: '外部ナレッジベースのみを使用する場合、Rerankモデルを有効にするかを選択できます。有効にしない場合、検索結果はスコアに基づいてソートされます。異なるナレッジベースで検索戦略が一貫していないと、結果が不正確になる可能性があります。', + mixtureHighQualityAndEconomicTip: '高品質なナレッジベースとコスト重視のナレッジベースを混在させるには、Rerank モデルが必要です。', + inconsistentEmbeddingModelTip: '選択されたナレッジベースの埋め込みモデルに一貫性がない場合、Rerank モデルが必要です。', + mixtureInternalAndExternalTip: '内部と外部のナレッジベースを混在させる場合、Rerank モデルが必要です。', + allExternalTip: '外部ナレッジベースのみを使用する場合、Rerank モデルを有効にするかを選択できます。有効にしない場合、検索結果はスコアに基づいてソートされます。異なるナレッジベースで検索戦略が一貫していないと、結果が不正確になる可能性があります。', retrievalSettings: '検索設定', - rerankSettings: 'Rerank設定', + rerankSettings: 'Rerank 設定', weightedScore: { title: 'ウェイト設定', description: '重みを調整することで、並べ替え戦略はセマンティックマッチングとキーワードマッチングのどちらを優先するかを決定します。', @@ -154,9 +154,9 @@ const translation = { semantic: 'セマンティクス', keyword: 'キーワード', }, - nTo1RetrievalLegacy: '製品計画によると、N-to-1 Retrievalは9月に正式に廃止される予定です。それまでは通常通り使用できます。', + nTo1RetrievalLegacy: '製品計画によると、N-to-1 Retrieval は 9 月に正式に廃止される予定です。それまでは通常通り使用できます。', nTo1RetrievalLegacyLink: '詳細はこちら', - nTo1RetrievalLegacyLinkText: ' N-to-1 retrievalは9月に正式に廃止されます。', + nTo1RetrievalLegacyLinkText: ' N-to-1 retrieval は 9 月に正式に廃止されます。', batchAction: { selected: '選択済み', enable: '有効にする', @@ -168,7 +168,7 @@ const translation = { preprocessDocument: '{{num}}件のドキュメントを前処理', allKnowledge: 'ナレッジベース全体', allKnowledgeDescription: 'このワークスペースにナレッジベース全体を表示する場合に選択します。ワークスペースのオーナーのみがすべてのナレッジベースを管理できます。', - embeddingModelNotAvailable: 'Embeddingモデル不可用。', + embeddingModelNotAvailable: 'Embedding モデル不可用。', metadata: { metadata: 'メタデータ', addMetadata: 'メタデータを追加', diff --git a/web/i18n/ja-JP/education.ts b/web/i18n/ja-JP/education.ts index d51bac817d..20544b1dee 100644 --- a/web/i18n/ja-JP/education.ts +++ b/web/i18n/ja-JP/education.ts @@ -1,8 +1,8 @@ const translation = { toVerified: '教育認証を取得', toVerifiedTip: { - front: '現在、教育認証ステータスを取得する資格があります。以下に教育情報を入力し、認証プロセスを完了すると、Difyプロフェッショナルプランの', - coupon: '50%割引クーポン', + front: '現在、教育認証ステータスを取得する資格があります。以下に教育情報を入力し、認証プロセスを完了すると、Dify プロフェッショナルプランの', + coupon: '100%割引クーポン', end: 'を受け取ることができます。', }, currentSigned: '現在ログイン中のアカウントは', @@ -29,18 +29,18 @@ const translation = { privacyPolicy: 'プライバシーポリシー', }, option: { - age: '18歳以上であることを確認します。', - inSchool: '提供した教育機関に在籍または勤務している ことを確認します。Difyは在籍/雇用証明の提出を求める場合があります。不正な情報を申告した場合、教育認証に基づき免除された費用を支払うことに同意します。', + age: '18 歳以上であることを確認します。', + inSchool: '提供した教育機関に在籍または勤務している ことを確認します。Dify は在籍/雇用証明の提出を求める場合があります。不正な情報を申告した場合、教育認証に基づき免除された費用を支払うことに同意します。', }, }, }, submit: '送信', submitError: 'フォームの送信に失敗しました。しばらくしてから再度ご提出ください。', learn: '教育認証の取得方法はこちら', - successTitle: 'Dify教育認証を取得しました!', - successContent: 'お客様のアカウントに Difyプロフェッショナルプランの50%割引クーポン を発行しました。有効期間は 1年間 ですので、期限内にご利用ください。', - rejectTitle: 'Dify教育認証が拒否されました', - rejectContent: '申し訳ございませんが、このメールアドレスでは 教育認証 の資格を取得できず、Difyプロフェッショナルプランの50%割引クーポン を受け取ることはできません。', + successTitle: 'Dify 教育認証を取得しました!', + successContent: 'お客様のアカウントに Dify プロフェッショナルプランの 100% 割引クーポン を発行しました。有効期間は 1 年間 ですので、期限内にご利用ください。', + rejectTitle: 'Dify 教育認証が拒否されました', + rejectContent: '申し訳ございませんが、このメールアドレスでは 教育認証 の資格を取得できず、Dify プロフェッショナルプランの 100%割引クーポン を受け取ることはできません。', emailLabel: '現在のメールアドレス', } diff --git a/web/i18n/ja-JP/explore.ts b/web/i18n/ja-JP/explore.ts index 37a1f4182d..09a0748f08 100644 --- a/web/i18n/ja-JP/explore.ts +++ b/web/i18n/ja-JP/explore.ts @@ -16,7 +16,7 @@ const translation = { }, }, apps: { - title: 'Difyによるアプリの探索', + title: 'アプリを探索', description: 'これらのテンプレートアプリを即座に使用するか、テンプレートに基づいて独自のアプリをカスタマイズしてください。', allCategories: '推奨', }, diff --git a/web/i18n/ja-JP/login.ts b/web/i18n/ja-JP/login.ts index 7ba8047aff..84ab9eecd0 100644 --- a/web/i18n/ja-JP/login.ts +++ b/web/i18n/ja-JP/login.ts @@ -1,6 +1,6 @@ const translation = { pageTitle: 'はじめましょう!👋', - welcome: 'Difyへようこそ。続行するにはログインしてください。', + welcome: 'Dify へようこそ。続行するにはログインしてください。', email: 'メールアドレス', emailPlaceholder: 'メールアドレスを入力してください', password: 'パスワード', @@ -9,10 +9,10 @@ const translation = { namePlaceholder: 'ユーザー名を入力してください', forget: 'パスワードをお忘れですか?', signBtn: 'サインイン', - sso: 'SSOに続ける', + sso: 'SSO に続ける', installBtn: 'セットアップ', setAdminAccount: '管理者アカウントの設定', - setAdminAccountDesc: 'アプリケーションの作成やLLMプロバイダの管理など、管理者アカウントの最大権限を設定します。', + setAdminAccountDesc: 'アプリケーションの作成や LLM プロバイダの管理など、管理者アカウントの最大権限を設定します。', createAndSignIn: '作成してサインイン', oneMoreStep: 'あと一歩', createSample: 'この情報を基に、サンプルアプリケーションを作成します', @@ -20,14 +20,14 @@ const translation = { invitationCodePlaceholder: '招待コードを入力してください', interfaceLanguage: 'インターフェース言語', timezone: 'タイムゾーン', - go: 'Difyへ移動', + go: 'Dify へ移動', sendUsMail: '自己紹介をメールで送信し、招待リクエストを処理します。', acceptPP: 'プライバシーポリシーを読み、同意します', reset: 'パスワードをリセットするには、次のコマンドを実行してください', - withGitHub: 'GitHubで続行', - withGoogle: 'Googleで続行', - rightTitle: 'LLMのフルポテンシャルを解き放つ', - rightDesc: '魅力的で操作可能で改善可能なAIアプリケーションを簡単に構築します。', + withGitHub: 'GitHub で続行', + withGoogle: 'Google で続行', + rightTitle: 'LLM のフルポテンシャルを解き放つ', + rightDesc: '魅力的で操作可能で改善可能な AI アプリケーションを簡単に構築します。', tos: '利用規約', pp: 'プライバシーポリシー', tosDesc: 'サインアップすることで、以下に同意するものとします', @@ -53,20 +53,20 @@ const translation = { emailInValid: '有効なメールアドレスを入力してください', nameEmpty: '名前は必須です', passwordEmpty: 'パスワードは必須です', - passwordLengthInValid: 'パスワードは8文字以上でなければなりません', - passwordInvalid: 'パスワードは文字と数字を含み、長さは8以上である必要があります', + passwordLengthInValid: 'パスワードは 8 文字以上でなければなりません', + passwordInvalid: 'パスワードは文字と数字を含み、長さは 8 以上である必要があります', registrationNotAllowed: 'アカウントが見つかりません。登録するためにシステム管理者に連絡してください。', }, license: { - tip: 'GitHubのオープンソースライセンスを確認してから、Dify Community Editionを開始してください。', + tip: 'GitHub のオープンソースライセンスを確認してから、Dify Community Edition を開始してください。', link: 'オープンソースライセンス', }, join: '参加する', - joinTipStart: 'あなた様を招待します', + joinTipStart: 'あなたを招待します', joinTipEnd: 'チームに参加する', invalid: 'リンクの有効期限が切れています', - explore: 'Difyを探索する', - activatedTipStart: 'あなた様は', + explore: 'Dify を探索する', + activatedTipStart: 'あなたは', activatedTipEnd: 'チームに参加しました', activated: '今すぐサインイン', adminInitPassword: '管理者初期化パスワード', @@ -74,13 +74,13 @@ const translation = { checkCode: { invalidCode: '無効なコード', verify: '確かめる', - verificationCodePlaceholder: '6桁のコードを入力してください', + verificationCodePlaceholder: '6 桁のコードを入力してください', useAnotherMethod: '別の方法を使用する', - didNotReceiveCode: 'コードが届きませんか?', + didNotReceiveCode: 'コードが届きませんか?', resend: '再送', verificationCode: '認証コード', tips: '<strong>確認コードを{{email}}に送信します。</strong>', - validTime: 'コードは5分間有効であることに注意してください', + validTime: 'コードは 5 分間有効であることに注意してください', emptyCode: 'コードが必要です', checkYourEmail: 'メールをチェックしてください', }, @@ -90,7 +90,7 @@ const translation = { resetPassword: 'パスワードのリセット', changePasswordBtn: 'パスワードを設定する', setYourAccount: 'アカウントを設定する', - withSSO: 'SSOを続行する', + withSSO: 'SSO を続行する', noLoginMethod: '認証方法が構成されていません', backToLogin: 'ログインに戻る', continueWithCode: 'コードで続行', @@ -98,13 +98,18 @@ const translation = { usePassword: 'パスワードを使用', sendVerificationCode: '確認コードの送信', enterYourName: 'ユーザー名を入力してください', - resetPasswordDesc: 'Difyへのサインアップに使用したメールアドレスを入力すると、パスワードリセットメールが送信されます。', + resetPasswordDesc: 'Dify へのサインアップに使用したメールアドレスを入力すると、パスワードリセットメールが送信されます。', licenseLost: 'ライセンスを失った', - licenseExpiredTip: 'ワークスペースの Dify Enterprise ライセンスの有効期限が切れています。Difyを引き続き使用するには、管理者に連絡してください。', + licenseExpiredTip: 'ワークスペースの Dify Enterprise ライセンスの有効期限が切れています。Dify を引き続き使用するには、管理者に連絡してください。', licenseInactive: 'ライセンスが非アクティブです', - licenseInactiveTip: 'ワークスペースの Dify Enterprise ライセンスが非アクティブです。Difyを引き続き使用するには、管理者に連絡してください。', + licenseInactiveTip: 'ワークスペースの Dify Enterprise ライセンスが非アクティブです。Dify を引き続き使用するには、管理者に連絡してください。', licenseExpired: 'ライセンスの有効期限が切れています', - licenseLostTip: 'Difyライセンスサーバーへの接続に失敗しました。続けてDifyを使用するために管理者に連絡してください。', + licenseLostTip: 'Dify ライセンスサーバーへの接続に失敗しました。続けて Dify を使用するために管理者に連絡してください。', + webapp: { + noLoginMethod: 'Web アプリに対して認証方法が構成されていません', + noLoginMethodTip: 'システム管理者に連絡して、認証方法を追加してください。', + disabled: 'Web アプリの認証が無効になっています。システム管理者に連絡して有効にしてください。直接アプリを使用してみてください。', + }, } export default translation diff --git a/web/i18n/ja-JP/plugin.ts b/web/i18n/ja-JP/plugin.ts index 1d2f1a2fb5..6b0c9748ba 100644 --- a/web/i18n/ja-JP/plugin.ts +++ b/web/i18n/ja-JP/plugin.ts @@ -17,7 +17,7 @@ const translation = { list: { source: { local: 'ローカルパッケージファイルからインストール', - github: 'GitHubからインストールする', + github: 'GitHub からインストールする', marketplace: 'マーケットプレイスからインストール', }, noInstalled: 'プラグインはインストールされていません', @@ -33,7 +33,7 @@ const translation = { marketplace: 'マーケットプレイスからインストールされました', local: 'ローカルプラグイン', debugging: 'デバッグプラグイン', - github: 'Githubからインストールしました', + github: 'Github からインストールしました', }, operation: { info: 'プラグイン情報', @@ -51,7 +51,7 @@ const translation = { unsupportedContent2: 'バージョンを切り替えるにはクリックしてください。', unsupportedContent: 'インストールされたプラグインのバージョンは、このアクションを提供していません。', title: 'ツールを追加', - uninstalledContent: 'このプラグインはローカル/GitHubリポジトリからインストールされます。インストール後にご利用ください。', + uninstalledContent: 'このプラグインはローカル/GitHub リポジトリからインストールされます。インストール後にご利用ください。', descriptionLabel: 'ツールの説明', auto: '自動', params: '推論設定', @@ -59,12 +59,13 @@ const translation = { placeholder: 'ツールを選択...', uninstalledTitle: 'ツールがインストールされていません', empty: 'ツールを追加するには「+」ボタンをクリックしてください。複数のツールを追加できます。', - paramsTip1: 'LLM推論パラメータを制御します。', + paramsTip1: 'LLM 推論パラメータを制御します。', toolLabel: '道具', unsupportedTitle: 'サポートされていないアクション', + toolSetting: 'ツール設定', }, endpointDisableTip: 'エンドポイントを無効にする', - endpointModalDesc: '設定が完了すると、APIエンドポイントを介してプラグインが提供する機能を使用できます。', + endpointModalDesc: '設定が完了すると、API エンドポイントを介してプラグインが提供する機能を使用できます。', endpointDisableContent: '{{name}}を無効にしますか?', endpointModalTitle: 'エンドポイントを設定する', endpointDeleteTip: 'エンドポイントを削除', @@ -140,15 +141,15 @@ const translation = { installFromGitHub: { installedSuccessfully: 'インストールに成功しました', installNote: '信頼できるソースからのみプラグインをインストールするようにしてください。', - updatePlugin: 'GitHubからプラグインを更新する', + updatePlugin: 'GitHub からプラグインを更新する', selectPackage: 'パッケージを選択', installFailed: 'インストールに失敗しました', selectPackagePlaceholder: 'パッケージを選択してください', - gitHubRepo: 'GitHubリポジトリ', + gitHubRepo: 'GitHub リポジトリ', selectVersionPlaceholder: 'バージョンを選択してください', uploadFailed: 'アップロードに失敗しました', selectVersion: 'バージョンを選択', - installPlugin: 'GitHubからプラグインをインストールする', + installPlugin: 'GitHub からプラグインをインストールする', }, upgrade: { title: 'プラグインをインストールする', @@ -161,14 +162,14 @@ const translation = { }, error: { fetchReleasesError: 'リリースを取得できません。後でもう一度お試しください。', - inValidGitHubUrl: '無効なGitHub URLです。有効なURLを次の形式で入力してください: https://github.com/owner/repo', - noReleasesFound: 'リリースは見つかりません。GitHubリポジトリまたは入力URLを確認してください。', + inValidGitHubUrl: '無効な GitHub URL です。有効な URL を次の形式で入力してください:https://github.com/owner/repo', + noReleasesFound: 'リリースは見つかりません。GitHub リポジトリまたは入力 URL を確認してください。', }, marketplace: { - empower: 'AI開発をサポートする', + empower: 'AI 開発をサポートする', discover: '探索', and: 'と', - difyMarketplace: 'Difyマーケットプレイス', + difyMarketplace: 'Dify マーケットプレイス', moreFrom: 'マーケットプレイスからのさらなる情報', noPluginFound: 'プラグインが見つかりません', pluginsResult: '{{num}} 件の結果', @@ -180,8 +181,8 @@ const translation = { firstReleased: 'リリース順', }, viewMore: 'もっと見る', - verifiedTip: 'このプラグインはDifyによって認証されています', - partnerTip: 'このプラグインはDifyのパートナーによって認証されています', + verifiedTip: 'このプラグインは Dify によって認証されています', + partnerTip: 'このプラグインは Dify のパートナーによって認証されています', }, task: { installError: '{{errorLength}} プラグインのインストールに失敗しました。表示するにはクリックしてください。', @@ -189,7 +190,7 @@ const translation = { clearAll: 'すべてクリア', installedError: '{{errorLength}} プラグインのインストールに失敗しました', installingWithError: '{{installingLength}}個のプラグインをインストール中、{{successLength}}件成功、{{errorLength}}件失敗', - installing: '{{installingLength}}個のプラグインをインストール中、0個完了。', + installing: '{{installingLength}}個のプラグインをインストール中、0 個完了。', }, from: 'インストール元', install: '{{num}} インストール', @@ -205,8 +206,12 @@ const translation = { searchTools: '検索ツール...', installPlugin: 'プラグインをインストールする', searchInMarketplace: 'マーケットプレイスで検索', - submitPlugin: 'プラグインを提出する', - difyVersionNotCompatible: '現在のDifyバージョンはこのプラグインと互換性がありません。最小バージョンは{{minimalDifyVersion}}です。', + difyVersionNotCompatible: '現在の Dify バージョンはこのプラグインと互換性がありません。最小バージョンは{{minimalDifyVersion}}です。', + metadata: { + title: 'プラグイン', + }, + requestAPlugin: 'プラグインをリクエストする', + publishPlugins: 'プラグインを公開する', } export default translation diff --git a/web/i18n/ja-JP/run-log.ts b/web/i18n/ja-JP/run-log.ts index 758e37c7de..2c4bc46331 100644 --- a/web/i18n/ja-JP/run-log.ts +++ b/web/i18n/ja-JP/run-log.ts @@ -19,7 +19,7 @@ const translation = { steps: '処理ステップ数', }, resultEmpty: { - title: '今回の実行ではJSON形式のみが出力されました', + title: '今回の実行では JSON 形式のみが出力されました', tipLeft: '詳細を確認するには', link: '詳細情報パネル', tipRight: 'へ移動してください', diff --git a/web/i18n/ja-JP/share-app.ts b/web/i18n/ja-JP/share-app.ts index 948fd28bd9..20dad7faec 100644 --- a/web/i18n/ja-JP/share-app.ts +++ b/web/i18n/ja-JP/share-app.ts @@ -30,10 +30,12 @@ const translation = { }, tryToSolve: '問題を解決する', temporarySystemIssue: 'システムに一時的な問題が発生しています', + expand: '拡大', + collapse: '縮小', }, generation: { tabs: { - create: '1回実行', + create: '1 回実行', batch: '一括実行', saved: '保存済み', }, @@ -42,7 +44,7 @@ const translation = { description: 'コンテンツ生成後に結果がここに表示されます', startCreateContent: '生成を開始', }, - title: 'AI文章作成', + title: 'AI 文章作成', queryTitle: '入力内容', completionResult: '生成結果', queryPlaceholder: '入力してください', @@ -50,11 +52,11 @@ const translation = { execution: '処理中', executions: '{{num}}回実行', copy: 'コピー', - resultTitle: 'AI生成結果', - noData: 'AIがコンテンツを生成します', - csvUploadTitle: 'CSVファイルをドロップするか', + resultTitle: 'AI 生成結果', + noData: 'AI がコンテンツを生成します', + csvUploadTitle: 'CSV ファイルをドロップするか', browse: 'ファイルを選択', - csvStructureTitle: 'CSV形式要件:', + csvStructureTitle: 'CSV 形式要件:', downloadTemplate: 'テンプレートを取得', field: '', batchFailed: { @@ -65,12 +67,15 @@ const translation = { errorMsg: { empty: 'ファイル内容が空です', fileStructNotMatch: 'ファイル形式が不正です', - emptyLine: '{{rowIndex}}行目: 内容が空です', - invalidLine: '{{rowIndex}}行目: {{varName}}の入力が必要です', - moreThanMaxLengthLine: '{{rowIndex}}行目: {{varName}}が制限長({{maxLength}})を超過', - atLeastOne: '1行以上のデータが必要です', + emptyLine: '{{rowIndex}}行目:内容が空です', + invalidLine: '{{rowIndex}}行目:{{varName}}の入力が必要です', + moreThanMaxLengthLine: '{{rowIndex}}行目:{{varName}}が制限長({{maxLength}})を超過', + atLeastOne: '1 行以上のデータが必要です', }, }, + login: { + backToHome: 'ホームに戻る', + }, } export default translation diff --git a/web/i18n/ja-JP/time.ts b/web/i18n/ja-JP/time.ts index e2410dd34b..6594533b2b 100644 --- a/web/i18n/ja-JP/time.ts +++ b/web/i18n/ja-JP/time.ts @@ -1,3 +1,37 @@ -const translation = {} +const translation = { + daysInWeek: { + Tue: '火曜日', + Sat: '土曜日', + Mon: '月曜日', + Thu: '木曜日', + Fri: '金曜日', + Wed: '水曜日', + Sun: '日曜日', + }, + months: { + November: '11 月', + December: '12 月', + March: '3 月', + September: '9 月', + July: '7 月', + April: '4 月', + February: '2 月', + June: '6 月', + January: '1 月', + May: '5 月', + August: '8 月', + October: '10 月', + }, + operation: { + now: '今', + cancel: 'キャンセル', + ok: 'はい', + pickDate: '日付を選択', + }, + title: { + pickTime: 'ピックタイム', + }, + defaultPlaceholder: '時間を選んでください...', +} export default translation diff --git a/web/i18n/ja-JP/tools.ts b/web/i18n/ja-JP/tools.ts index f234625eba..cf9dad95b3 100644 --- a/web/i18n/ja-JP/tools.ts +++ b/web/i18n/ja-JP/tools.ts @@ -1,7 +1,7 @@ const translation = { title: 'ツール', createCustomTool: 'カスタムツールを作成する', - customToolTip: 'Difyカスタムツールの詳細', + customToolTip: 'Dify カスタムツールの詳細', type: { all: 'すべて', builtIn: 'ツール', @@ -10,12 +10,11 @@ const translation = { }, contribute: { line1: '私は', - line2: 'Difyへのツールの貢献に興味があります。', + line2: 'Dify へのツールの貢献に興味があります。', viewGuide: 'ガイドを見る', }, author: '著者:', auth: { - unauthorized: '認証する', authorized: '認証済み', setup: '使用するための認証を設定する', setupModalTitle: '認証の設定', @@ -30,7 +29,7 @@ const translation = { added: '追加済', manageInTools: 'ツールリストに移動して管理する', emptyTitle: '利用可能なワークフローツールはありません', - emptyTip: '追加するには、「ワークフロー -> ツールとして公開 」に移動する', + emptyTip: '追加するには、「ワークフロー -> ツールとして公開」に移動する', emptyTitleCustom: 'カスタムツールはありません', emptyTipCustom: 'カスタムツールの作成', }, @@ -41,20 +40,20 @@ const translation = { name: '名前', toolNamePlaceHolder: 'ツール名を入力してください', nameForToolCall: 'ツールコールの名前', - nameForToolCallPlaceHolder: '機械認識に使用される名前, 例えば、getCurrentWeather、list_pets', + nameForToolCallPlaceHolder: '機械認識に使用される名前,例えば、getCurrentWeather、list_pets', nameForToolCallTip: '数字、文字、アンダースコアのみがサポートされます。', description: 'ツールの説明', descriptionPlaceholder: 'ツールの使い方の簡単な説明。例えば、特定の場所の温度を知るためなど。', schema: 'スキーマ', - schemaPlaceHolder: 'ここにOpenAPIスキーマを入力してください', - viewSchemaSpec: 'OpenAPI-Swagger仕様を表示する', - importFromUrl: 'URLからインポートする', + schemaPlaceHolder: 'ここに OpenAPI スキーマを入力してください', + viewSchemaSpec: 'OpenAPI-Swagger 仕様を表示する', + importFromUrl: 'URL からインポートする', importFromUrlPlaceHolder: 'https://...', - urlError: '有効なURLを入力してください', + urlError: '有効な URL を入力してください', examples: '例', exampleOptions: { - json: '天気(JSON)', - yaml: 'ペットストア(YAML)', + json: '天気 (JSON)', + yaml: 'ペットストア (YAML)', blankTemplate: '空白テンプレート', }, availableTools: { @@ -69,12 +68,12 @@ const translation = { authMethod: { title: '認証方法', type: '認証タイプ', - keyTooltip: 'HTTPヘッダーキー。アイデアがない場合は "Authorization" として残しておいてもかまいません。またはカスタム値に設定できます。', + keyTooltip: 'HTTP ヘッダーキー。アイデアがない場合は "Authorization" として残しておいてもかまいません。またはカスタム値に設定できます。', types: { none: 'なし', - api_key: 'APIキー', - apiKeyPlaceholder: 'APIキーのHTTPヘッダー名', - apiValuePlaceholder: 'APIキーを入力してください', + api_key: 'API キー', + apiKeyPlaceholder: 'API キーの HTTP ヘッダー名', + apiValuePlaceholder: 'API キーを入力してください', }, key: 'キー', value: '値', @@ -96,10 +95,10 @@ const translation = { method: 'メソッド', methodSetting: '設定', methodSettingTip: 'ユーザーがツール設定を入力する', - methodParameter: 'LLM入力', + methodParameter: 'LLM 入力', methodParameterTip: 'LLM は推論中に入力されます', label: 'ラベル', - labelPlaceholder: 'ラベルを選択します(オプション)', + labelPlaceholder: 'ラベルを選択します (オプション)', description: '説明', descriptionPlaceholder: 'パラメータの意味の説明', }, @@ -108,7 +107,7 @@ const translation = { confirmTitle: '保存しますか?', confirmTip: 'このツールを使用しているアプリは影響を受けます', deleteToolConfirmTitle: 'このツールを削除しますか?', - deleteToolConfirmContent: 'ツールの削除は取り消しできません。ユーザーはもうあなた様のツールにアクセスできません。', + deleteToolConfirmContent: 'ツールの削除は取り消しできません。ユーザーはもうあなたのツールにアクセスできません。', }, test: { title: 'テスト', @@ -137,7 +136,7 @@ const translation = { }, noCustomTool: { title: 'カスタムツールがありません!', - content: 'AIアプリを構築するためのカスタムツールをここで追加および管理します。', + content: 'AI アプリを構築するためのカスタムツールをここで追加および管理します。', createTool: 'ツールを作成する', }, noSearchRes: { diff --git a/web/i18n/ja-JP/workflow.ts b/web/i18n/ja-JP/workflow.ts index c4a36ccdc9..1320e7f3b6 100644 --- a/web/i18n/ja-JP/workflow.ts +++ b/web/i18n/ja-JP/workflow.ts @@ -20,7 +20,7 @@ const translation = { goBackToEdit: '編集に戻る', conversationLog: '会話ログ', features: '機能', - featuresDescription: 'Webアプリの操作性を向上させる機能', + featuresDescription: 'Web アプリの操作性を向上させる機能', ImageUploadLegacyTip: '開始フォームでファイル型変数が作成可能になりました。画像アップロード機能は今後サポート終了となります。', fileUploadTip: '画像アップロード機能がファイルアップロードに拡張されました', featuresDocLink: '詳細を見る', @@ -36,7 +36,7 @@ const translation = { runApp: 'アプリを実行', batchRunApp: 'アプリを一括実行', openInExplore: '探索ページで開く', - accessAPIReference: 'APIリファレンス', + accessAPIReference: 'API リファレンス', embedIntoSite: 'サイトに埋め込む', addTitle: 'タイトルを追加...', addDescription: '説明を追加...', @@ -45,7 +45,7 @@ const translation = { variableNamePlaceholder: '変数名を入力', setVarValuePlaceholder: '変数値を設定', needConnectTip: '接続されていないステップがあります', - maxTreeDepth: '1ブランチあたりの最大ノード数:{{depth}}', + maxTreeDepth: '1 ブランチあたりの最大ノード数:{{depth}}', needEndNode: '終了ブロックを追加する必要があります', needAnswerNode: '回答ブロックを追加する必要があります', workflowProcess: 'ワークフロー処理', @@ -59,10 +59,10 @@ const translation = { processData: 'データ処理', input: '入力', output: '出力', - jinjaEditorPlaceholder: '「/」または 「{」で変数挿入', + jinjaEditorPlaceholder: '「/」または「{」で変数挿入', viewOnly: '閲覧のみ', showRunHistory: '実行履歴を表示', - enableJinja: 'Jinjaテンプレートを有効化', + enableJinja: 'Jinja テンプレートを有効化', learnMore: '詳細を見る', copy: 'コピー', duplicate: '複製', @@ -70,22 +70,26 @@ const translation = { pasteHere: 'ここに貼り付け', pointerMode: 'ポインターモード', handMode: 'ハンドモード', + exportImage: '画像を出力', + exportPNG: 'PNG で出力', + exportJPEG: 'JPEG で出力', + exportSVG: 'SVG で出力', model: 'モデル', - workflowAsTool: 'ワークフローをツールどして公開する', + workflowAsTool: 'ワークフローをツールとして公開する', configureRequired: '設定が必要', configure: '設定', manageInTools: 'ツールページで管理', workflowAsToolTip: 'ワークフロー更新後はツールの再設定が必要です', viewDetailInTracingPanel: '詳細を表示', syncingData: 'データ同期中。。。', - importDSL: 'DSLをインポート', + importDSL: 'DSL をインポート', importDSLTip: '現在の下書きは上書きされます。インポート前にワークフローをエクスポートしてバックアップしてください', backupCurrentDraft: '現在の下書きをバックアップ', - chooseDSL: 'DSL(yml)ファイルを選択', + chooseDSL: 'DSL(yml) ファイルを選択', overwriteAndImport: '上書きしてインポート', importFailure: 'インポート失敗', importWarning: '注意事項', - importWarningDetails: 'DSLバージョンの違いにより機能に影響が出る可能性があります', + importWarningDetails: 'DSL バージョンの違いにより機能に影響が出る可能性があります', importSuccess: 'インポート成功', parallelRun: '並列実行', parallelTip: { @@ -112,7 +116,7 @@ const translation = { }, env: { envPanelTitle: '環境変数', - envDescription: '環境変数は、個人情報や認証情報を格納するために使用することができます。これらは読み取り専用であり、DSLファイルからエクスポートする際には分離されます。', + envDescription: '環境変数は、個人情報や認証情報を格納するために使用することができます。これらは読み取り専用であり、DSL ファイルからエクスポートする際には分離されます。', envPanelButton: '環境変数を追加', modal: { title: '環境変数を追加', @@ -127,7 +131,7 @@ const translation = { export: { title: 'シークレット環境変数をエクスポートしますか?', checkbox: 'シークレット値を含む', - ignore: 'DSLをエクスポート', + ignore: 'DSL をエクスポート', export: 'シークレット値付きでエクスポート', }, }, @@ -143,10 +147,10 @@ const translation = { namePlaceholder: '変数名を入力', type: 'タイプ', value: 'デフォルト値', - valuePlaceholder: 'デフォルト値、設定しない場合は空白にしでください', + valuePlaceholder: 'デフォルト値、設定しない場合は空白にしてください', description: '説明', descriptionPlaceholder: '変数の説明を入力', - editInJSON: 'JSONで編集', + editInJSON: 'JSON で編集', oneByOne: '個別追加', editInForm: 'フォームで編集', arrayValue: '値', @@ -194,7 +198,7 @@ const translation = { variableValue: '変数値', code: 'コード', model: 'モデル', - rerankModel: 'Rerankモデル', + rerankModel: 'Rerank モデル', visionVariable: 'ビジョン変数', }, invalidVariable: '無効な変数です', @@ -237,7 +241,7 @@ const translation = { 'if-else': 'IF/ELSE', 'code': 'コード実行', 'template-transform': 'テンプレート', - 'http-request': 'HTTPリクエスト', + 'http-request': 'HTTP リクエスト', 'variable-assigner': '変数代入器', 'variable-aggregator': '変数集約器', 'assigner': '変数代入', @@ -257,11 +261,11 @@ const translation = { 'answer': 'チャットダイアログの返答内容を定義します。', 'llm': '大規模言語モデルを呼び出して質問回答や自然言語処理を実行します。', 'knowledge-retrieval': 'ナレッジベースからユーザー質問に関連するテキストを検索します。', - 'question-classifier': '質問の分類条件を定義し、LLMが分類に基づいて対話フローを制御します。', - 'if-else': 'if/else条件でワークフローを2つの分岐に分割します。', - 'code': 'Python/NodeJSコードを実行してカスタムロジックを実装します。', - 'template-transform': 'Jinjaテンプレート構文でデータを文字列に変換します。', - 'http-request': 'HTTPリクエストを送信できます。', + 'question-classifier': '質問の分類条件を定義し、LLM が分類に基づいて対話フローを制御します。', + 'if-else': 'if/else 条件でワークフローを 2 つの分岐に分割します。', + 'code': 'Python/NodeJS コードを実行してカスタムロジックを実装します。', + 'template-transform': 'Jinja テンプレート構文でデータを文字列に変換します。', + 'http-request': 'HTTP リクエストを送信できます。', 'variable-assigner': '複数分岐の変数を集約し、下流ノードの設定を統一します。', 'assigner': '書き込み可能な変数(例:会話変数)への値の割り当てを行います。', 'variable-aggregator': '複数分岐の変数を集約し、下流ノードの設定を統一します。', @@ -269,14 +273,14 @@ const translation = { 'loop': '終了条件達成まで、または最大反復回数までロジックを繰り返します。', 'loop-end': '「break」相当の機能です。このノードに設定項目はなく、ループ処理中にこのノードに到達すると即時終了します。', 'parameter-extractor': '自然言語から構造化パラメータを抽出し、後続処理で利用します。', - 'document-extractor': 'アップロード文書をLLM処理用に最適化されたテキストに変換します。', + 'document-extractor': 'アップロード文書を LLM 処理用に最適化されたテキストに変換します。', 'list-operator': '配列のフィルタリングやソート処理を行います。', 'agent': '大規模言語モデルを活用した質問応答や自然言語処理を実行します。', }, operator: { zoomIn: '拡大', zoomOut: '縮小', - zoomTo50: '50%サイズ', + zoomTo50: '50% サイズ', zoomTo100: '等倍表示', zoomToFit: '画面に合わせる', }, @@ -303,6 +307,7 @@ const translation = { organizeBlocks: 'ノード整理', change: '変更', optional: '(任意)', + moveToThisNode: 'このノードに移動する', }, nodes: { common: { @@ -331,7 +336,7 @@ const translation = { defaultValue: { title: 'デフォルト値', desc: '例外発生時のデフォルト出力', - tip: '例外発生時に返される値:', + tip: '例外発生時に返される値:', inLog: 'ノード例外 - デフォルト値を出力', output: 'デフォルト値出力', }, @@ -358,7 +363,7 @@ const translation = { retryFailedTimes: '{{times}}回再試行失敗', times: '回', ms: 'ミリ秒', - retries: '再試行回数: {{num}}', + retries: '再試行回数:{{num}}', }, }, start: { @@ -393,7 +398,7 @@ const translation = { outputVars: '出力変数', }, llm: { - model: 'AIモデル', + model: 'AI モデル', variables: '変数', context: 'コンテキスト', contextTooltip: 'ナレッジベースをコンテキストとして利用', @@ -419,7 +424,35 @@ const translation = { singleRun: { variable: '変数', }, - sysQueryInUser: 'ユーザーメッセージにsys.queryを含めてください', + sysQueryInUser: 'ユーザーメッセージに sys.query を含めてください', + jsonSchema: { + title: '構造化データスキーマ', + instruction: '指示', + promptTooltip: 'テキスト説明から標準 JSON スキーマを自動生成できます。', + promptPlaceholder: 'JSON スキーマを入力...', + generate: '生成', + import: 'JSON インポート', + generateJsonSchema: 'スキーマ生成', + generationTip: '自然言語で簡単に JSON スキーマを作成可能です。', + generating: 'JSON スキーマを生成中...', + generatedResult: '生成結果', + resultTip: 'こちらが生成された結果です。ご満足いただけない場合は、前の画面に戻ってプロンプトを修正できます。', + back: '前に戻る', + regenerate: '再生成する', + apply: '適用', + doc: '構造化出力の詳細を見る', + resetDefaults: '初期化', + required: '必須項目', + addField: 'フィールドを追加', + addChildField: 'サブフィールドを追加', + showAdvancedOptions: '詳細設定', + stringValidations: '文字列検証', + fieldNamePlaceholder: 'フィールド名', + descriptionPlaceholder: '説明を入力', + warningTips: { + saveSchema: '編集中のフィールドを確定してから保存してください。', + }, + }, }, knowledgeRetrieval: { queryVariable: '検索変数', @@ -429,7 +462,7 @@ const translation = { content: 'セグメント内容', title: 'セグメントタイトル', icon: 'セグメントアイコン', - url: 'セグメントURL', + url: 'セグメント URL', metadata: 'メタデータ', }, metadata: { @@ -464,9 +497,9 @@ const translation = { http: { inputVars: '入力変数', api: 'API', - apiPlaceholder: 'URLを入力(変数使用時は"/"を入力)', + apiPlaceholder: 'URL を入力(変数使用時は"/"を入力)', extractListPlaceholder: 'リスト番号を入力(変数使用時は"/"を入力)', - notStartWithHttp: 'APIは http:// または https:// で始まってください', + notStartWithHttp: 'API は http:// または https:// で始まってください', key: 'キー', type: 'タイプ', value: '値', @@ -486,12 +519,12 @@ const translation = { 'authorization': '認証', 'authorizationType': '認証タイプ', 'no-auth': 'なし', - 'api-key': 'APIキー', - 'auth-type': 'API認証タイプ', + 'api-key': 'API キー', + 'auth-type': 'API 認証タイプ', 'basic': 'ベーシック', 'bearer': 'Bearer', 'custom': 'カスタム', - 'api-key-title': 'APIキー', + 'api-key-title': 'API キー', 'header': 'ヘッダー', }, insertVarPlaceholder: '変数を挿入するには\'/\'を入力してください', @@ -505,8 +538,8 @@ const translation = { writePlaceholder: '書き込みタイムアウト(秒)', }, curl: { - title: 'cURLからインポート', - placeholder: 'ここにcURL文字列を貼り付けます', + title: 'cURL からインポート', + placeholder: 'ここに cURL 文字列を貼り付けます', }, }, code: { @@ -519,7 +552,7 @@ const translation = { templateTransform: { inputVars: '入力変数', code: 'コード', - codeSupportTip: 'Jinja2のみをサポートしています', + codeSupportTip: 'Jinja2 のみをサポートしています', outputVars: { output: '変換されたコンテンツ', }, @@ -527,7 +560,7 @@ const translation = { ifElse: { if: 'もし', else: 'それ以外', - elseDescription: 'IF条件が満たされない場合に実行するロジックを定義します。', + elseDescription: 'IF 条件が満たされない場合に実行するロジックを定義します。', and: 'かつ', or: 'または', operator: '演算子', @@ -542,13 +575,15 @@ const translation = { 'empty': '空', 'not empty': '空でない', 'null': 'null', - 'not null': 'nullでない', + 'not null': 'null でない', 'regex match': '正規表現マッチ', 'in': '含まれている', 'not in': '含まれていない', 'all of': 'すべての', 'exists': '存在します', 'not exists': '存在しません', + 'before': '前に', + 'after': '後', }, enterValue: '値を入力', addCondition: '条件を追加', @@ -588,7 +623,7 @@ const translation = { assigner: { 'assignedVariable': '代入された変数', 'writeMode': '書き込みモード', - 'writeModeTip': '代入された変数が配列の場合, 末尾に追記モードを追加する。', + 'writeModeTip': '代入された変数が配列の場合,末尾に追記モードを追加する。', 'over-write': '上書き', 'append': '追記', 'plus': 'プラス', @@ -607,6 +642,8 @@ const translation = { 'over-write': '上書き', 'extend': '延ばす', '*=': '*=', + 'remove-last': '最後を削除する', + 'remove-first': '最初を削除する', }, 'setParameter': 'パラメータを設定...', 'selectAssignedVariable': '代入変数を選択...', @@ -617,19 +654,19 @@ const translation = { 'assignedVarsDescription': '代入される変数は、会話変数などの書き込み可能な変数である必要があります。', }, tool: { - toAuthorize: '承認するには', inputVars: '入力変数', outputVars: { text: 'ツールが生成したコンテンツ', files: { title: 'ツールが生成したファイル', type: 'サポートタイプ。現在は画像のみサポートされています', - transfer_method: '転送方法。値はremote_urlまたはlocal_fileです', - url: '画像URL', - upload_file_id: 'アップロードファイルID', + transfer_method: '転送方法。値は remote_url または local_file です', + url: '画像 URL', + upload_file_id: 'アップロードファイル ID', }, - json: 'ツールで生成されたJSON', + json: 'ツールで生成された JSON', }, + authorize: '認証する', }, questionClassifiers: { model: 'モデル', @@ -668,7 +705,7 @@ const translation = { advancedSetting: '高度な設定', reasoningMode: '推論モード', reasoningModeTip: '関数呼び出しやプロンプトの指示に応答するモデルの能力に基づいて、適切な推論モードを選択できます。', - isSuccess: '成功。成功した場合の値は1、失敗した場合の値は0です。', + isSuccess: '成功。成功した場合の値は 1、失敗した場合の値は 0 です。', errorReason: 'エラーの理由', }, iteration: { @@ -695,7 +732,7 @@ const translation = { parallelModeEnableDesc: '並列モードでは、イテレーション内のタスクは並列実行をサポートします。これは、右側のプロパティパネルで構成できます。', parallelModeEnableTitle: 'パラレルモード有効', MaxParallelismDesc: '最大並列処理は、1 回の反復で同時に実行されるタスクの数を制御するために使用されます。', - answerNodeWarningDesc: '並列モードの警告: 応答ノード、会話変数の割り当て、およびイテレーション内の永続的な読み取り/書き込み操作により、例外が発生する可能性があります。', + answerNodeWarningDesc: '並列モードの警告:応答ノード、会話変数の割り当て、およびイテレーション内の永続的な読み取り/書き込み操作により、例外が発生する可能性があります。', }, loop: { deleteTitle: 'ループノードを削除しますか?', @@ -708,7 +745,7 @@ const translation = { breakCondition: 'ループ終了条件', breakConditionTip: 'ループ内の変数やセッション変数を参照し、終了条件を設定できます。', loopMaxCount: '最大ループ回数', - loopMaxCountError: '最大ループ回数は1から{{maxCount}}の範囲で正しく入力してください。', + loopMaxCountError: '最大ループ回数は 1 から{{maxCount}}の範囲で正しく入力してください。', errorResponseMethod: 'エラー対応方法', ErrorMethod: { operationTerminated: 'エラー時に処理を終了', @@ -721,10 +758,13 @@ const translation = { setLoopVariables: 'ループスコープ内で変数を設定', variableName: '変数名', inputMode: '入力モード', - exitConditionTip: 'ループノードには少なくとも1つの終了条件が必要です', + exitConditionTip: 'ループノードには少なくとも 1 つの終了条件が必要です', loopNode: 'ループノード', - currentLoopCount: '現在のループ回数: {{count}}', - totalLoopCount: '総ループ回数: {{count}}', + currentLoopCount: '現在のループ回数:{{count}}', + totalLoopCount: '総ループ回数:{{count}}', + error_other: '{{count}} エラー', + error_one: '{{count}} エラー', + comma: ',', }, note: { addNote: 'コメントを追加', @@ -751,7 +791,7 @@ const translation = { }, inputVar: '入力変数', learnMore: '詳細はこちら', - supportFileTypes: 'サポートするファイルタイプ: {{types}}。', + supportFileTypes: 'サポートするファイルタイプ:{{types}}。', }, listFilter: { outputVars: { @@ -759,7 +799,7 @@ const translation = { first_record: '最初のレコード', result: 'フィルター結果', }, - limit: 'トップN', + limit: 'トップ N', asc: 'ASC', filterCondition: 'フィルター条件', filterConditionKey: 'フィルター条件キー', @@ -769,7 +809,7 @@ const translation = { filterConditionComparisonOperator: 'フィルター条件を比較オペレーター', inputVar: '入力変数', desc: 'DESC', - extractsCondition: 'N個のアイテムを抽出します', + extractsCondition: 'N 個のアイテムを抽出します', }, agent: { strategy: { @@ -788,7 +828,7 @@ const translation = { modelNotInMarketplace: { manageInPlugins: 'プラグインを管理する', title: 'モデルがインストールされていません', - desc: 'このモデルはローカルまたはGitHubリポジトリからインストールされます。インストール後にご利用ください。', + desc: 'このモデルはローカルまたは GitHub リポジトリからインストールされます。インストール後にご利用ください。', }, modelNotSupport: { title: 'サポートされていないモデル', @@ -800,14 +840,14 @@ const translation = { }, outputVars: { files: { - url: '画像のURL', + url: '画像の URL', type: 'サポートタイプ。現在はサポート画像のみ', - upload_file_id: 'ファイルIDをアップロード', - transfer_method: '転送方法。値はremote_urlまたはlocal_fileです。', + upload_file_id: 'ファイル ID をアップロード', + transfer_method: '転送方法。値は remote_url または local_file です。', title: 'エージェント生成ファイル', }, text: 'エージェント生成コンテンツ', - json: 'エージェント生成のJSON', + json: 'エージェント生成の JSON', }, checkList: { strategyNotSelected: '戦略が選択されていません', @@ -835,9 +875,9 @@ const translation = { toolbox: 'ツールボックス', pluginNotInstalled: 'このプラグインはインストールされていません', strategyNotFoundDescAndSwitchVersion: 'インストールされたプラグインのバージョンはこの戦略を提供していません。バージョンを切り替えるにはクリックしてください。', - pluginNotInstalledDesc: 'このプラグインはGitHubからインストールされています。再インストールするにはプラグインに移動してください。', + pluginNotInstalledDesc: 'このプラグインは GitHub からインストールされています。再インストールするにはプラグインに移動してください。', unsupportedStrategy: 'サポートされていない戦略', - pluginNotFoundDesc: 'このプラグインはGitHubからインストールされています。再インストールするにはプラグインに移動してください。', + pluginNotFoundDesc: 'このプラグインは GitHub からインストールされています。再インストールするにはプラグインに移動してください。', strategyNotFoundDesc: 'インストールされたプラグインのバージョンは、この戦略を提供していません。', }, }, diff --git a/web/i18n/ko-KR/app-api.ts b/web/i18n/ko-KR/app-api.ts index 810e67a875..4f8ac14d86 100644 --- a/web/i18n/ko-KR/app-api.ts +++ b/web/i18n/ko-KR/app-api.ts @@ -16,7 +16,7 @@ const translation = { never: '없음', apiKeyModal: { apiSecretKey: 'API 비밀 키', - apiSecretKeyTips: 'API 키를 보호하여 API의 남용을 방지하십시오. 프런트엔드 코드에서 평문으로 사용하지 마세요. :)', + apiSecretKeyTips: 'API 키를 보호하여 API 의 남용을 방지하십시오. 프런트엔드 코드에서 평문으로 사용하지 마세요. :)', createNewSecretKey: '새로운 비밀 키 생성', secretKey: '비밀 키', created: '생성 날짜', @@ -30,43 +30,43 @@ const translation = { }, completionMode: { title: '완성 모드 API', - info: '문서, 요약, 번역 등 고품질 텍스트 생성을 위해 사용자 입력을 사용하는 완성 메시지 API를 사용합니다. 텍스트 생성은 Dify Prompt Engineering에서 설정한 모델 매개변수와 프롬프트 템플릿에 의존합니다.', + info: '문서, 요약, 번역 등 고품질 텍스트 생성을 위해 사용자 입력을 사용하는 완성 메시지 API 를 사용합니다. 텍스트 생성은 Dify Prompt Engineering 에서 설정한 모델 매개변수와 프롬프트 템플릿에 의존합니다.', createCompletionApi: '완성 메시지 생성', createCompletionApiTip: '질의 응답 모드를 지원하기 위해 완성 메시지를 생성합니다.', inputsTips: - '(선택 사항) Prompt Eng의 변수에 해당하는 키-값 쌍으로 사용자 입력 필드를 제공합니다. 키는 변수 이름이고 값은 매개변수 값입니다. 필드 유형이 Select인 경우 전송되는 값은 미리 설정된 선택 사항 중 하나여야 합니다.', + '(선택 사항) Prompt Eng 의 변수에 해당하는 키 - 값 쌍으로 사용자 입력 필드를 제공합니다. 키는 변수 이름이고 값은 매개변수 값입니다. 필드 유형이 Select 인 경우 전송되는 값은 미리 설정된 선택 사항 중 하나여야 합니다.', queryTips: '사용자 입력 텍스트 내용.', blocking: '블로킹 유형으로 실행이 완료되고 결과가 반환될 때까지 대기합니다. (처리가 오래 걸리면 요청이 중단될 수 있습니다)', - streaming: '스트리밍 반환. SSE(Server-Sent Events)를 기반으로 하는 스트리밍 반환 구현.', - messageFeedbackApi: '메시지 피드백(좋아요)', + streaming: '스트리밍 반환. SSE(Server-Sent Events) 를 기반으로 하는 스트리밍 반환 구현.', + messageFeedbackApi: '메시지 피드백 (좋아요)', messageFeedbackApiTip: '엔드 사용자 대신 수신된 메시지를 "좋아요" 또는 "좋아요"로 평가합니다. 이 데이터는 로그 및 주석 페이지에 표시되며 향후 모델 세부 조정에 사용됩니다.', messageIDTip: '메시지 ID', - ratingTip: '좋아요 또는 좋아요, null은 취소', + ratingTip: '좋아요 또는 좋아요, null 은 취소', parametersApi: '애플리케이션 매개변수 정보 가져오기', parametersApiTip: '변수 이름, 필드 이름, 유형, 기본값을 포함한 설정된 입력 매개변수를 가져옵니다. 일반적으로 이러한 필드는 양식에 표시하거나 클라이언트 로드 후에 기본값을 입력하는 데 사용됩니다.', }, chatMode: { title: '채팅 모드 API', - info: '질의 응답 형식을 사용하는 다목적 대화형 응용 프로그램에는 채팅 메시지 API를 호출하여 대화를 시작합니다. 반환된 conversation_id를 전달하여 계속된 대화를 유지합니다. 응답 매개변수 및 템플릿은 Dify Prompt Eng의 설정에 의존합니다.', + info: '질의 응답 형식을 사용하는 다목적 대화형 응용 프로그램에는 채팅 메시지 API 를 호출하여 대화를 시작합니다. 반환된 conversation_id 를 전달하여 계속된 대화를 유지합니다. 응답 매개변수 및 템플릿은 Dify Prompt Eng 의 설정에 의존합니다.', createChatApi: '채팅 메시지 생성', createChatApiTip: '새로운 대화 메시지를 생성하거나 기존 대화를 계속합니다.', inputsTips: - '(선택 사항) Prompt Eng의 변수에 해당하는 키-값 쌍으로 사용자 입력 필드를 제공합니다. 키는 변수 이름이고 값은 매개변수 값입니다. 필드 유형이 Select인 경우 전송되는 값은 미리 설정된 선택 사항 중 하나여야 합니다.', + '(선택 사항) Prompt Eng 의 변수에 해당하는 키 - 값 쌍으로 사용자 입력 필드를 제공합니다. 키는 변수 이름이고 값은 매개변수 값입니다. 필드 유형이 Select 인 경우 전송되는 값은 미리 설정된 선택 사항 중 하나여야 합니다.', queryTips: '사용자 입력/질문 내용', blocking: '블로킹 유형으로 실행이 완료되고 결과가 반환될 때까지 대기합니다. (처리가 오래 걸리면 요청이 중단될 수 있습니다)', - streaming: '스트리밍 반환. SSE(Server-Sent Events)를 기반으로 하는 스트리밍 반환 구현.', - conversationIdTip: '(선택 사항) 대화 ID: 처음 대화의 경우 비워두고, 계속된 경우 컨텍스트에서 conversation_id를 전달합니다.', - messageFeedbackApi: '메시지 피드백(좋아요)', + streaming: '스트리밍 반환. SSE(Server-Sent Events) 를 기반으로 하는 스트리밍 반환 구현.', + conversationIdTip: '(선택 사항) 대화 ID: 처음 대화의 경우 비워두고, 계속된 경우 컨텍스트에서 conversation_id 를 전달합니다.', + messageFeedbackApi: '메시지 피드백 (좋아요)', messageFeedbackApiTip: '엔드 사용자 대신 수신된 메시지를 "좋아요" 또는 "좋아요"로 평가합니다. 이 데이터는 로그 및 주석 페이지에 표시되며 향후 모델 세부 조정에 사용됩니다.', messageIDTip: '메시지 ID', - ratingTip: '좋아요 또는 좋아요, null은 취소', + ratingTip: '좋아요 또는 좋아요, null 은 취소', chatMsgHistoryApi: '채팅 메시지 기록 가져오기', chatMsgHistoryApiTip: '첫 번째 페이지는 최신의 "limit" 바를 반환합니다. 역순입니다.', chatMsgHistoryConversationIdTip: '대화 ID', chatMsgHistoryFirstId: '현재 페이지의 첫 번째 채팅 레코드의 ID. 기본값은 없음입니다.', chatMsgHistoryLimit: '한 번에 반환되는 채팅 수', conversationsListApi: '대화 목록 가져오기', - conversationsListApiTip: '현재 사용자의 세션 목록을 가져옵니다. 기본적으로 최근 20개의 세션이 반환됩니다.', + conversationsListApiTip: '현재 사용자의 세션 목록을 가져옵니다. 기본적으로 최근 20 개의 세션이 반환됩니다.', conversationsListFirstIdTip: '현재 페이지의 마지막 레코드의 ID, 기본값은 없음입니다.', conversationsListLimitTip: '한 번에 반환되는 채팅 수', conversationRenamingApi: '대화 이름 변경', diff --git a/web/i18n/ko-KR/app-debug.ts b/web/i18n/ko-KR/app-debug.ts index bafe0bf8d8..3c5a3f4b1f 100644 --- a/web/i18n/ko-KR/app-debug.ts +++ b/web/i18n/ko-KR/app-debug.ts @@ -39,8 +39,8 @@ const translation = { settingBtn: '설정으로 이동', }, trailUseGPT4Info: { - title: '현재 gpt-4는 지원되지 않습니다', - description: 'gpt-4를 사용하려면 API 키를 설정해야 합니다.', + title: '현재 gpt-4 는 지원되지 않습니다', + description: 'gpt-4 를 사용하려면 API 키를 설정해야 합니다.', }, feature: { groupChat: { @@ -52,12 +52,12 @@ const translation = { }, conversationOpener: { title: '대화 시작', - description: '채팅 앱에서 AI가 사용자에게 처음으로 적극적으로 말을 건다면 일반적으로 환영 메시지로 사용됩니다.', + description: '채팅 앱에서 AI 가 사용자에게 처음으로 적극적으로 말을 건다면 일반적으로 환영 메시지로 사용됩니다.', }, suggestedQuestionsAfterAnswer: { title: '팔로우업', description: '다음 질문 제안을 설정하면 사용자에게 더 나은 채팅이 제공됩니다.', - resDes: '사용자의 다음 질문에 대한 3가지 제안.', + resDes: '사용자의 다음 질문에 대한 3 가지 제안.', tryToAsk: '질문해보세요', }, moreLikeThis: { @@ -162,7 +162,7 @@ const translation = { }, moderation: { title: '콘텐츠 모더레이션', - description: '모더레이션 API를 사용하거나 기밀 단어 목록을 유지함으로써 모델 출력을 안전하게 합니다.', + description: '모더레이션 API 를 사용하거나 기밀 단어 목록을 유지함으로써 모델 출력을 안전하게 합니다.', allEnabled: '입력/출력 콘텐츠가 모두 활성화되어 있습니다', inputEnabled: '입력 콘텐츠가 활성화되어 있습니다', outputEnabled: '출력 콘텐츠가 활성화되어 있습니다', @@ -178,7 +178,7 @@ const translation = { keywords: '키워드', }, keywords: { - tip: '한 줄에 하나씩, 줄 바꿈으로 입력하세요. 한 줄 당 최대 100자.', + tip: '한 줄에 하나씩, 줄 바꿈으로 입력하세요. 한 줄 당 최대 100 자.', placeholder: '한 줄씩 입력하세요', line: '줄', }, @@ -188,7 +188,7 @@ const translation = { preset: '프리셋 응답', placeholder: '프리셋 응답 내용을 입력하세요', condition: '최소한 하나의 입력 및 출력 콘텐츠를 모더레이션합니다', - fromApi: '프리셋 응답은 API에서 반환됩니다', + fromApi: '프리셋 응답은 API 에서 반환됩니다', errorMessage: '프리셋 응답은 비워둘 수 없습니다', supportMarkdown: '마크다운이 지원됩니다', }, @@ -201,10 +201,10 @@ const translation = { }, automatic: { title: '자동 어플리케이션 오케스트레이션', - description: '시나리오를 설명하세요. Dify가 어플리케이션을 자동으로 오케스트레이션 합니다.', + description: '시나리오를 설명하세요. Dify 가 어플리케이션을 자동으로 오케스트레이션 합니다.', intendedAudience: '누가 대상이 되는지 설명하세요.', intendedAudiencePlaceHolder: '예: 학생', - solveProblem: '어떤 문제를 AI가 해결할 것으로 예상하나요?', + solveProblem: '어떤 문제를 AI 가 해결할 것으로 예상하나요?', solveProblemPlaceHolder: '예: 학업 성적 평가', generate: '생성', audiencesRequired: '대상이 필요합니다', @@ -231,7 +231,7 @@ const translation = { }, chatSubTitle: '단계', completionSubTitle: '접두사 프롬프트', - promptTip: '프롬프트는 AI의 응답을 지시하고 제한하여 유도합니다. {{input}}과 같은 변수를 삽입하세요. 이 프롬프트는 사용자에게 표시되지 않습니다.', + promptTip: '프롬프트는 AI 의 응답을 지시하고 제한하여 유도합니다. {{input}}과 같은 변수를 삽입하세요. 이 프롬프트는 사용자에게 표시되지 않습니다.', formattingChangedTitle: '포맷이 변경되었습니다', formattingChangedText: '포맷을 변경하면 디버그 영역이 재설정됩니다. 계속하시겠습니까?', variableTitle: '변수', @@ -249,7 +249,7 @@ const translation = { }, varKeyError: { canNoBeEmpty: '{{key}}가 필요합니다', - tooLong: '{{key}}가 너무 깁니다. 30자를 넘을 수 없습니다', + tooLong: '{{key}}가 너무 깁니다. 30 자를 넘을 수 없습니다', notValid: '{{key}}가 유효하지 않습니다. 문자, 숫자, 밑줄만 포함할 수 있습니다', notStartWithNumber: '{{key}}는 숫자로 시작할 수 없습니다', keyAlreadyExists: '{{key}}는 이미 존재합니다', @@ -279,6 +279,7 @@ const translation = { 'labelName': '레이블명', 'inputPlaceholder': '입력하세요', 'required': '필수', + 'hide': '숨기기', 'errorMsg': { varNameRequired: '변수명은 필수입니다', labelNameRequired: '레이블명은 필수입니다', @@ -294,9 +295,9 @@ const translation = { visionSettings: { title: '비전 설정', resolution: '해상도', - resolutionTooltip: `저해상도는 모델에게 512 x 512 해상도의 저해상도 이미지를 제공하여 65 토큰의 예산으로 이미지를 표현합니다. 이로 인해 API는 더 빠른 응답을 제공하며 높은 세부 정보가 필요한 경우 토큰 소모를 늘립니다. + resolutionTooltip: `저해상도는 모델에게 512 x 512 해상도의 저해상도 이미지를 제공하여 65 토큰의 예산으로 이미지를 표현합니다. 이로 인해 API 는 더 빠른 응답을 제공하며 높은 세부 정보가 필요한 경우 토큰 소모를 늘립니다. \n - 고해상도는 먼저 모델에게 저해상도 이미지를 보여주고, 그 후 입력 이미지 크기에 따라 512px의 정사각형 세부 사진을 만듭니다. 각 세부 사진에 대해 129 토큰의 예산을 사용합니다.`, + 고해상도는 먼저 모델에게 저해상도 이미지를 보여주고, 그 후 입력 이미지 크기에 따라 512px 의 정사각형 세부 사진을 만듭니다. 각 세부 사진에 대해 129 토큰의 예산을 사용합니다.`, high: '고', low: '저', uploadMethod: '업로드 방식', @@ -363,12 +364,12 @@ const translation = { }, retrieveMultiWay: { title: '멀티패스 리트리벌', - description: '사용자 의도에 따라 모든 지식을 쿼리하고, 관련 텍스트를 여러 소스에서 가져와 다시 순위를 매긴 후 사용자 쿼리에 가장 적합한 결과를 선택합니다. 재순위 모델 API의 구성이 필요합니다.', + description: '사용자 의도에 따라 모든 지식을 쿼리하고, 관련 텍스트를 여러 소스에서 가져와 다시 순위를 매긴 후 사용자 쿼리에 가장 적합한 결과를 선택합니다. 재순위 모델 API 의 구성이 필요합니다.', }, rerankModelRequired: '재순위 모델이 필요합니다', params: '매개변수', top_k: '상위 K', - top_kTip: '사용자 질문에 가장 유사한 청크를 필터링하는 데 사용됩니다. 시스템은 선택한 모델의 max_tokens에 따라 동적으로 상위 K 값을 조정합니다.', + top_kTip: '사용자 질문에 가장 유사한 청크를 필터링하는 데 사용됩니다. 시스템은 선택한 모델의 max_tokens 에 따라 동적으로 상위 K 값을 조정합니다.', score_threshold: '점수 임계값', score_thresholdTip: '청크 필터링의 유사성 임계값을 설정하는 데 사용됩니다.', retrieveChangeTip: '인덱스 모드 및 리트리벌 모드를 변경하면 이 지식과 관련된 애플리케이션에 영향을 줄 수 있습니다.', @@ -409,7 +410,7 @@ const translation = { promptPlaceholder: '여기에 프롬프트를 입력하세요', tools: { name: '도구', - description: '도구를 사용하여 인터넷 검색이나 과학적 계산 등 LLM의 기능을 확장할 수 있습니다', + description: '도구를 사용하여 인터넷 검색이나 과학적 계산 등 LLM 의 기능을 확장할 수 있습니다', enabled: '활성화됨', }, }, diff --git a/web/i18n/ko-KR/app-log.ts b/web/i18n/ko-KR/app-log.ts index 4017692f5c..6ed33b3c1c 100644 --- a/web/i18n/ko-KR/app-log.ts +++ b/web/i18n/ko-KR/app-log.ts @@ -49,7 +49,7 @@ const translation = { dislike: '좋아요 취소', addAnnotation: '향상 추가', editAnnotation: '향상 편집', - annotationPlaceholder: 'AI가 응답할 것으로 예상하는 답변을 입력하여 향후 모델 세부 조정 및 텍스트 생성 품질 지속적 향상을 위해 개선할 수 있습니다.', + annotationPlaceholder: 'AI 가 응답할 것으로 예상하는 답변을 입력하여 향후 모델 세부 조정 및 텍스트 생성 품질 지속적 향상을 위해 개선할 수 있습니다.', }, variables: '변수', uploadImages: '업로드된 이미지', @@ -58,10 +58,10 @@ const translation = { filter: { period: { today: '오늘', - last7days: '지난 7일', - last4weeks: '지난 4주', - last3months: '지난 3개월', - last12months: '지난 12개월', + last7days: '지난 7 일', + last4weeks: '지난 4 주', + last3months: '지난 3 개월', + last12months: '지난 12 개월', monthToDate: '월 초부터 오늘까지', quarterToDate: '분기 초부터 오늘까지', yearToDate: '연 초부터 오늘까지', @@ -77,7 +77,7 @@ const translation = { ascending: '오름차순', }, workflowTitle: '워크플로우 로그', - workflowSubtitle: '이 로그는 Automate의 작업을 기록했습니다.', + workflowSubtitle: '이 로그는 Automate 의 작업을 기록했습니다.', runDetail: { title: '대화 로그', workflowTitle: '로그 세부 정보', diff --git a/web/i18n/ko-KR/app-overview.ts b/web/i18n/ko-KR/app-overview.ts index be6e5117cf..136e472a24 100644 --- a/web/i18n/ko-KR/app-overview.ts +++ b/web/i18n/ko-KR/app-overview.ts @@ -25,7 +25,7 @@ const translation = { callTimes: '요청 횟수', usedToken: '사용된 토큰', setAPIBtn: '모델 제공자 설정으로 이동', - tryCloud: '또는 Dify의 클라우드 버전을 무료로 체험해보세요', + tryCloud: '또는 Dify 의 클라우드 버전을 무료로 체험해보세요', }, overview: { title: '개요', @@ -34,7 +34,7 @@ const translation = { accessibleAddress: '공개 URL', preview: '미리보기', regenerate: '재생성', - regenerateNotice: '공개 URL을 재생성하시겠습니까?', + regenerateNotice: '공개 URL 을 재생성하시겠습니까?', preUseReminder: '계속하기 전에 웹앱을 활성화하세요.', settings: { entry: '설정', @@ -48,21 +48,21 @@ const translation = { title: '워크플로 단계', show: '표시', hide: '숨기기', - showDesc: 'WebApp에서 워크플로 세부 정보 표시 또는 숨기기', + showDesc: 'WebApp 에서 워크플로 세부 정보 표시 또는 숨기기', subTitle: '워크플로우 세부 정보', }, chatColorTheme: '챗봇 색상 테마', chatColorThemeDesc: '챗봇의 색상 테마를 설정하세요', chatColorThemeInverted: '반전', - invalidHexMessage: '잘못된 16진수 값', - invalidPrivacyPolicy: '유효하지 않은 개인정보처리방침 링크입니다. http 또는 https로 시작하는 유효한 링크를 사용해 주세요', + invalidHexMessage: '잘못된 16 진수 값', + invalidPrivacyPolicy: '유효하지 않은 개인정보처리방침 링크입니다. http 또는 https 로 시작하는 유효한 링크를 사용해 주세요', more: { entry: '추가 설정 보기', copyright: '저작권', copyRightPlaceholder: '저작권자 또는 조직 이름을 입력하세요', privacyPolicy: '개인정보 처리방침', privacyPolicyPlaceholder: '개인정보 처리방침 링크를 입력하세요', - privacyPolicyTip: '방문자가 애플리케이션이 수집하는 데이터를 이해하고, Dify의 <privacyPolicyLink>개인정보 처리방침</privacyPolicyLink>을 참조할 수 있도록 합니다.', + privacyPolicyTip: '방문자가 애플리케이션이 수집하는 데이터를 이해하고, Dify 의 <privacyPolicyLink>개인정보 처리방침</privacyPolicyLink>을 참조할 수 있도록 합니다.', customDisclaimer: '사용자 지정 면책 조항', customDisclaimerPlaceholder: '사용자 지정 면책 조항 텍스트를 입력합니다.', customDisclaimerTip: '사용자 지정 고지 사항 텍스트는 클라이언트 쪽에 표시되어 응용 프로그램에 대한 추가 정보를 제공합니다', @@ -72,8 +72,8 @@ const translation = { sso: { label: 'SSO 인증', title: '웹앱 SSO', - tooltip: '관리자에게 문의하여 WebApp SSO를 사용하도록 설정합니다.', - description: '모든 사용자는 WebApp을 사용하기 전에 SSO로 로그인해야 합니다.', + tooltip: '관리자에게 문의하여 web app SSO 를 사용하도록 설정합니다.', + description: '모든 사용자는 WebApp 을 사용하기 전에 SSO 로 로그인해야 합니다.', }, modalTip: '클라이언트 쪽 웹앱 설정.', }, @@ -81,8 +81,8 @@ const translation = { entry: '임베드', title: '웹사이트에 임베드하기', explanation: '챗봇 앱을 웹사이트에 임베드하는 방법을 선택하세요.', - iframe: '웹사이트의 원하는 위치에 챗봇 앱을 추가하려면 이 iframe을 HTML 코드에 추가하세요.', - scripts: '웹사이트의 우측 하단에 챗봇 앱을 추가하려면 이 코드를 HTML에 추가하세요.', + iframe: '웹사이트의 원하는 위치에 챗봇 앱을 추가하려면 이 iframe 을 HTML 코드에 추가하세요.', + scripts: '웹사이트의 우측 하단에 챗봇 앱을 추가하려면 이 코드를 HTML 에 추가하세요.', chromePlugin: 'Dify Chatbot Chrome 확장 프로그램 설치', copied: '복사되었습니다', copy: '복사', @@ -98,18 +98,18 @@ const translation = { title: 'AI 웹앱 사용자화', explanation: '시나리오와 스타일 요구에 따라 웹앱의 프론트엔드를 사용자화할 수 있습니다.', way1: { - name: '클라이언트 코드를 포크하여 수정하고 Vercel에 배포하기 (권장)', + name: '클라이언트 코드를 포크하여 수정하고 Vercel 에 배포하기 (권장)', step1: '클라이언트 코드를 포크하여 수정합니다', step1Tip: '여기를 클릭하여 소스 코드를 GitHub 계정에 포크하고 코드를 수정하세요', step1Operation: 'Dify-WebClient', - step2: 'Vercel에 배포합니다', - step2Tip: '여기를 클릭하여 리포지토리를 Vercel에 임포트하고 배포하세요', + step2: 'Vercel 에 배포합니다', + step2Tip: '여기를 클릭하여 리포지토리를 Vercel 에 임포트하고 배포하세요', step2Operation: '리포지토리 임포트', step3: '환경 변수를 설정합니다', - step3Tip: 'Vercel에 다음 환경 변수를 추가하세요', + step3Tip: 'Vercel 에 다음 환경 변수를 추가하세요', }, way2: { - name: '클라이언트 측 코드를 작성하여 API를 호출하고 서버에 배포합니다', + name: '클라이언트 측 코드를 작성하여 API 를 호출하고 서버에 배포합니다', operation: '문서', }, }, @@ -140,7 +140,7 @@ const translation = { }, activeUsers: { title: '활성 사용자 수', - explanation: 'AI와의 Q&A에 참여하는 고유 사용자 수; 엔지니어링/디버깅 목적의 프롬프트는 제외됩니다.', + explanation: 'AI 와의 Q&A 에 참여하는 고유 사용자 수; 엔지니어링/디버깅 목적의 프롬프트는 제외됩니다.', }, tokenUsage: { title: '토큰 사용량', @@ -149,7 +149,7 @@ const translation = { }, avgSessionInteractions: { title: '평균 세션 상호작용 수', - explanation: '사용자와 AI의 연속적인 커뮤니케이션 수; 대화형 애플리케이션을 위한 것입니다.', + explanation: '사용자와 AI 의 연속적인 커뮤니케이션 수; 대화형 애플리케이션을 위한 것입니다.', }, avgUserInteractions: { title: '평균 사용자 상호작용 수', @@ -157,15 +157,15 @@ const translation = { }, userSatisfactionRate: { title: '사용자 만족도율', - explanation: '1,000개의 메시지 당 "좋아요" 수입니다. 이는 사용자가 매우 만족한 응답의 비율을 나타냅니다.', + explanation: '1,000 개의 메시지 당 "좋아요" 수입니다. 이는 사용자가 매우 만족한 응답의 비율을 나타냅니다.', }, avgResponseTime: { title: '평균 응답 시간', - explanation: 'AI가 처리/응답하는 시간(밀리초); 텍스트 기반 애플리케이션을 위한 것입니다.', + explanation: 'AI 가 처리/응답하는 시간 (밀리초); 텍스트 기반 애플리케이션을 위한 것입니다.', }, tps: { title: '토큰 출력 속도', - explanation: 'LLM의 성능을 측정합니다. 요청 시작부터 출력 완료까지의 LLM의 토큰 출력 속도를 계산합니다.', + explanation: 'LLM 의 성능을 측정합니다. 요청 시작부터 출력 완료까지의 LLM 의 토큰 출력 속도를 계산합니다.', }, }, } diff --git a/web/i18n/ko-KR/app.ts b/web/i18n/ko-KR/app.ts index 89cd274647..7227fd3171 100644 --- a/web/i18n/ko-KR/app.ts +++ b/web/i18n/ko-KR/app.ts @@ -34,7 +34,7 @@ const translation = { workflowWarning: '현재 베타 버전입니다.', chatbotType: '챗봇 오케스트레이션 방식', basic: '기본', - basicTip: '초보자용. 나중에 Chatflow로 전환할 수 있습니다.', + basicTip: '초보자용. 나중에 Chatflow 로 전환할 수 있습니다.', basicFor: '초보자용', basicDescription: '기본 오케스트레이션은 내장된 프롬프트를 수정할 수 없고 간단한 설정을 사용하여 챗봇 앱을 오케스트레이션합니다. 초보자용입니다.', advanced: 'Chatflow', @@ -69,10 +69,10 @@ const translation = { appCreateDSLWarning: '주의: DSL 버전 차이는 특정 기능에 영향을 미칠 수 있습니다.', appCreateDSLErrorPart1: 'DSL 버전에서 상당한 차이가 감지되었습니다. 강제로 가져오면 응용 프로그램이 오작동할 수 있습니다.', chooseAppType: '앱 유형 선택', - forBeginners: '초보자용', + forBeginners: '초보자용 기본 앱 유형', forAdvanced: '고급 사용자용', chatbotShortDescription: '간단한 설정으로 LLM 기반 챗봇', - workflowUserDescription: '자동화 및 배치 처리와 같은 단일 라운드 작업을 위한 워크플로우 오케스트레이션.', + workflowUserDescription: '드래그 앤 드롭으로 자율 AI 워크플로우를 시각적으로 구축', noTemplateFoundTip: '다른 키워드를 사용하여 검색해 보십시오.', noIdeaTip: '아이디어가 없으신가요? 템플릿을 확인해 보세요', optional: '선택적', @@ -81,14 +81,14 @@ const translation = { learnMore: '더 알아보세요', foundResults: '{{개수}} 결과', agentShortDescription: '추론 및 자율적인 도구 사용 기능이 있는 지능형 에이전트', - advancedShortDescription: '메모리를 사용한 복잡한 다중 턴 대화를 위한 워크플로우', + advancedShortDescription: '다중 대화를 위해 강화된 워크플로우', noAppsFound: '앱을 찾을 수 없습니다.', foundResult: '{{개수}} 결과', completionUserDescription: '간단한 구성으로 텍스트 생성 작업을 위한 AI 도우미를 빠르게 구축합니다.', - chatbotUserDescription: '간단한 구성으로 LLM 기반 챗봇을 빠르게 구축할 수 있습니다. 나중에 Chatflow로 전환할 수 있습니다.', - workflowShortDescription: '단일 턴 자동화 작업을 위한 오케스트레이션', + chatbotUserDescription: '간단한 구성으로 LLM 기반 챗봇을 빠르게 구축할 수 있습니다. 나중에 Chatflow 로 전환할 수 있습니다.', + workflowShortDescription: '지능형 자동화를 위한 에이전트 플로우', agentUserDescription: '작업 목표를 달성하기 위해 반복적인 추론과 자율적인 도구를 사용할 수 있는 지능형 에이전트입니다.', - advancedUserDescription: '메모리 기능이 있는 다라운드의 복잡한 대화 작업을 위한 워크플로우 조정.', + advancedUserDescription: '메모리 기능과 챗봇 인터페이스를 갖춘 워크플로우', }, editApp: '정보 편집하기', editAppTitle: '앱 정보 편집하기', @@ -117,7 +117,7 @@ const translation = { }, tracing: { title: '앱 성능 추적', - description: '제3자 LLMOps 제공업체 구성 및 앱 성능 추적.', + description: '제 3 자 LLMOps 제공업체 구성 및 앱 성능 추적.', config: '구성', collapse: '접기', expand: '펼치기', @@ -125,7 +125,7 @@ const translation = { disabled: '비활성화됨', disabledTip: '먼저 제공업체를 구성해 주세요', enabled: '서비스 중', - tracingDescription: 'LLM 호출, 컨텍스트, 프롬프트, HTTP 요청 등 앱 실행의 전체 컨텍스트를 제3자 추적 플랫폼에 캡처합니다.', + tracingDescription: 'LLM 호출, 컨텍스트, 프롬프트, HTTP 요청 등 앱 실행의 전체 컨텍스트를 제 3 자 추적 플랫폼에 캡처합니다.', configProviderTitle: { configured: '구성됨', notConfigured: '추적을 활성화하려면 제공업체를 구성하세요', @@ -153,23 +153,27 @@ const translation = { view: '보기', opik: { title: '오픽', - description: 'Opik은 LLM 애플리케이션을 평가, 테스트 및 모니터링하기 위한 오픈 소스 플랫폼입니다.', + description: 'Opik 은 LLM 애플리케이션을 평가, 테스트 및 모니터링하기 위한 오픈 소스 플랫폼입니다.', + }, + weave: { + title: '직조하다', + description: 'Weave 는 LLM 애플리케이션을 평가하고 테스트하며 모니터링하기 위한 오픈 소스 플랫폼입니다.', }, }, answerIcon: { - description: 'WebApp 아이콘을 사용하여 공유 응용 프로그램에서 바꿀🤖지 여부', - title: 'WebApp 아이콘을 사용하여 🤖', - descriptionInExplore: 'Explore에서 WebApp 아이콘을 사용하여 바꿀🤖지 여부', + description: 'web app 아이콘을 사용하여 공유 응용 프로그램에서 바꿀🤖지 여부', + title: 'web app 아이콘을 사용하여 🤖', + descriptionInExplore: 'Explore 에서 web app 아이콘을 사용하여 바꿀🤖지 여부', }, - importFromDSL: 'DSL에서 가져오기', + importFromDSL: 'DSL 에서 가져오기', importFromDSLFile: 'DSL 파일에서', - importFromDSLUrl: 'URL에서', + importFromDSLUrl: 'URL 에서', importFromDSLUrlPlaceholder: '여기에 DSL 링크 붙여 넣기', mermaid: { handDrawn: '손으로 그린', classic: '고전', }, - openInExplore: 'Explore에서 열기', + openInExplore: 'Explore 에서 열기', newAppFromTemplate: { sidebar: { Agent: '대리인', @@ -190,6 +194,54 @@ const translation = { label: '앱', placeholder: '앱 선택...', }, + structOutput: { + required: '필수', + LLMResponse: 'LLM 응답', + modelNotSupported: '모델이 지원되지 않습니다.', + notConfiguredTip: '구성이 아직 설정되지 않았습니다.', + structured: '구조화된', + configure: '설정하다', + moreFillTip: '최대 10 단계 중첩을 표시합니다.', + modelNotSupportedTip: '현재 모델은 이 기능을 지원하지 않으며 자동으로 프롬프트 주입으로 다운그레이드됩니다.', + structuredTip: '구조화된 출력은 모델이 제공한 JSON 스키마를 항상 준수하는 응답을 생성하도록 보장하는 기능입니다.', + }, + accessItemsDescription: { + anyone: '누구나 웹 앱에 접근할 수 있습니다.', + specific: '특정 그룹이나 회원만 웹 앱에 접근할 수 있습니다.', + organization: '조직 내 모든 사람이 웹 애플리케이션에 접근할 수 있습니다.', + external: '인증된 외부 사용자만 웹 애플리케이션에 접근할 수 있습니다.', + }, + accessControlDialog: { + accessItems: { + anyone: '링크가 있는 누구나', + specific: '특정 그룹 또는 구성원', + organization: '기업 내의 회원만', + external: '인증된 외부 사용자', + }, + operateGroupAndMember: { + searchPlaceholder: '그룹 및 구성원 검색', + allMembers: '모든 멤버들', + expand: '확장하다', + noResult: '결과 없음', + }, + title: '웹 애플리케이션 접근 제어', + accessLabel: '누가 접근할 수 있습니까?', + groups_one: '{{count}} 그룹', + groups_other: '{{count}} 그룹', + members_one: '{{count}} 회원', + members_other: '{{count}} 회원', + noGroupsOrMembers: '선택된 그룹 또는 멤버가 없습니다.', + webAppSSONotEnabledTip: '웹 앱 인증 방법을 구성하려면 엔터프라이즈 관리자인에게 문의하십시오.', + updateSuccess: '업데이트가 성공적으로 완료되었습니다.', + description: '웹 앱 접근 권한 설정', + }, + publishApp: { + title: '누가 웹 애플리케이션에 접근할 수 있나요?', + notSet: '설정되지 않음', + notSetDesc: '현재 아무도 웹 앱에 접근할 수 없습니다. 권한을 설정해 주세요.', + }, + accessControl: '웹 애플리케이션 접근 제어', + noAccessPermission: '웹 앱에 대한 접근 권한이 없습니다.', } export default translation diff --git a/web/i18n/ko-KR/billing.ts b/web/i18n/ko-KR/billing.ts index 94d557fd4b..87ccf27fe0 100644 --- a/web/i18n/ko-KR/billing.ts +++ b/web/i18n/ko-KR/billing.ts @@ -9,7 +9,7 @@ const translation = { buyPermissionDeniedTip: '구독하려면 엔터프라이즈 관리자에게 문의하세요', plansCommon: { title: '당신에게 맞는 요금제를 선택하세요', - yearlyTip: '연간 구독 시 2개월 무료!', + yearlyTip: '연간 구독 시 2 개월 무료!', mostPopular: '가장 인기 있는', planRange: { monthly: '월간', @@ -30,8 +30,8 @@ const translation = { teamMembers: '팀 멤버', buildApps: '앱 만들기', vectorSpace: '벡터 공간', - vectorSpaceBillingTooltip: '1MB당 약 120만 글자의 벡터화된 데이터를 저장할 수 있습니다 (OpenAI Embeddings을 기반으로 추정되며 모델에 따라 다릅니다).', - vectorSpaceTooltip: '벡터 공간은 LLM이 데이터를 이해하는 데 필요한 장기 기억 시스템입니다.', + vectorSpaceBillingTooltip: '1MB 당 약 120 만 글자의 벡터화된 데이터를 저장할 수 있습니다 (OpenAI Embeddings 을 기반으로 추정되며 모델에 따라 다릅니다).', + vectorSpaceTooltip: '벡터 공간은 LLM 이 데이터를 이해하는 데 필요한 장기 기억 시스템입니다.', documentProcessingPriority: '문서 처리 우선순위', documentProcessingPriorityTip: '더 높은 문서 처리 우선순위를 원하시면 요금제를 업그레이드하세요.', documentProcessingPriorityUpgrade: '더 높은 정확성과 빠른 속도로 데이터를 처리합니다.', @@ -68,36 +68,104 @@ const translation = { messageRequest: { title: '메시지 크레딧', tooltip: 'GPT 제외 다양한 요금제에서의 메시지 호출 쿼터 (gpt4 제외). 제한을 초과하는 메시지는 OpenAI API 키를 사용합니다.', + titlePerMonth: '{{count,number}} 메시지/월', }, annotatedResponse: { title: '주석 응답 쿼터', tooltip: '수동으로 편집 및 응답 주석 달기로 앱의 사용자 정의 가능한 고품질 질의응답 기능을 제공합니다 (채팅 앱에만 해당).', }, - ragAPIRequestTooltip: 'Dify의 지식베이스 처리 기능을 호출하는 API 호출 수를 나타냅니다.', + ragAPIRequestTooltip: 'Dify 의 지식베이스 처리 기능을 호출하는 API 호출 수를 나타냅니다.', receiptInfo: '팀 소유자 및 팀 관리자만 구독 및 청구 정보를 볼 수 있습니다', annotationQuota: 'Annotation Quota(주석 할당량)', documentsUploadQuota: '문서 업로드 할당량', + freeTrialTipPrefix: '가입하고 받으세요', + comparePlanAndFeatures: '계획 및 기능 비교', + documents: '{{count,number}} 지식 문서', + apiRateLimit: 'API 요금 한도', + cloud: '클라우드 서비스', + unlimitedApiRate: 'API 호출 속도 제한 없음', + freeTrialTip: '200 회의 OpenAI 호출에 대한 무료 체험.', + annualBilling: '연간 청구', + getStarted: '시작하기', + apiRateLimitUnit: '{{count,number}}/일', + freeTrialTipSuffix: '신용카드 없음', + teamWorkspace: '{{count,number}} 팀 작업 공간', + self: '자체 호스팅', + teamMember_other: '{{count,number}} 팀원', + teamMember_one: '{{count,number}} 팀원', + priceTip: '작업 공간당/', + apiRateLimitTooltip: 'Dify API 를 통한 모든 요청에는 API 요금 한도가 적용되며, 여기에는 텍스트 생성, 채팅 대화, 워크플로 실행 및 문서 처리가 포함됩니다.', + documentsRequestQuota: '{{count,number}}/분 지식 요청 비율 제한', + documentsTooltip: '지식 데이터 소스에서 가져올 수 있는 문서 수에 대한 쿼터.', + documentsRequestQuotaTooltip: '지식 기반 내에서 작업 공간이 분당 수행할 수 있는 총 작업 수를 지정합니다. 여기에는 데이터 세트 생성, 삭제, 업데이트, 문서 업로드, 수정, 보관 및 지식 기반 쿼리가 포함됩니다. 이 지표는 지식 기반 요청의 성능을 평가하는 데 사용됩니다. 예를 들어, 샌드박스 사용자가 1 분 이내에 10 회의 연속 히트 테스트를 수행하면, 해당 작업 공간은 다음 1 분 동안 데이터 세트 생성, 삭제, 업데이트 및 문서 업로드 또는 수정과 같은 작업을 수행하는 것이 일시적으로 제한됩니다.', }, plans: { sandbox: { name: '샌드박스', - description: 'GPT 무료 체험 200회', + description: 'GPT 무료 체험 200 회', includesTitle: '포함된 항목:', + for: '핵심 기능 무료 체험', }, professional: { name: '프로페셔널', description: '개인 및 소규모 팀을 위해 더 많은 파워를 저렴한 가격에 제공합니다.', includesTitle: '무료 플랜에 추가로 포함된 항목:', + for: '독립 개발자/소규모 팀을 위한', }, team: { name: '팀', description: '제한 없이 협업하고 최고의 성능을 누리세요.', includesTitle: '프로페셔널 플랜에 추가로 포함된 항목:', + for: '중간 규모 팀을 위한', }, enterprise: { name: '엔터프라이즈', description: '대규모 미션 크리티컬 시스템을 위한 완전한 기능과 지원을 제공합니다.', includesTitle: '팀 플랜에 추가로 포함된 항목:', + features: { + 2: '독점 기업 기능', + 1: '상업적 라이선스 승인', + 3: '다중 작업 공간 및 기업 관리', + 4: 'SSO', + 5: 'Dify 파트너에 의해 협상된 SLA', + 6: '고급 보안 및 제어', + 0: '기업급 확장 가능한 배포 솔루션', + 7: '디피 공식 업데이트 및 유지 관리', + 8: '전문 기술 지원', + }, + price: '맞춤형', + btnText: '판매 문의하기', + for: '대규모 팀을 위해', + priceTip: '연간 청구 전용', + }, + community: { + features: { + 0: '모든 핵심 기능이 공개 저장소에 릴리스됨', + 2: 'Dify 오픈 소스 라이선스를 준수합니다.', + 1: '단일 작업 공간', + }, + btnText: '커뮤니티 시작하기', + description: '개인 사용자, 소규모 팀 또는 비상업적 프로젝트를 위한', + name: '커뮤니티', + price: '무료', + includesTitle: '무료 기능:', + for: '개인 사용자, 소규모 팀 또는 비상업적 프로젝트를 위한', + }, + premium: { + features: { + 1: '단일 작업 공간', + 2: '웹앱 로고 및 브랜딩 맞춤화', + 3: '우선 이메일 및 채팅 지원', + 0: '다양한 클라우드 제공업체에 의한 자율 관리 신뢰성', + }, + btnText: '프리미엄 받기', + priceTip: '클라우드 마켓플레이스를 기반으로', + name: '프리미엄', + description: '중규모 조직 및 팀을 위한', + comingSoon: '마이크로소프트 애저 및 구글 클라우드 지원 곧 제공됩니다.', + price: '확장 가능', + for: '중규모 조직 및 팀을 위한', + includesTitle: '커뮤니티의 모든 것, 여기에 추가로:', }, }, vectorSpace: { @@ -107,12 +175,26 @@ const translation = { apps: { fullTipLine1: '더 많은 앱을 생성하려면,', fullTipLine2: '요금제를 업그레이드하세요.', + contactUs: '문의하기', + fullTip1: '업그레이드하여 더 많은 앱을 만들기', + fullTip2: '계획 한도에 도달했습니다.', + fullTip2des: '비활성 애플리케이션을 정리하여 사용량을 줄이거나 저희에게 문의하는 것이 좋습니다.', + fullTip1des: '이 계획에서 앱을 구축할 수 있는 한계에 도달했습니다.', }, annotatedResponse: { fullTipLine1: '더 많은 대화를 주석 처리하려면,', fullTipLine2: '요금제를 업그레이드하세요.', quotaTitle: '주석 응답 쿼터', }, + usagePage: { + vectorSpace: '지식 데이터 저장소', + annotationQuota: '주석 할당량', + teamMembers: '팀원들', + buildApps: '앱 만들기', + documentsUploadQuota: '문서 업로드 한도', + vectorSpaceTooltip: '고품질 색인 모드를 사용하는 문서는 지식 데이터 저장소 자원을 소모합니다. 지식 데이터 저장소가 한도에 도달하면 새 문서를 업로드할 수 없습니다.', + }, + teamMembers: '팀원들', } export default translation diff --git a/web/i18n/ko-KR/common.ts b/web/i18n/ko-KR/common.ts index 8068a76d8e..c0d64e71e0 100644 --- a/web/i18n/ko-KR/common.ts +++ b/web/i18n/ko-KR/common.ts @@ -54,6 +54,10 @@ const translation = { copied: '복사', viewDetails: '세부 정보보기', in: '안으로', + downloadFailed: '다운로드 실패했습니다. 나중에 다시 시도하십시오.', + format: '형식', + more: '더 많은', + downloadSuccess: '다운로드 완료.', }, placeholder: { input: '입력해주세요', @@ -99,20 +103,20 @@ const translation = { model: { params: { temperature: '온도', - temperatureTip: '랜덤성을 제어합니다. 온도를 낮추면 더 랜덤한 결과물을 얻을 수 있습니다. 온도가 0에 가까워질수록 모델은 결정적이고 반복적으로 작동합니다.', - top_p: '상위P', - top_pTip: '뉴클리어스 샘플링에 의한 다양성 제어: 0.5는 모든 확률 가중 옵션의 절반을 고려함을 의미합니다.', + temperatureTip: '랜덤성을 제어합니다. 온도를 낮추면 더 랜덤한 결과물을 얻을 수 있습니다. 온도가 0 에 가까워질수록 모델은 결정적이고 반복적으로 작동합니다.', + top_p: '상위 P', + top_pTip: '뉴클리어스 샘플링에 의한 다양성 제어: 0.5 는 모든 확률 가중 옵션의 절반을 고려함을 의미합니다.', presence_penalty: '존재 페널티', presence_penaltyTip: '이전 텍스트에서 토큰이 나타나는지 여부에 따라 새로운 토큰에 얼마나 많은 페널티를 부여할지 제어합니다. 모델이 새로운 주제에 대해 말할 가능성이 높아집니다.', frequency_penalty: '빈도 페널티', frequency_penaltyTip: '이전 텍스트 내 토큰의 기존 빈도에 따라 새로운 토큰에 얼마나 많은 페널티를 부여할지 제어합니다. 모델이 같은 문구를 글자 그대로 반복할 가능성이 줄어듭니다.', max_tokens: '최대 토큰', max_tokensTip: - '응답의 최대 길이를 토큰 단위로 제한하는 데 사용됩니다. 큰 값은 프롬프트, 채팅 로그 및 남은 공간에 대한 제한을 가질 수 있습니다. 2/3 이하로 설정하는 것이 좋습니다. gpt-4-1106-preview, gpt-4-vision-preview의 최대 토큰 (입력 128k 출력 4k)보다 작게 설정하는 것이 좋습니다.', + '응답의 최대 길이를 토큰 단위로 제한하는 데 사용됩니다. 큰 값은 프롬프트, 채팅 로그 및 남은 공간에 대한 제한을 가질 수 있습니다. 2/3 이하로 설정하는 것이 좋습니다. gpt-4-1106-preview, gpt-4-vision-preview 의 최대 토큰 (입력 128k 출력 4k) 보다 작게 설정하는 것이 좋습니다.', maxTokenSettingTip: '최대 토큰 설정이 높아서 프롬프트, 쿼리 및 데이터 공간에 제한이 생길 수 있습니다. 현재 모델의 최대 토큰의 80% 이하로 설정해주세요.', - setToCurrentModelMaxTokenTip: '최대 토큰이 현재 모델의 최대 토큰의 80%로 업데이트되었습니다 {{maxToken}}.', + setToCurrentModelMaxTokenTip: '최대 토큰이 현재 모델의 최대 토큰의 80% 로 업데이트되었습니다 {{maxToken}}.', stop_sequences: '중단 시퀀스', - stop_sequencesTip: 'API가 진행 중인 토큰 생성을 중단하는 최대 4개의 시퀀스입니다. 반환된 텍스트에는 중단 시퀀스가 포함되지 않습니다.', + stop_sequencesTip: 'API 가 진행 중인 토큰 생성을 중단하는 최대 4 개의 시퀀스입니다. 반환된 텍스트에는 중단 시퀀스가 포함되지 않습니다.', stop_sequencesPlaceholder: '시퀀스를 입력하고 탭 키를 누르세요', }, tone: { @@ -137,6 +141,8 @@ const translation = { newDataset: '지식 만들기', tools: '도구', exploreMarketplace: 'Marketplace 둘러보기', + appDetail: '앱 세부정보', + account: '계정', }, userProfile: { settings: '설정', @@ -149,6 +155,9 @@ const translation = { community: '커뮤니티', about: 'Dify 소개', logout: '로그아웃', + github: '깃허브', + compliance: '컴플라이언스', + support: '지원', }, settings: { accountGroup: '계정', @@ -192,12 +201,15 @@ const translation = { deletePlaceholder: '이메일을 입력해 주세요', sendVerificationButton: '인증 코드 보내기', verificationLabel: '인증 코드', - verificationPlaceholder: '6자리 코드를 붙여넣습니다.', + verificationPlaceholder: '6 자리 코드를 붙여넣습니다.', permanentlyDeleteButton: '계정 영구 삭제', feedbackTitle: '피드백', feedbackLabel: '계정을 삭제한 이유를 알려주시겠습니까?', feedbackPlaceholder: '선택적', deletePrivacyLinkTip: '당사가 귀하의 데이터를 처리하는 방법에 대한 자세한 내용은 다음을 참조하십시오.', + workspaceIcon: '작업 공간 아이콘', + editWorkspaceInfo: '작업 공간 정보 편집', + workspaceName: '작업 공간 이름', }, members: { team: '팀', @@ -222,7 +234,7 @@ const translation = { sendInvite: '초대 보내기', invitedAsRole: '{{role}} 사용자로 초대되었습니다', invitationSent: '초대가 전송되었습니다', - invitationSentTip: '초대가 전송되었으며, 그들은 Dify에 로그인하여 당신의 팀 데이터에 액세스할 수 있습니다.', + invitationSentTip: '초대가 전송되었으며, 그들은 Dify 에 로그인하여 당신의 팀 데이터에 액세스할 수 있습니다.', invitationLink: '초대 링크', failedInvitationEmails: '다음 사용자들은 성공적으로 초대되지 않았습니다', ok: '확인', @@ -259,7 +271,7 @@ const translation = { validatedError: '검증 실패:', validating: '키를 확인하는 중...', saveFailed: 'API 키 저장 실패', - apiKeyExceedBill: '이 API KEY에는 사용 가능한 할당량이 없습니다. 자세한 내용은', + apiKeyExceedBill: '이 API KEY 에는 사용 가능한 할당량이 없습니다. 자세한 내용은', addKey: '키 추가', comingSoon: '곧 출시됨', editKey: '편집', @@ -275,7 +287,7 @@ const translation = { openaiHosted: '호스팅된 OpenAI', onTrial: '트라이얼 중', exhausted: '할당량이 다 사용되었습니다', - desc: 'Dify가 제공하는 OpenAI 호스팅 서비스를 사용하면 GPT-3.5와 같은 모델을 사용할 수 있습니다. 트라이얼 할당량이 다 사용되기 전에 다른 모델 제공자를 설정해야 합니다.', + desc: 'Dify 가 제공하는 OpenAI 호스팅 서비스를 사용하면 GPT-3.5 와 같은 모델을 사용할 수 있습니다. 트라이얼 할당량이 다 사용되기 전에 다른 모델 제공자를 설정해야 합니다.', callTimes: '호출 횟수', usedUp: '트라이얼 할당량이 다 사용되었습니다. 다른 모델 제공자를 추가하세요.', useYourModel: '현재 사용자 정의 모델 제공자를 사용 중입니다.', @@ -296,10 +308,10 @@ const translation = { using: '임베드 기능을 사용 중입니다', enableTip: 'Anthropic 모델을 활성화하려면 먼저 OpenAI 또는 Azure OpenAI 서비스에 바인딩해야 합니다.', notEnabled: '비활성화됨', - keyFrom: 'Anthropic에서 API 키를 받으세요', + keyFrom: 'Anthropic 에서 API 키를 받으세요', }, encrypted: { - front: 'API KEY는', + front: 'API KEY 는', back: '기술을 사용하여 암호화 및 저장됩니다.', }, }, @@ -354,7 +366,7 @@ const translation = { tip: '지불된 할당량에 우선순위가 부여됩니다. 평가판 할당량은 유료 할당량이 소진된 후 사용됩니다.', }, item: { - deleteDesc: '{{modelName}}은(는) 시스템 추론 모델로 사용 중입니다. 제거 후 일부 기능을 사용할 수 없습니다. 확인하시겠습니까?', + deleteDesc: '{{modelName}}은 (는) 시스템 추론 모델로 사용 중입니다. 제거 후 일부 기능을 사용할 수 없습니다. 확인하시겠습니까?', freeQuota: '무료 할당량', }, addApiKey: 'API 키 추가', @@ -389,7 +401,7 @@ const translation = { apiKey: 'API 키', defaultConfig: '기본 구성', providerManaged: '제공자 관리', - loadBalancing: '부하 분산Load balancing', + loadBalancing: '부하 분산 Load balancing', addConfig: '구성 추가', apiKeyStatusNormal: 'APIKey 상태는 정상입니다.', configLoadBalancing: 'Config 로드 밸런싱', @@ -399,8 +411,8 @@ const translation = { loadBalancingDescription: '여러 자격 증명 세트로 부담을 줄입니다.', upgradeForLoadBalancing: '로드 밸런싱을 사용하도록 계획을 업그레이드합니다.', apiKeyRateLimit: '속도 제한에 도달했으며, {{seconds}}s 후에 사용할 수 있습니다.', - loadBalancingInfo: '기본적으로 부하 분산은 라운드 로빈 전략을 사용합니다. 속도 제한이 트리거되면 1분의 휴지 기간이 적용됩니다.', - loadBalancingLeastKeyWarning: '로드 밸런싱을 사용하려면 최소 2개의 키를 사용하도록 설정해야 합니다.', + loadBalancingInfo: '기본적으로 부하 분산은 라운드 로빈 전략을 사용합니다. 속도 제한이 트리거되면 1 분의 휴지 기간이 적용됩니다.', + loadBalancingLeastKeyWarning: '로드 밸런싱을 사용하려면 최소 2 개의 키를 사용하도록 설정해야 합니다.', providerManagedDescription: '모델 공급자가 제공하는 단일 자격 증명 집합을 사용합니다.', installProvider: '모델 공급자 설치', discoverMore: '더 알아보기', @@ -451,7 +463,7 @@ const translation = { apiBasedExtension: { title: 'API 기반 확장은 Dify 애플리케이션 전체에서 간편한 사용을 위한 설정을 단순화하고 집중적인 API 관리를 제공합니다.', link: '사용자 정의 API 기반 확장을 개발하는 방법 배우기', - linkUrl: 'https://docs.dify.ai/features/extension/api_based_extension', + linkUrl: 'https://docs.dify.ai/en/guides/extension/api-based-extension/README', add: 'API 기반 확장 추가', selector: { title: 'API 기반 확장', @@ -472,7 +484,7 @@ const translation = { apiKey: { title: 'API 키', placeholder: 'API 키를 입력하세요', - lengthError: 'API 키는 5자 미만이어야 합니다', + lengthError: 'API 키는 5 자 미만이어야 합니다', }, }, type: '유형', @@ -539,6 +551,7 @@ const translation = { inputPlaceholder: '봇과 대화', thought: '생각', thinking: '생각...', + resend: '재전송', }, promptEditor: { placeholder: '여기에 프롬프트 단어를 입력하세요. 변수를 삽입하려면 "{{"를 입력하고, 프롬프트 컨텐츠 블록을 삽입하려면 "/"를 입력하세요.', @@ -591,7 +604,7 @@ const translation = { uploadFromComputer: '컴퓨터에서 업로드', uploadFromComputerReadError: '이미지 읽기 실패. 다시 시도하세요.', uploadFromComputerUploadError: '이미지 업로드 실패. 다시 업로드하세요.', - uploadFromComputerLimit: '업로드 이미지 크기는 {{size}} MB를 초과할 수 없습니다', + uploadFromComputerLimit: '업로드 이미지 크기는 {{size}} MB 를 초과할 수 없습니다', pasteImageLink: '이미지 링크 붙여넣기', pasteImageLinkInputPlaceholder: '여기에 이미지 링크를 붙여넣으세요', pasteImageLinkInvalid: '유효하지 않은 이미지 링크', @@ -613,7 +626,7 @@ const translation = { failed: '태그 생성에 실패했습니다', }, errorMsg: { - urlError: 'URL은 http:// 또는 https:// 로 시작해야 합니다.', + urlError: 'URL 은 http:// 또는 https:// 로 시작해야 합니다.', fieldRequired: '{{field}}는 필수입니다.', }, fileUploader: { @@ -629,10 +642,31 @@ const translation = { license: { expiring_plural: '{{count}}일 후에 만료', expiring: '하루 후에 만료', + unlimited: '무제한', }, pagination: { perPage: '페이지당 항목 수', }, + theme: { + theme: '테마', + light: '밝은', + dark: '어두운', + auto: '시스템', + }, + compliance: { + iso27001: 'ISO 27001:2022 인증', + soc2Type1: 'SOC 2 유형 I 보고서', + soc2Type2: 'SOC 2 유형 II 보고서', + gdpr: 'GDPR DPA', + professionalUpgradeTooltip: '팀 플랜 이상에서만 사용할 수 있습니다.', + sandboxUpgradeTooltip: '전문가 또는 팀 플랜에서만 사용할 수 있습니다.', + }, + imageInput: { + supportedFormats: 'PNG, JPG, JPEG, WEBP 및 GIF 를 지원합니다.', + browse: '브라우즈', + dropImageHere: '여기에 이미지를 드롭하거나', + }, + you: '너', } export default translation diff --git a/web/i18n/ko-KR/custom.ts b/web/i18n/ko-KR/custom.ts index 6205a67275..9b70e7326a 100644 --- a/web/i18n/ko-KR/custom.ts +++ b/web/i18n/ko-KR/custom.ts @@ -3,16 +3,18 @@ const translation = { upgradeTip: { prefix: '플랜을 업그레이드하여', suffix: '브랜드를 사용자 정의하세요.', + des: '계획을 업그레이드하여 브랜드를 맞춤화하세요.', + title: '플랜을 업그레이드하세요', }, webapp: { - title: 'WebApp 브랜드 사용자 정의', + title: 'web app 브랜드 사용자 정의', removeBrand: 'Powered by Dify 삭제', changeLogo: 'Powered by 브랜드 이미지 변경', - changeLogoTip: '최소 크기 40x40px의 SVG 또는 PNG 형식', + changeLogoTip: '최소 크기 40x40px 의 SVG 또는 PNG 형식', }, app: { title: '앱 헤더 브랜드 사용자 정의', - changeLogoTip: '최소 크기 80x80px의 SVG 또는 PNG 형식', + changeLogoTip: '최소 크기 80x80px 의 SVG 또는 PNG 형식', }, upload: '업로드', uploading: '업로드 중', diff --git a/web/i18n/ko-KR/dataset-creation.ts b/web/i18n/ko-KR/dataset-creation.ts index b40be59fce..33f6e332a0 100644 --- a/web/i18n/ko-KR/dataset-creation.ts +++ b/web/i18n/ko-KR/dataset-creation.ts @@ -22,21 +22,21 @@ const translation = { }, uploader: { title: '텍스트 파일 업로드', - button: '파일을 끌어다 놓거나', + button: '파일이나 폴더를 끌어서 놓기', browse: '찾아보기', - tip: '{{supportTypes}}을(를) 지원합니다. 파일당 최대 크기는 {{size}}MB입니다.', + tip: '{{supportTypes}}을 (를) 지원합니다. 파일당 최대 크기는 {{size}}MB 입니다.', validation: { typeError: '지원되지 않는 파일 유형입니다', - size: '파일 크기가 너무 큽니다. 최대 크기는 {{size}}MB입니다', + size: '파일 크기가 너무 큽니다. 최대 크기는 {{size}}MB 입니다', count: '여러 파일은 지원되지 않습니다', - filesNumber: '일괄 업로드 제한({{filesNumber}}개)에 도달했습니다.', + filesNumber: '일괄 업로드 제한 ({{filesNumber}}개) 에 도달했습니다.', }, cancel: '취소', change: '변경', failed: '업로드에 실패했습니다', }, - notionSyncTitle: 'Notion에 연결되지 않았습니다', - notionSyncTip: 'Notion과 동기화하려면 먼저 Notion에 연결해야 합니다.', + notionSyncTitle: 'Notion 에 연결되지 않았습니다', + notionSyncTip: 'Notion 과 동기화하려면 먼저 Notion 에 연결해야 합니다.', connect: '연결하기', button: '다음', emptyDatasetCreation: '비어있는 지식 생성', @@ -46,13 +46,13 @@ const translation = { input: '지식 이름', placeholder: '입력하세요', nameNotEmpty: '이름은 비워둘 수 없습니다', - nameLengthInvalid: '이름은 1~40자여야 합니다', + nameLengthInvalid: '이름은 1~40 자여야 합니다', cancelButton: '취소', confirmButton: '생성', failed: '생성에 실패했습니다', }, website: { - firecrawlDocLink: 'https://docs.dify.ai/guides/knowledge-base/sync-from-website', + firecrawlDocLink: 'https://docs.dify.ai/en/guides/knowledge-base/create-knowledge-and-upload-documents/import-content-data/sync-from-website', limit: '한계', options: '옵션', firecrawlDoc: 'Firecrawl 문서', @@ -62,8 +62,8 @@ const translation = { excludePaths: '경로 제외', preview: '미리 보기', run: '달리다', - fireCrawlNotConfigured: 'Firecrawl이 구성되지 않았습니다.', - firecrawlTitle: 'Firecrawl로 🔥웹 콘텐츠 추출', + fireCrawlNotConfigured: 'Firecrawl 이 구성되지 않았습니다.', + firecrawlTitle: 'Firecrawl 로 🔥웹 콘텐츠 추출', configure: '구성', resetAll: '모두 재설정', crawlSubPage: '하위 페이지 크롤링', @@ -71,17 +71,25 @@ const translation = { scrapTimeInfo: '{{time}}s 내에 총 {{total}} 페이지를 스크랩했습니다.', unknownError: '알 수 없는 오류', totalPageScraped: '스크랩한 총 페이지 수:', - fireCrawlNotConfiguredDescription: 'API 키로 Firecrawl을 구성하여 사용합니다.', - extractOnlyMainContent: '기본 콘텐츠만 추출합니다(머리글, 탐색, 바닥글 등 없음).', - maxDepthTooltip: '입력한 URL을 기준으로 크롤링할 최대 수준입니다. 깊이 0은 입력 된 url의 페이지를 긁어 내고, 깊이 1은 url과 enteredURL + one / 이후의 모든 것을 긁어 모으는 식입니다.', + fireCrawlNotConfiguredDescription: 'API 키로 Firecrawl 을 구성하여 사용합니다.', + extractOnlyMainContent: '기본 콘텐츠만 추출합니다 (머리글, 탐색, 바닥글 등 없음).', + maxDepthTooltip: '입력한 URL 을 기준으로 크롤링할 최대 수준입니다. 깊이 0 은 입력 된 url 의 페이지를 긁어 내고, 깊이 1 은 url 과 enteredURL + one / 이후의 모든 것을 긁어 모으는 식입니다.', chooseProvider: '제공자 선택', jinaReaderDocLink: 'https://jina.ai/reader', useSitemap: '사이트맵 사용', - jinaReaderNotConfiguredDescription: '액세스를 위해 무료 API 키를 입력하여 Jina Reader를 설정합니다.', - jinaReaderDoc: 'Jina Reader에 대해 자세히 알아보기', - jinaReaderTitle: '전체 사이트를 Markdown으로 변환', - jinaReaderNotConfigured: 'Jina Reader가 구성되지 않았습니다.', - useSitemapTooltip: '사이트맵을 따라 사이트를 크롤링합니다. 그렇지 않은 경우 Jina Reader는 페이지 관련성에 따라 반복적으로 크롤링하여 더 적지만 더 높은 품질의 페이지를 생성합니다.', + jinaReaderNotConfiguredDescription: '액세스를 위해 무료 API 키를 입력하여 Jina Reader 를 설정합니다.', + jinaReaderDoc: 'Jina Reader 에 대해 자세히 알아보기', + jinaReaderTitle: '전체 사이트를 Markdown 으로 변환', + jinaReaderNotConfigured: 'Jina Reader 가 구성되지 않았습니다.', + useSitemapTooltip: '사이트맵을 따라 사이트를 크롤링합니다. 그렇지 않은 경우 Jina Reader 는 페이지 관련성에 따라 반복적으로 크롤링하여 더 적지만 더 높은 품질의 페이지를 생성합니다.', + watercrawlDoc: '워터크롤 문서', + waterCrawlNotConfiguredDescription: 'API 키로 Watercrawl 을 구성하여 사용하십시오.', + watercrawlTitle: 'Watercrawl 로 웹 콘텐츠 추출하기', + configureFirecrawl: '파이어크롤 구성하기', + watercrawlDocLink: '웹사이트에서 동기화하기', + configureJinaReader: '지나 리더 설정하기', + waterCrawlNotConfigured: 'Watercrawl 이 설정되어 있지 않습니다.', + configureWatercrawl: '워터크롤 구성하기', }, cancel: '취소', }, @@ -92,15 +100,15 @@ const translation = { custom: '사용자 설정', customDescription: '청크 규칙, 청크 길이, 전처리 규칙 등을 사용자 정의합니다.', separator: '세그먼트 식별자', - separatorPlaceholder: '예: 줄바꿈(\\\\n) 또는 특수 구분자(예: "***")', + separatorPlaceholder: '예: 줄바꿈 (\\\\n) 또는 특수 구분자 (예: "***")', maxLength: '최대 청크 길이', overlap: '청크 중첩', - overlapTip: '청크 중첩을 설정하여 그 사이의 의미적 연관성을 유지하고 검색 효과를 향상시킬 수 있습니다. 최대 청크 크기의 10%~25%로 설정하는 것이 좋습니다.', + overlapTip: '청크 중첩을 설정하여 그 사이의 의미적 연관성을 유지하고 검색 효과를 향상시킬 수 있습니다. 최대 청크 크기의 10%~25% 로 설정하는 것이 좋습니다.', overlapCheck: '청크 중첩은 최대 청크 길이를 초과할 수 없습니다', rules: '텍스트 전처리 규칙', removeExtraSpaces: '연속된 공백, 줄바꿈, 탭을 대체합니다', - removeUrlEmails: '모든 URL과 이메일 주소를 제거합니다', - removeStopwords: '일반적인 불용어(예: "a", "an", "the" 등)를 제거합니다', + removeUrlEmails: '모든 URL 과 이메일 주소를 제거합니다', + removeStopwords: '일반적인 불용어 (예: "a", "an", "the" 등) 를 제거합니다', preview: '미리보기', reset: '초기화', indexMode: '인덱스 모드', @@ -134,7 +142,7 @@ const translation = { sideTipP4: '적절한 청크와 클리닝은 모델의 성능을 향상시키고 정확하고 가치 있는 결과를 제공합니다.', previewTitle: '미리보기', previewTitleButton: '미리보기', - previewButton: '질문-답변 형식으로 전환', + previewButton: '질문 - 답변 형식으로 전환', previewSwitchTipStart: '현재 청크 미리보기는 텍스트 형식입니다. 질문과 답변 형식 미리보기로 전환하면', previewSwitchTipEnd: ' 추가 토큰이 소비됩니다', characters: '문자', @@ -143,25 +151,25 @@ const translation = { datasetSettingLink: '지식 설정', webpageUnit: '페이지', websiteSource: '웹 사이트 전처리', - separatorTip: '구분 기호는 텍스트를 구분하는 데 사용되는 문자입니다. \\n\\n 및 \\n은 단락과 줄을 구분하는 데 일반적으로 사용되는 구분 기호입니다. 쉼표(\\n\\n,\\n)와 함께 사용하면 최대 청크 길이를 초과할 경우 단락이 줄로 분할됩니다. 직접 정의한 특수 구분 기호(예: ***)를 사용할 수도 있습니다.', + separatorTip: '구분 기호는 텍스트를 구분하는 데 사용되는 문자입니다. \\n\\n 및 \\n은 단락과 줄을 구분하는 데 일반적으로 사용되는 구분 기호입니다. 쉼표 (\\n\\n,\\n) 와 함께 사용하면 최대 청크 길이를 초과할 경우 단락이 줄로 분할됩니다. 직접 정의한 특수 구분 기호 (예: ***) 를 사용할 수도 있습니다.', maxLengthCheck: '최대 청크 길이는 {{limit}} 미만이어야 합니다.', childChunkForRetrieval: '검색을 위한 자식 청크', qaSwitchHighQualityTipContent: '현재 고품질 인덱스 방법만 Q&A 형식 청크를 지원합니다. 고화질 모드로 전환하시겠습니까?', previewChunkTip: '왼쪽의 \'Preview Chunk\' 버튼을 클릭하여 프리뷰를 로드합니다', general: '일반', fullDoc: '전체 문서', - previewChunk: '프리뷰 청크(Preview Chunk)', + previewChunk: '프리뷰 청크 (Preview Chunk)', parentChunkForContext: '컨텍스트에 대한 Parent-chunk', parentChildDelimiterTip: '구분 기호는 텍스트를 구분하는 데 사용되는 문자입니다. \\n\\n은 원본 문서를 큰 부모 청크로 분할하는 데 권장됩니다. 직접 정의한 특수 구분 기호를 사용할 수도 있습니다.', paragraph: '단락', - parentChild: '부모-자식', + parentChild: '부모 - 자식', useQALanguage: 'Q&A 형식을 사용하는 청크', highQualityTip: '고품질 모드에서 삽입을 마치면 경제적 모드로 되돌릴 수 없습니다.', notAvailableForQA: 'Q&A 인덱스에는 사용할 수 없습니다.', qaSwitchHighQualityTipTitle: 'Q&A 형식에는 고품질 인덱싱 방법이 필요합니다.', - notAvailableForParentChild: '부모-자식 인덱스에는 사용할 수 없습니다.', + notAvailableForParentChild: '부모 - 자식 인덱스에는 사용할 수 없습니다.', previewChunkCount: '{{개수}} 추정된 청크', - parentChildTip: '부모-자식 모드를 사용할 때 자식 청크는 검색에 사용되고 부모 청크는 컨텍스트로 회수에 사용됩니다.', + parentChildTip: '부모 - 자식 모드를 사용할 때 자식 청크는 검색에 사용되고 부모 청크는 컨텍스트로 회수에 사용됩니다.', generalTip: '일반적인 텍스트 청크 모드에서는 검색된 청크와 회수된 청크가 동일합니다.', fullDocTip: '전체 문서가 상위 청크로 사용되며 직접 검색됩니다. 성능상의 이유로 10000 토큰을 초과하는 텍스트는 자동으로 잘립니다.', parentChildChunkDelimiterTip: '구분 기호는 텍스트를 구분하는 데 사용되는 문자입니다. \\n 은 부모 청크를 작은 자식 청크로 분할하는 데 권장됩니다. 직접 정의한 특수 구분 기호를 사용할 수도 있습니다.', @@ -199,7 +207,12 @@ const translation = { otherDataSource: { learnMore: '더 알아보세요', title: '다른 데이터 소스에 연결하시겠습니까?', - description: '현재 Dify의 기술 자료에는 제한된 데이터 소스만 있습니다. Dify 기술 자료에 데이터 소스를 제공하는 것은 모든 사용자를 위해 플랫폼의 유연성과 기능을 향상시키는 데 도움이 되는 환상적인 방법입니다. 기여 가이드를 통해 쉽게 시작할 수 있습니다. 자세한 내용은 아래 링크를 클릭하십시오.', + description: '현재 Dify 의 기술 자료에는 제한된 데이터 소스만 있습니다. Dify 기술 자료에 데이터 소스를 제공하는 것은 모든 사용자를 위해 플랫폼의 유연성과 기능을 향상시키는 데 도움이 되는 환상적인 방법입니다. 기여 가이드를 통해 쉽게 시작할 수 있습니다. 자세한 내용은 아래 링크를 클릭하십시오.', + }, + watercrawl: { + getApiKeyLinkText: 'watercrawl.dev 에서 API 키를 얻으세요.', + configWatercrawl: '워터크롤 구성하기', + apiKeyPlaceholder: 'watercrawl.dev 의 API 키', }, } diff --git a/web/i18n/ko-KR/dataset-documents.ts b/web/i18n/ko-KR/dataset-documents.ts index 6f6cb451cd..a379318959 100644 --- a/web/i18n/ko-KR/dataset-documents.ts +++ b/web/i18n/ko-KR/dataset-documents.ts @@ -1,7 +1,7 @@ const translation = { list: { title: '문서', - desc: '지식의 모든 파일이 여기에 표시되며, 전체 지식이 Dify의 인용문이나 챗 플러그인을 통해 링크되거나 색인화될 수 있습니다.', + desc: '지식의 모든 파일이 여기에 표시되며, 전체 지식이 Dify 의 인용문이나 챗 플러그인을 통해 링크되거나 색인화될 수 있습니다.', addFile: '파일 추가', addPages: '페이지 추가', table: { @@ -49,10 +49,10 @@ const translation = { empty: { title: '아직 문서가 없습니다', upload: { - tip: '파일을 업로드하거나 웹 사이트에서 동기화하거나 Notion이나 GitHub 같은 웹 앱에서 동기화할 수 있습니다.', + tip: '파일을 업로드하거나 웹 사이트에서 동기화하거나 Notion 이나 GitHub 같은 웹 앱에서 동기화할 수 있습니다.', }, sync: { - tip: 'Dify는 정기적으로 Notion에서 파일을 다운로드하고 처리합니다.', + tip: 'Dify 는 정기적으로 Notion 에서 파일을 다운로드하고 처리합니다.', }, }, delete: { @@ -82,8 +82,8 @@ const translation = { }, metadata: { title: '메타데이터', - desc: '문서 메타데이터에 레이블을 붙여 AI가 신속하게 접근할 수 있고 사용자에게 출처가 공개됩니다.', - dateTimeFormat: 'YYYY년 M월 D일 hh:mm A', + desc: '문서 메타데이터에 레이블을 붙여 AI 가 신속하게 접근할 수 있고 사용자에게 출처가 공개됩니다.', + dateTimeFormat: 'YYYY 년 M 월 D 일 hh:mm A', docTypeSelectTitle: '문서 유형을 선택하세요', docTypeChangeTitle: '문서 유형 변경', docTypeSelectWarning: '문서 유형을 변경하면 현재 입력된 메타데이터가 유지되지 않습니다.', @@ -94,8 +94,8 @@ const translation = { }, source: { upload_file: '파일 업로드', - notion: 'Notion에서 동기화', - github: 'GitHub에서 동기화', + notion: 'Notion 에서 동기화', + github: 'GitHub 에서 동기화', }, type: { book: '도서', @@ -106,8 +106,8 @@ const translation = { businessDocument: '비즈니스 문서', IMChat: 'IM 채팅', wikipediaEntry: '위키피디아 항목', - notion: 'Notion에서 동기화', - github: 'GitHub에서 동기화', + notion: 'Notion 에서 동기화', + github: 'GitHub 에서 동기화', technicalParameters: '기술적 매개변수', }, field: { @@ -301,7 +301,7 @@ const translation = { designDocument: '디자인 문서', productSpecification: '제품 사양서', financialReport: '재무 보고서', - marketAnalysis: '시장 분석', + marketAnalysis: '마켓 분석', projectPlan: '프로젝트 계획서', teamStructure: '팀 구조', policiesProcedures: '정책 및 절차', @@ -332,13 +332,13 @@ const translation = { childMaxTokens: '아이', parentMaxTokens: '부모', pause: '일시 중지', - hierarchical: '부모-자식', + hierarchical: '부모 - 자식', }, segment: { paragraphs: '단락', keywords: '키워드', addKeyWord: '키워드 추가', - keywordError: '키워드 최대 길이는 20자입니다', + keywordError: '키워드 최대 길이는 20 자입니다', characters: '문자', hitCount: '검색 횟수', vectorHash: '벡터 해시: ', @@ -351,41 +351,41 @@ const translation = { newTextSegment: '새로운 텍스트 세그먼트', newQaSegment: '새로운 Q&A 세그먼트', delete: '이 청크를 삭제하시겠습니까?', - parentChunks_one: '부모 청크(PARENT CHUNK)', + parentChunks_one: '부모 청크 (PARENT CHUNK)', newChunk: '새 청크', - addChildChunk: '자손 청크 추가(Add Child Chunk)', - editChildChunk: '자손 청크 편집(Edit Child Chunk)', - chunkDetail: '청크 디테일(Chunk Detail)', - editChunk: '청크 편집(Edit Chunk)', + addChildChunk: '자손 청크 추가 (Add Child Chunk)', + editChildChunk: '자손 청크 편집 (Edit Child Chunk)', + chunkDetail: '청크 디테일 (Chunk Detail)', + editChunk: '청크 편집 (Edit Chunk)', regeneratingTitle: '자식 청크 재생성', - newChildChunk: '새 자손 청크(New Child Chunk)', - childChunkAdded: '자식 청크 1개 추가됨', + newChildChunk: '새 자손 청크 (New Child Chunk)', + childChunkAdded: '자식 청크 1 개 추가됨', chunk: '덩어리', searchResults_zero: '결과', empty: '청크를 찾을 수 없습니다.', - editParentChunk: '부모 청크 편집(Edit Parent Chunk)', + editParentChunk: '부모 청크 편집 (Edit Parent Chunk)', chunks_one: '덩어리', regenerationSuccessMessage: '이 창을 닫을 수 있습니다.', - childChunks_one: '자식 청크(CHILD CHUNK)', + childChunks_one: '자식 청크 (CHILD CHUNK)', regenerationSuccessTitle: '재생이 완료되었습니다.', editedAt: '편집 위치', addAnother: '다른 항목 추가', - chunkAdded: '청크 1개 추가됨', + chunkAdded: '청크 1 개 추가됨', searchResults_one: '결과', searchResults_other: '결과', regenerationConfirmMessage: '자식 청크를 다시 생성하면 편집된 청크와 새로 추가된 청크를 포함하여 현재 자식 청크를 덮어씁니다. 재생성은 취소할 수 없습니다.', regenerationConfirmTitle: '자식 청크를 다시 생성하시겠습니까?', clearFilter: '필터 지우기', characters_one: '문자', - parentChunk: '부모-청크', + parentChunk: '부모 - 청크', expandChunks: '청크 확장', collapseChunks: '청크 축소', - parentChunks_other: '부모 청크(PARENT CHUNKS)', + parentChunks_other: '부모 청크 (PARENT CHUNKS)', childChunk: '자식 청크', childChunks_other: '자식 청크', chunks_other: '청크', edited: '편집', - addChunk: '청크 추가(Add Chunk)', + addChunk: '청크 추가 (Add Chunk)', characters_other: '문자', regeneratingMessage: '시간이 걸릴 수 있으니 잠시만 기다려 주십시오...', }, diff --git a/web/i18n/ko-KR/dataset-hit-testing.ts b/web/i18n/ko-KR/dataset-hit-testing.ts index a5329fbdb5..17ab7db08a 100644 --- a/web/i18n/ko-KR/dataset-hit-testing.ts +++ b/web/i18n/ko-KR/dataset-hit-testing.ts @@ -13,7 +13,7 @@ const translation = { input: { title: '소스 텍스트', placeholder: '텍스트를 입력하세요. 간결한 설명문이 좋습니다.', - countWarning: '최대 200자까지 입력할 수 있습니다.', + countWarning: '최대 200 자까지 입력할 수 있습니다.', indexWarning: '고품질 지식만.', testing: '테스트 중', }, @@ -29,7 +29,7 @@ const translation = { records: '레코드', hitChunks: '{{num}}개의 자식 청크를 히트했습니다.', keyword: '키워드', - chunkDetail: '청크 디테일(Chunk Detail)', + chunkDetail: '청크 디테일 (Chunk Detail)', } export default translation diff --git a/web/i18n/ko-KR/dataset-settings.ts b/web/i18n/ko-KR/dataset-settings.ts index c15fff8db6..272cd4b9f9 100644 --- a/web/i18n/ko-KR/dataset-settings.ts +++ b/web/i18n/ko-KR/dataset-settings.ts @@ -7,7 +7,7 @@ const translation = { nameError: '이름은 비워둘 수 없습니다', desc: '지식 설명', descInfo: '지식 내용을 개괄하는 명확한 텍스트 설명을 작성하세요. 이 설명은 여러 지식 중에서 선택하는 기준으로 사용됩니다.', - descPlaceholder: '이 지식에 포함된 내용을 설명하세요. 자세한 설명은 AI가 지식 내용에 빠르게 접근할 수 있도록 합니다. 비어 있으면 Dify가 기본 검색 전략을 사용합니다.', + descPlaceholder: '이 지식에 포함된 내용을 설명하세요. 자세한 설명은 AI 가 지식 내용에 빠르게 접근할 수 있도록 합니다. 비어 있으면 Dify 가 기본 검색 전략을 사용합니다.', descWrite: '좋은 지식 설명 작성 방법 배우기', permissions: '권한', permissionsOnlyMe: '나만', @@ -25,6 +25,7 @@ const translation = { learnMore: '자세히 알아보기', description: ' 검색 방법에 대한 자세한 정보', longDescription: ' 검색 방법에 대한 자세한 내용은 언제든지 지식 설정에서 변경할 수 있습니다.', + method: '검색 방법', }, save: '저장', permissionsInvitedMembers: '부분 팀 구성원', @@ -33,7 +34,7 @@ const translation = { externalKnowledgeID: '외부 지식 ID', retrievalSettings: '검색 설정', upgradeHighQualityTip: '고품질 모드로 업그레이드한 후에는 경제적 모드로 되돌릴 수 없습니다.', - indexMethodChangeToEconomyDisabledTip: 'HQ에서 ECO로 다운그레이드할 수 없습니다.', + indexMethodChangeToEconomyDisabledTip: 'HQ 에서 ECO 로 다운그레이드할 수 없습니다.', helpText: '좋은 데이터 세트 설명을 작성하는 방법을 알아보세요.', searchModel: '모델 검색', }, diff --git a/web/i18n/ko-KR/dataset.ts b/web/i18n/ko-KR/dataset.ts index 4d622cf7f2..3eb4634194 100644 --- a/web/i18n/ko-KR/dataset.ts +++ b/web/i18n/ko-KR/dataset.ts @@ -67,17 +67,17 @@ const translation = { semantic: '의미론적', keyword: '키워드', }, - nTo1RetrievalLegacy: 'N-대-1 검색은 9월부터 공식적으로 더 이상 사용되지 않습니다. 더 나은 결과를 얻으려면 최신 다중 경로 검색을 사용하는 것이 좋습니다.', + nTo1RetrievalLegacy: 'N-대 -1 검색은 9 월부터 공식적으로 더 이상 사용되지 않습니다. 더 나은 결과를 얻으려면 최신 다중 경로 검색을 사용하는 것이 좋습니다.', nTo1RetrievalLegacyLink: '자세히 알아보기', - nTo1RetrievalLegacyLinkText: 'N-대-1 검색은 9월에 공식적으로 더 이상 사용되지 않습니다.', + nTo1RetrievalLegacyLinkText: 'N-대 -1 검색은 9 월에 공식적으로 더 이상 사용되지 않습니다.', defaultRetrievalTip: '다중 경로 검색이 기본적으로 사용됩니다. 지식은 여러 기술 자료에서 검색된 다음 순위가 다시 매겨집니다.', editExternalAPIConfirmWarningContent: { - front: '이 외부 지식 API는 다음에 연결됩니다.', + front: '이 외부 지식 API 는 다음에 연결됩니다.', end: '외부 지식, 그리고 이 수정 사항은 그들 모두에게 적용될 것입니다. 이 변경 사항을 저장하시겠습니까?', }, editExternalAPIFormWarning: { end: '외부 지식', - front: '이 외부 API는 다음에 연결됩니다.', + front: '이 외부 API 는 다음에 연결됩니다.', }, deleteExternalAPIConfirmWarningContent: { title: { @@ -85,25 +85,25 @@ const translation = { end: '?', }, content: { - front: '이 외부 지식 API는 다음에 연결됩니다.', - end: '외부 지식. 이 API를 삭제하면 모두 무효화됩니다. 이 API를 삭제하시겠습니까?', + front: '이 외부 지식 API 는 다음에 연결됩니다.', + end: '외부 지식. 이 API 를 삭제하면 모두 무효화됩니다. 이 API 를 삭제하시겠습니까?', }, - noConnectionContent: '이 API를 삭제하시겠습니까?', + noConnectionContent: '이 API 를 삭제하시겠습니까?', }, selectExternalKnowledgeAPI: { placeholder: '외부 지식 API 선택', }, connectDatasetIntro: { content: { - link: '외부 API를 만드는 방법 알아보기', - end: '. 그런 다음 해당 기술 ID를 찾아 왼쪽 양식에 입력합니다. 모든 정보가 올바르면 연결 단추를 클릭한 후 기술 자료의 검색 테스트로 자동으로 이동합니다.', - front: '외부 기술 자료에 연결하려면 먼저 외부 API를 만들어야 합니다. 주의 깊게 읽고 참조하십시오.', + link: '외부 API 를 만드는 방법 알아보기', + end: '. 그런 다음 해당 기술 ID 를 찾아 왼쪽 양식에 입력합니다. 모든 정보가 올바르면 연결 단추를 클릭한 후 기술 자료의 검색 테스트로 자동으로 이동합니다.', + front: '외부 기술 자료에 연결하려면 먼저 외부 API 를 만들어야 합니다. 주의 깊게 읽고 참조하십시오.', }, learnMore: '더 알아보세요', title: '외부 기술 자료에 연결하는 방법', }, connectHelper: { - helper1: 'API 및 기술 자료 ID를 통해 외부 기술 자료에 연결합니다. 현재,', + helper1: 'API 및 기술 자료 ID 를 통해 외부 기술 자료에 연결합니다. 현재,', helper4: '도움말 문서 읽기', helper2: '검색 기능만 지원됩니다', helper5: '이 기능을 사용하기 전에 주의하십시오.', @@ -134,19 +134,19 @@ const translation = { externalTag: '외부', editExternalAPIFormTitle: '외부 지식 API 편집', externalKnowledgeNamePlaceholder: '기술 자료의 이름을 입력하십시오.', - externalAPIPanelDocumentation: '외부 지식 API를 만드는 방법 알아보기', + externalAPIPanelDocumentation: '외부 지식 API 를 만드는 방법 알아보기', createNewExternalAPI: '새 외부 지식 API 만들기', mixtureInternalAndExternalTip: '리랭크 모델은 내부 및 외부 지식의 혼합에 필요합니다.', connectDataset: '외부 기술 자료에 연결', learnHowToWriteGoodKnowledgeDescription: '적절한 지식 설명을 작성하는 방법 알아보기', - externalKnowledgeDescriptionPlaceholder: '이 기술 자료의 내용 설명(선택 사항)', + externalKnowledgeDescriptionPlaceholder: '이 기술 자료의 내용 설명 (선택 사항)', externalKnowledgeId: '외부 지식 ID', - externalKnowledgeIdPlaceholder: '지식 ID를 입력하십시오.', + externalKnowledgeIdPlaceholder: '지식 ID 를 입력하십시오.', allExternalTip: '외부 지식만 사용하는 경우 사용자는 리랭크 모델을 사용할지 여부를 선택할 수 있습니다. 활성화하지 않으면 검색된 청크가 점수에 따라 정렬됩니다. 서로 다른 기술 자료의 검색 전략이 일관되지 않으면 부정확합니다.', - externalAPIPanelDescription: '외부 지식 API는 Dify 외부의 기술 자료에 연결하고 해당 기술 자료에서 지식을 검색하는 데 사용됩니다.', - noExternalKnowledge: '아직 외부 지식 API가 없으므로 여기를 클릭하여 생성하십시오.', + externalAPIPanelDescription: '외부 지식 API 는 Dify 외부의 기술 자료에 연결하고 해당 기술 자료에서 지식을 검색하는 데 사용됩니다.', + noExternalKnowledge: '아직 외부 지식 API 가 없으므로 여기를 클릭하여 생성하십시오.', chunkingMode: { - parentChild: '부모-자식', + parentChild: '부모 - 자식', general: '일반', }, parentMode: { @@ -164,9 +164,57 @@ const translation = { localDocs: '로컬 문서', preprocessDocument: '{{숫자}} 문서 전처리', enable: '사용', - documentsDisabled: '{{num}} 문서 사용 안 함 - 30일 이상 비활성 상태', + documentsDisabled: '{{num}} 문서 사용 안 함 - 30 일 이상 비활성 상태', allKnowledge: '모든 지식', allKnowledgeDescription: '이 작업 영역의 모든 정보를 표시하려면 선택합니다. 워크스페이스 소유자만 모든 기술 자료를 관리할 수 있습니다.', + metadata: { + createMetadata: { + namePlaceholder: '메타데이터 이름 추가', + name: '이름', + type: '유형', + back: '뒤', + title: '새 메타데이터', + }, + checkName: { + empty: '메타데이터 이름은 비어 있을 수 없습니다.', + invalid: '메타데이터 이름은 소문자, 숫자 및 밑줄만 포함할 수 있으며 소문자로 시작해야 합니다.', + }, + batchEditMetadata: { + multipleValue: '다중 값', + editMetadata: '메타데이터 편집', + applyToAllSelectDocument: '선택한 모든 문서에 적용', + editDocumentsNum: '{{num}} 개 문서 편집 중', + applyToAllSelectDocumentTip: '선택된 모든 문서에 대해 위에서 편집한 모든 메타데이터와 새 메타데이터를 자동으로 생성하십시오. 그렇지 않으면 메타데이터 편집은 해당 문서에만 적용됩니다.', + }, + selectMetadata: { + manageAction: '관리하다', + newAction: '새 메타데이터', + search: '메타데이터 검색', + }, + datasetMetadata: { + name: '이름', + deleteTitle: '삭제 확인', + disabled: '사용안함', + addMetaData: '메타데이터 추가', + values: '{{num}} 값들', + namePlaceholder: '메타데이터 이름', + rename: '이름 변경', + builtInDescription: '내장 메타데이터는 자동으로 추출되고 생성됩니다. 사용하기 전에 활성화해야 하며 편집할 수 없습니다.', + deleteContent: '정말 \'{{name}}\' 메타데이터를 삭제하시겠습니까?', + description: '이 지식에서 모든 메타데이터를 관리할 수 있습니다. 수정 사항은 모든 문서에 동기화됩니다.', + builtIn: '내장형', + }, + documentMetadata: { + technicalParameters: '기술 매개변수', + startLabeling: '레이블링 시작', + metadataToolTip: '메타데이터는 정보 검색의 정확성과 관련성을 향상시키는 중요한 필터 역할을 합니다. 이 문서에 대한 메타데이터를 여기에서 수정하고 추가할 수 있습니다.', + documentInformation: '문서 정보', + }, + addMetadata: '메타데이터 추가', + metadata: '메타데이터', + chooseTime: '시간을 선택하세요...', + }, + embeddingModelNotAvailable: '임베딩 모델을 사용할 수 없습니다.', } export default translation diff --git a/web/i18n/ko-KR/education.ts b/web/i18n/ko-KR/education.ts new file mode 100644 index 0000000000..eba00b0f9f --- /dev/null +++ b/web/i18n/ko-KR/education.ts @@ -0,0 +1,47 @@ +const translation = { + toVerifiedTip: { + end: 'Dify 프로페셔널 플랜을 위해.', + coupon: '독점 100% 쿠폰', + front: '당신은 이제 교육 인증 상태를 받을 자격이 있습니다. 아래에 귀하의 교육 정보를 입력하여 과정을 완료하고 인증을 받으십시오.', + }, + form: { + schoolName: { + placeholder: '귀하의 학교의 공식 약어가 아닌 전체 이름을 입력하세요.', + title: '당신의 학교 이름', + }, + schoolRole: { + option: { + teacher: '교사', + student: '학생', + administrator: '학교 관리자', + }, + title: '당신의 학교 역할', + }, + terms: { + desc: { + end: '제출함으로써:', + and: '와', + termsOfService: '서비스 약관', + front: '귀하의 정보 및 교육 인증 상태 사용은 우리의', + privacyPolicy: '개인정보 보호정책', + }, + option: { + inSchool: '나는 제공된 기관에 재학 중이거나 고용되어 있음을 확인합니다. Dify 는 재학증명서나 고용증명서를 요청할 수 있습니다. 만약 내가 자격을 허위로 진술하면, 나는 내 교육 상태에 따라 처음 면제된 수수료를 지불하기로 동의합니다.', + age: '나는 최소한 18 세 이상임을 확인합니다.', + }, + title: '약관 및 동의사항', + }, + }, + submit: '제출', + rejectContent: '안타깝게도, 귀하는 교육 인증 상태에 적합하지 않으므로 이 이메일 주소를 사용할 경우 Dify Professional Plan 의 독점 100% 쿠폰을 받을 수 없습니다.', + successContent: '귀하의 계정에 Dify Professional 플랜을 위한 100% 할인 쿠폰을 발급했습니다. 이 쿠폰은 1 년간 유효하므로 유효 기간 내에 사용해 주시기 바랍니다.', + currentSigned: '현재 로그인 중입니다', + toVerified: '교육 인증 받기', + rejectTitle: '귀하의 Dify 교육 인증이 거부되었습니다.', + learn: '교육 인증을 받는 방법을 배우세요', + submitError: '양식 제출에 실패했습니다. 나중에 다시 시도해 주세요.', + successTitle: '당신은 Dify 교육 인증을 받았습니다.', + emailLabel: '현재 이메일', +} + +export default translation diff --git a/web/i18n/ko-KR/explore.ts b/web/i18n/ko-KR/explore.ts index 6a6522fd1a..bc6438af2b 100644 --- a/web/i18n/ko-KR/explore.ts +++ b/web/i18n/ko-KR/explore.ts @@ -16,7 +16,7 @@ const translation = { }, }, apps: { - title: 'Dify로 앱 탐색', + title: 'Dify 로 앱 탐색', description: '이 템플릿 앱을 즉시 사용하거나 템플릿을 기반으로 고유한 앱을 사용자 정의하세요.', allCategories: '모든 카테고리', }, @@ -37,6 +37,7 @@ const translation = { Agent: '에이전트', Workflow: '워크플로우', HR: '인사', + Entertainment: '오락', }, } diff --git a/web/i18n/ko-KR/login.ts b/web/i18n/ko-KR/login.ts index 05a60c7b68..da044554bc 100644 --- a/web/i18n/ko-KR/login.ts +++ b/web/i18n/ko-KR/login.ts @@ -1,6 +1,6 @@ const translation = { pageTitle: '시작하기 🎉', - welcome: 'Dify에 오신 것을 환영합니다. 계속하려면 로그인하세요.', + welcome: 'Dify 에 오신 것을 환영합니다. 계속하려면 로그인하세요.', email: '이메일 주소', emailPlaceholder: '이메일 주소를 입력하세요', password: '비밀번호', @@ -19,13 +19,13 @@ const translation = { invitationCodePlaceholder: '초대 코드를 입력하세요', interfaceLanguage: '인터페이스 언어', timezone: '시간대', - go: 'Dify로 이동', + go: 'Dify 로 이동', sendUsMail: '간단한 소개를 메일로 보내주시면 초대 요청을 처리해드립니다.', acceptPP: '개인정보 처리 방침에 동의합니다.', reset: '비밀번호를 재설정하려면 다음 명령을 실행하세요:', - withGitHub: 'GitHub로 계속', - withGoogle: 'Google로 계속', - rightTitle: 'LLM의 최대 잠재력을 발휘하세요', + withGitHub: 'GitHub 로 계속', + withGoogle: 'Google 로 계속', + rightTitle: 'LLM 의 최대 잠재력을 발휘하세요', rightDesc: '매력적이고 조작 가능하며 개선 가능한 AI 애플리케이션을 쉽게 구축하세요.', tos: '이용약관', pp: '개인정보 처리 방침', @@ -52,33 +52,33 @@ const translation = { emailInValid: '유효한 이메일 주소를 입력하세요.', nameEmpty: '사용자 이름을 입력하세요.', passwordEmpty: '비밀번호를 입력하세요.', - passwordInvalid: '비밀번호는 문자와 숫자를 포함하고 8자 이상이어야 합니다.', - passwordLengthInValid: '비밀번호는 8자 이상이어야 합니다.', + passwordInvalid: '비밀번호는 문자와 숫자를 포함하고 8 자 이상이어야 합니다.', + passwordLengthInValid: '비밀번호는 8 자 이상이어야 합니다.', registrationNotAllowed: '계정을 찾을 수 없습니다. 등록하려면 시스템 관리자에게 문의하십시오.', }, license: { - tip: 'Dify Community Edition을 시작하기 전에 GitHub의', + tip: 'Dify Community Edition 을 시작하기 전에 GitHub 의', link: '오픈 소스 라이선스', }, join: '가입하기', joinTipStart: '당신을 초대합니다.', joinTipEnd: '팀에 가입하세요.', invalid: '링크의 유효 기간이 만료되었습니다.', - explore: 'Dify를 탐색하세요', + explore: 'Dify 를 탐색하세요', activatedTipStart: '이제', activatedTipEnd: '팀에 가입되었습니다.', activated: '지금 로그인하세요', adminInitPassword: '관리자 초기화 비밀번호', validate: '확인', - sso: 'SSO로 계속하기', + sso: 'SSO 로 계속하기', checkCode: { verify: '확인', verificationCode: '인증 코드', tips: '<strong>{{email}}</strong>로 인증 코드를 보내드립니다.', - validTime: '코드는 5분 동안 유효합니다', + validTime: '코드는 5 분 동안 유효합니다', checkYourEmail: '이메일 주소 확인', invalidCode: '유효하지 않은 코드', - verificationCodePlaceholder: '6자리 코드 입력', + verificationCodePlaceholder: '6 자리 코드 입력', emptyCode: '코드가 필요합니다.', useAnotherMethod: '다른 방법 사용', didNotReceiveCode: '코드를 받지 못하셨나요?', @@ -89,7 +89,7 @@ const translation = { useVerificationCode: '인증 코드 사용', continueWithCode: '코드로 계속하기', usePassword: '비밀번호 사용', - withSSO: 'SSO로 계속하기', + withSSO: 'SSO 로 계속하기', backToLogin: '로그인으로 돌아가기', resetPassword: '비밀번호 재설정', setYourAccount: '계정 설정', @@ -98,13 +98,18 @@ const translation = { changePasswordBtn: '비밀번호 설정', enterYourName: '사용자 이름을 입력해 주세요', noLoginMethodTip: '인증 방법을 추가하려면 시스템 관리자에게 문의하십시오.', - resetPasswordDesc: 'Dify에 가입할 때 사용한 이메일을 입력하면 비밀번호 재설정 이메일을 보내드립니다.', - licenseInactiveTip: '작업 영역에 대한 Dify Enterprise 라이선스가 비활성 상태입니다. Dify를 계속 사용하려면 관리자에게 문의하십시오.', + resetPasswordDesc: 'Dify 에 가입할 때 사용한 이메일을 입력하면 비밀번호 재설정 이메일을 보내드립니다.', + licenseInactiveTip: '작업 영역에 대한 Dify Enterprise 라이선스가 비활성 상태입니다. Dify 를 계속 사용하려면 관리자에게 문의하십시오.', licenseLost: '라이센스 분실', - licenseLostTip: 'Dify 라이선스 서버에 연결하지 못했습니다. Dify를 계속 사용하려면 관리자에게 문의하십시오.', + licenseLostTip: 'Dify 라이선스 서버에 연결하지 못했습니다. Dify 를 계속 사용하려면 관리자에게 문의하십시오.', licenseInactive: 'License Inactive(라이선스 비활성)', licenseExpired: '라이센스가 만료되었습니다.', - licenseExpiredTip: '작업 영역에 대한 Dify Enterprise 라이선스가 만료되었습니다. Dify를 계속 사용하려면 관리자에게 문의하십시오.', + licenseExpiredTip: '작업 영역에 대한 Dify Enterprise 라이선스가 만료되었습니다. Dify 를 계속 사용하려면 관리자에게 문의하십시오.', + webapp: { + noLoginMethod: '웹 애플리케이션에 대한 인증 방법이 구성되어 있지 않습니다.', + disabled: '웹앱 인증이 비활성화되었습니다. 이를 활성화하려면 시스템 관리자에게 문의하십시오. 앱을 직접 사용해 볼 수 있습니다.', + noLoginMethodTip: '인증 방법을 추가하려면 시스템 관리자에게 연락하십시오.', + }, } export default translation diff --git a/web/i18n/ko-KR/plugin.ts b/web/i18n/ko-KR/plugin.ts index 06445f3fb7..429fc14730 100644 --- a/web/i18n/ko-KR/plugin.ts +++ b/web/i18n/ko-KR/plugin.ts @@ -18,21 +18,21 @@ const translation = { source: { marketplace: '마켓플레이스에서 설치', local: '로컬 패키지 파일에서 설치', - github: 'GitHub에서 설치', + github: 'GitHub 에서 설치', }, noInstalled: '설치된 플러그인이 없습니다.', notFound: '플러그인을 찾을 수 없습니다.', }, source: { local: '로컬 패키지 파일', - marketplace: '시장', + marketplace: '마켓', github: '깃허브', }, detailPanel: { categoryTip: { marketplace: '마켓플레이스에서 설치됨', debugging: '디버깅 플러그인', - github: 'Github에서 설치됨', + github: 'Github 에서 설치됨', local: '로컬 플러그인', }, operation: { @@ -56,12 +56,13 @@ const translation = { settings: '사용자 설정', unsupportedContent2: '버전을 전환하려면 클릭합니다.', uninstalledTitle: '도구가 설치되지 않음', - descriptionPlaceholder: '도구의 용도에 대한 간략한 설명(예: 특정 위치의 온도 가져오기).', + descriptionPlaceholder: '도구의 용도에 대한 간략한 설명 (예: 특정 위치의 온도 가져오기).', title: '추가 도구', toolLabel: '도구', placeholder: '도구 선택...', paramsTip2: '\'자동\'이 꺼져 있으면 기본값이 사용됩니다.', unsupportedContent: '설치된 플러그인 버전은 이 작업을 제공하지 않습니다.', + toolSetting: '도구 설정', }, configureApp: '앱 구성', strategyNum: '{{번호}} {{전략}} 포함', @@ -140,9 +141,9 @@ const translation = { installFromGitHub: { uploadFailed: '업로드 실패', selectVersionPlaceholder: '버전을 선택하세요.', - installPlugin: 'GitHub에서 플러그인 설치', + installPlugin: 'GitHub 에서 플러그인 설치', installFailed: '설치 실패', - updatePlugin: 'GitHub에서 플러그인 업데이트', + updatePlugin: 'GitHub 에서 플러그인 업데이트', selectPackage: '패키지 선택', gitHubRepo: 'GitHub 리포지토리', selectPackagePlaceholder: '패키지를 선택하세요.', @@ -160,9 +161,9 @@ const translation = { title: '플러그인 설치', }, error: { - noReleasesFound: '릴리스를 찾을 수 없습니다. GitHub 리포지토리 또는 입력 URL을 확인하세요.', + noReleasesFound: '릴리스를 찾을 수 없습니다. GitHub 리포지토리 또는 입력 URL 을 확인하세요.', fetchReleasesError: '릴리스를 검색할 수 없습니다. 나중에 다시 시도하십시오.', - inValidGitHubUrl: '잘못된 GitHub URL입니다. 유효한 URL을 https://github.com/owner/repo 형식으로 입력하십시오.', + inValidGitHubUrl: '잘못된 GitHub URL 입니다. 유효한 URL 을 https://github.com/owner/repo 형식으로 입력하십시오.', }, marketplace: { sortOption: { @@ -177,9 +178,11 @@ const translation = { difyMarketplace: 'Dify 마켓플레이스', pluginsResult: '{{num}} 결과', discover: '발견하다', - moreFrom: 'Marketplace에서 더 보기', + moreFrom: 'Marketplace 에서 더 보기', sortBy: '정렬', and: '그리고', + verifiedTip: 'Dify 에 의해 확인됨', + partnerTip: 'Dify 파트너에 의해 확인됨', }, task: { installingWithSuccess: '{{installingLength}} 플러그인 설치, {{successLength}} 성공.', @@ -195,15 +198,20 @@ const translation = { endpointsEnabled: '{{num}}개의 엔드포인트 집합이 활성화되었습니다.', installFrom: '에서 설치', allCategories: '모든 카테고리', - submitPlugin: '제출 플러그인', - findMoreInMarketplace: 'Marketplace에서 더 알아보기', + findMoreInMarketplace: 'Marketplace 에서 더 알아보기', searchCategories: '검색 카테고리', search: '검색', - searchInMarketplace: 'Marketplace에서 검색', + searchInMarketplace: 'Marketplace 에서 검색', from: '보낸 사람', searchPlugins: '검색 플러그인', install: '{{num}} 설치', - fromMarketplace: 'Marketplace에서', + fromMarketplace: 'Marketplace 에서', + metadata: { + title: '플러그인', + }, + difyVersionNotCompatible: '현재 Dify 버전이 이 플러그인과 호환되지 않습니다. 필요한 최소 버전으로 업그레이드하십시오: {{minimalDifyVersion}}', + requestAPlugin: '플러그인을 요청하세요', + publishPlugins: '플러그인 게시', } export default translation diff --git a/web/i18n/ko-KR/share-app.ts b/web/i18n/ko-KR/share-app.ts index be2e34a5fc..3958b4f93e 100644 --- a/web/i18n/ko-KR/share-app.ts +++ b/web/i18n/ko-KR/share-app.ts @@ -26,6 +26,12 @@ const translation = { }, tryToSolve: '해결하려고 합니다', temporarySystemIssue: '죄송합니다. 일시적인 시스템 문제가 발생했습니다.', + expand: '확장', + collapse: '축소', + viewChatSettings: '채팅 설정 보기', + newChatTip: '이미 새로운 채팅 중입니다.', + chatFormTip: '채팅이 시작된 후에는 채팅 설정을 수정할 수 없습니다.', + chatSettingsTitle: '새 채팅 설정', }, generation: { tabs: { @@ -45,7 +51,7 @@ const translation = { run: '실행', copy: '복사', resultTitle: 'AI 완성', - noData: 'AI가 필요한 내용을 제공할 것입니다.', + noData: 'AI 가 필요한 내용을 제공할 것입니다.', csvUploadTitle: 'CSV 파일을 여기로 끌어다 놓거나', browse: '찾아보기', csvStructureTitle: 'CSV 파일은 다음 구조를 따라야 합니다:', @@ -59,11 +65,16 @@ const translation = { errorMsg: { empty: '업로드된 파일에 컨텐츠를 입력해주세요.', fileStructNotMatch: '업로드된 CSV 파일이 구조와 일치하지 않습니다.', - emptyLine: '줄 {{rowIndex}}이(가) 비어 있습니다.', + emptyLine: '줄 {{rowIndex}}이 (가) 비어 있습니다.', invalidLine: '줄 {{rowIndex}}: {{varName}}의 값은 비워둘 수 없습니다.', moreThanMaxLengthLine: '줄 {{rowIndex}}: {{varName}}의 값은 {{maxLength}}자를 초과할 수 없습니다.', atLeastOne: '업로드된 파일에는 적어도 한 줄의 입력이 필요합니다.', }, + execution: '실행', + executions: '{{num}} 처형', + }, + login: { + backToHome: '홈으로 돌아가기', }, } diff --git a/web/i18n/ko-KR/time.ts b/web/i18n/ko-KR/time.ts index e2410dd34b..172bb78bd6 100644 --- a/web/i18n/ko-KR/time.ts +++ b/web/i18n/ko-KR/time.ts @@ -1,3 +1,37 @@ -const translation = {} +const translation = { + daysInWeek: { + Wed: '수요일', + Thu: '목요일', + Fri: '자유', + Sat: '토요일', + Sun: '태양', + Tue: '화요일', + Mon: '몬', + }, + months: { + May: '5 월', + January: '1 월', + August: '8 월', + July: '7 월', + April: '4 월', + October: '10 월', + December: '12 월', + February: '2 월', + June: '6 월', + November: '11 월', + March: '3 월', + September: '9 월', + }, + operation: { + pickDate: '날짜 선택', + cancel: '취소', + ok: '좋아요', + now: '지금', + }, + title: { + pickTime: '시간 선택', + }, + defaultPlaceholder: '시간을 선택하세요...', +} export default translation diff --git a/web/i18n/ko-KR/tools.ts b/web/i18n/ko-KR/tools.ts index 8727c6dfa5..45c63b5f80 100644 --- a/web/i18n/ko-KR/tools.ts +++ b/web/i18n/ko-KR/tools.ts @@ -9,13 +9,12 @@ const translation = { workflow: '워크플로우', }, contribute: { - line1: '저는 Dify에', + line1: '저는 Dify 에', line2: '도구를 기여하는데 관심이 있습니다.', viewGuide: '가이드 보기', }, author: '저자', auth: { - unauthorized: '인증되지 않음', authorized: '인증됨', setup: '사용을 위한 인증 설정', setupModalTitle: '인증 설정', @@ -48,9 +47,9 @@ const translation = { schema: '스키마', schemaPlaceHolder: '여기에 OpenAPI 스키마를 입력하세요', viewSchemaSpec: 'OpenAPI-Swagger 명세 보기', - importFromUrl: 'URL에서 가져오기', + importFromUrl: 'URL 에서 가져오기', importFromUrlPlaceHolder: 'https://...', - urlError: '유효한 URL을 입력하세요', + urlError: '유효한 URL 을 입력하세요', examples: '예시', exampleOptions: { json: '날씨 (JSON)', @@ -97,7 +96,7 @@ const translation = { methodSetting: '설정', methodSettingTip: '도구 설정에서 사용자가 기입', methodParameter: '파라미터', - methodParameterTip: '추론 중에 LLM이 기입', + methodParameterTip: '추론 중에 LLM 이 기입', label: '태그', labelPlaceholder: '태그를 선택하세요.(선택사항)', description: '설명', diff --git a/web/i18n/ko-KR/workflow.ts b/web/i18n/ko-KR/workflow.ts index 17297f1273..3701d50fe7 100644 --- a/web/i18n/ko-KR/workflow.ts +++ b/web/i18n/ko-KR/workflow.ts @@ -38,8 +38,6 @@ const translation = { setVarValuePlaceholder: '변수 값 설정', needConnectTip: '이 단계는 아무것도 연결되어 있지 않습니다', maxTreeDepth: '분기당 최대 {{depth}} 노드 제한', - needEndNode: '종료 블록을 추가해야 합니다', - needAnswerNode: '답변 블록을 추가해야 합니다', workflowProcess: '워크플로우 과정', notRunning: '아직 실행되지 않음', previewPlaceholder: '디버깅을 시작하려면 아래 상자에 내용을 입력하세요', @@ -58,7 +56,6 @@ const translation = { learnMore: '더 알아보기', copy: '복사', duplicate: '복제', - addBlock: '블록 추가', pasteHere: '여기에 붙여넣기', pointerMode: '포인터 모드', handMode: '핸드 모드', @@ -101,11 +98,23 @@ const translation = { ImageUploadLegacyTip: '이제 시작 양식에서 파일 형식 변수를 만들 수 있습니다. 앞으로 이미지 업로드 기능은 더 이상 지원되지 않습니다.', importWarning: '주의', importWarningDetails: 'DSL 버전 차이는 특정 기능에 영향을 미칠 수 있습니다.', - openInExplore: 'Explore에서 열기', + openInExplore: 'Explore 에서 열기', onFailure: '실패 시', addFailureBranch: '실패 분기 추가', noHistory: '이력 없음', loadMore: '더 많은 워크플로우 로드', + publishUpdate: '업데이트 게시', + exportJPEG: 'JPEG 로 내보내기', + exitVersions: '종료 버전', + exportImage: '이미지 내보내기', + noExist: '해당 변수가 없습니다.', + exportSVG: 'SVG 로 내보내기', + versionHistory: '버전 기록', + exportPNG: 'PNG 로 내보내기', + referenceVar: '참조 변수', + addBlock: '노드 추가', + needAnswerNode: '답변 노드를 추가해야 합니다.', + needEndNode: '종단 노드를 추가해야 합니다.', }, env: { envPanelTitle: '환경 변수', @@ -130,7 +139,7 @@ const translation = { }, chatVariable: { panelTitle: '대화 변수', - panelDescription: '대화 변수는 LLM이 기억해야 할 대화 기록, 업로드된 파일, 사용자 선호도 등의 상호작용 정보를 저장하는 데 사용됩니다. 이들은 읽기 및 쓰기가 가능합니다.', + panelDescription: '대화 변수는 LLM 이 기억해야 할 대화 기록, 업로드된 파일, 사용자 선호도 등의 상호작용 정보를 저장하는 데 사용됩니다. 이들은 읽기 및 쓰기가 가능합니다.', docLink: '자세한 내용은 문서를 참조하세요.', button: '변수 추가', modal: { @@ -143,7 +152,7 @@ const translation = { valuePlaceholder: '기본값, 설정하지 않으려면 비워두세요', description: '설명', descriptionPlaceholder: '변수에 대해 설명하세요', - editInJSON: 'JSON으로 편집', + editInJSON: 'JSON 으로 편집', oneByOne: '하나씩 추가', editInForm: '양식에서 편집', arrayValue: '값', @@ -167,36 +176,35 @@ const translation = { stepForward_other: '{{count}} 단계 앞으로', sessionStart: '세션 시작', currentState: '현재 상태', - nodeTitleChange: '블록 제목 변경됨', - nodeDescriptionChange: '블록 설명 변경됨', - nodeDragStop: '블록 이동됨', - nodeChange: '블록 변경됨', - nodeConnect: '블록 연결됨', - nodePaste: '블록 붙여넣기됨', - nodeDelete: '블록 삭제됨', - nodeAdd: '블록 추가됨', - nodeResize: '블록 크기 조정됨', noteAdd: '노트 추가됨', noteChange: '노트 변경됨', noteDelete: '노트 삭제됨', - edgeDelete: '블록 연결 해제됨', + nodeConnect: '노드가 연결되었습니다.', + nodePaste: '노드 붙여넣기', + nodeDelete: '노드가 삭제되었습니다.', + nodeAdd: '노드가 추가되었습니다.', + nodeChange: '노드가 변경되었습니다.', + nodeDescriptionChange: '노드 설명이 변경됨', + nodeResize: '노드 크기 조정됨', + nodeDragStop: '노드가 이동했습니다.', + edgeDelete: '노드가 연결이 끊어졌습니다.', }, errorMsg: { fieldRequired: '{{field}}가 필요합니다', authRequired: '인증이 필요합니다', - invalidJson: '{{field}}는 잘못된 JSON입니다', + invalidJson: '{{field}}는 잘못된 JSON 입니다', fields: { variable: '변수 이름', variableValue: '변수 값', code: '코드', model: '모델', rerankModel: '재정렬 모델', - visionVariable: '시력 변수', + visionVariable: '비전 변수', }, invalidVariable: '잘못된 변수', - rerankModelRequired: 'Rerank Model을 켜기 전에 설정에서 모델이 성공적으로 구성되었는지 확인하십시오.', + rerankModelRequired: 'Rerank Model 을 켜기 전에 설정에서 모델이 성공적으로 구성되었는지 확인하십시오.', noValidTool: '{{field}} 유효한 도구가 선택되지 않았습니다.', - toolParameterRequired: '{{field}}: 매개변수 [{{param}}]이 필요합니다.', + toolParameterRequired: '{{field}}: 매개변수 [{{param}}] 이 필요합니다.', }, singleRun: { testRun: '테스트 실행', @@ -205,10 +213,9 @@ const translation = { testRunIteration: '테스트 실행 반복', back: '뒤로', iteration: '반복', + loop: '루프', }, tabs: { - 'searchBlock': '블록 검색', - 'blocks': '블록', 'tools': '도구', 'allTool': '전체', 'builtInTool': '내장', @@ -222,6 +229,8 @@ const translation = { 'searchTool': '검색 도구', 'plugin': '플러그인', 'agent': '에이전트 전략', + 'blocks': '노드', + 'searchBlock': '검색 노드', }, blocks: { 'start': '시작', @@ -243,6 +252,9 @@ const translation = { 'document-extractor': 'Doc 추출기', 'list-operator': 'List 연산자', 'agent': '대리인', + 'loop-start': '루프 시작', + 'loop-end': '루프 종료', + 'loop': '루프', }, blocksAbout: { 'start': '워크플로우를 시작하기 위한 초기 매개변수를 정의합니다', @@ -250,43 +262,46 @@ const translation = { 'answer': '대화의 답변 내용을 정의합니다', 'llm': '질문에 답하거나 자연어를 처리하기 위해 대형 언어 모델을 호출합니다', 'knowledge-retrieval': '사용자 질문과 관련된 텍스트 콘텐츠를 지식 베이스에서 쿼리할 수 있습니다', - 'question-classifier': '사용자 질문의 분류 조건을 정의합니다. LLM은 분류 설명을 기반으로 대화의 진행 방식을 정의할 수 있습니다', + 'question-classifier': '사용자 질문의 분류 조건을 정의합니다. LLM 은 분류 설명을 기반으로 대화의 진행 방식을 정의할 수 있습니다', 'if-else': 'if/else 조건을 기반으로 워크플로우를 두 가지 분기로 나눌 수 있습니다', 'code': '사용자 정의 논리를 구현하기 위해 Python 또는 NodeJS 코드를 실행합니다', 'template-transform': 'Jinja 템플릿 구문을 사용하여 데이터를 문자열로 변환합니다', 'http-request': 'HTTP 프로토콜을 통해 서버 요청을 보낼 수 있습니다', 'variable-assigner': '다중 분기 변수들을 하나의 변수로 집계하여 다운스트림 노드의 통합 구성을 가능하게 합니다.', - 'assigner': '변수 할당 노드는 쓰기 가능한 변수(대화 변수 등)에 값을 할당하는 데 사용됩니다.', + 'assigner': '변수 할당 노드는 쓰기 가능한 변수 (대화 변수 등) 에 값을 할당하는 데 사용됩니다.', 'variable-aggregator': '다중 분기 변수들을 하나의 변수로 집계하여 다운스트림 노드의 통합 구성을 가능하게 합니다.', 'iteration': '목록 객체에서 여러 단계를 수행하여 모든 결과가 출력될 때까지 반복합니다.', - 'parameter-extractor': '도구 호출 또는 HTTP 요청을 위해 자연어에서 구조화된 매개변수를 추출하기 위해 LLM을 사용합니다.', - 'document-extractor': '업로드된 문서를 LLM에서 쉽게 이해할 수 있는 텍스트 콘텐츠로 구문 분석하는 데 사용됩니다.', + 'parameter-extractor': '도구 호출 또는 HTTP 요청을 위해 자연어에서 구조화된 매개변수를 추출하기 위해 LLM 을 사용합니다.', + 'document-extractor': '업로드된 문서를 LLM 에서 쉽게 이해할 수 있는 텍스트 콘텐츠로 구문 분석하는 데 사용됩니다.', 'list-operator': '배열 내용을 필터링하거나 정렬하는 데 사용됩니다.', 'agent': '질문에 답하거나 자연어를 처리하기 위해 대규모 언어 모델을 호출하는 경우', + 'loop': '종료 조건이 충족되거나 최대 반복 횟수에 도달할 때까지 논리 루프를 실행합니다.', + 'loop-end': '"break"와 동일합니다. 이 노드는 구성 항목이 없습니다. 루프 본문이 이 노드에 도달하면 루프가 종료됩니다.', }, operator: { zoomIn: '확대', zoomOut: '축소', - zoomTo50: '50%로 확대', - zoomTo100: '100%로 확대', + zoomTo50: '50% 로 확대', + zoomTo100: '100% 로 확대', zoomToFit: '화면에 맞게 확대', }, panel: { userInputField: '사용자 입력 필드', - changeBlock: '블록 변경', helpLink: '도움말 링크', about: '정보', createdBy: '작성자 ', nextStep: '다음 단계', - addNextStep: '이 워크플로우의 다음 블록 추가', - selectNextStep: '다음 블록 선택', runThisStep: '이 단계 실행', checklist: '체크리스트', checklistTip: '게시하기 전에 모든 문제가 해결되었는지 확인하세요', checklistResolved: '모든 문제가 해결되었습니다', - organizeBlocks: '블록 정리', change: '변경', optional: '(선택사항)', + moveToThisNode: '이 노드로 이동', + organizeBlocks: '노드 정리하기', + selectNextStep: '다음 단계 선택', + changeBlock: '노드 변경', + addNextStep: '이 워크플로우에 다음 단계를 추가하세요.', }, nodes: { common: { @@ -320,9 +335,9 @@ const translation = { failBranch: { title: '실패 분기', desc: '오류가 발생하면 예외 분기를 실행합니다', - customize: '캔버스로 이동하여 fail branch logic를 사용자 지정합니다.', + customize: '캔버스로 이동하여 fail branch logic 를 사용자 지정합니다.', inLog: '노드 예외는 실패 분기를 자동으로 실행합니다. 노드 출력은 오류 유형 및 오류 메시지를 반환하고 다운스트림으로 전달합니다.', - customizeTip: 'fail 분기가 활성화되면 노드에서 throw된 예외가 프로세스를 종료하지 않습니다. 대신 미리 정의된 실패 분기를 자동으로 실행하여 오류 메시지, 보고서, 수정 사항을 유연하게 제공하거나 작업을 건너뛸 수 있습니다.', + customizeTip: 'fail 분기가 활성화되면 노드에서 throw 된 예외가 프로세스를 종료하지 않습니다. 대신 미리 정의된 실패 분기를 자동으로 실행하여 오류 메시지, 보고서, 수정 사항을 유연하게 제공하거나 작업을 건너뛸 수 있습니다.', }, partialSucceeded: { tip: '프로세스에 {{num}} 노드가 비정상적으로 실행 중입니다. 추적으로 이동하여 로그를 확인하십시오.', @@ -381,7 +396,7 @@ const translation = { variables: '변수', context: '컨텍스트', contextTooltip: '컨텍스트로 지식을 가져올 수 있습니다', - notSetContextInPromptTip: '컨텍스트 기능을 활성화하려면 PROMPT에 컨텍스트 변수를 입력하세요.', + notSetContextInPromptTip: '컨텍스트 기능을 활성화하려면 PROMPT 에 컨텍스트 변수를 입력하세요.', prompt: '프롬프트', roleDescription: { system: '대화를 위한 고급 지침 제공', @@ -403,7 +418,35 @@ const translation = { singleRun: { variable: '변수', }, - sysQueryInUser: '사용자 메시지에 sys.query가 필요합니다', + sysQueryInUser: '사용자 메시지에 sys.query 가 필요합니다', + jsonSchema: { + warningTips: { + saveSchema: '현재 필드의 편집을 완료한 후 스키마를 저장하세요.', + }, + generating: 'JSON 스키마 생성 중...', + apply: '지원하다', + descriptionPlaceholder: '설명을 추가하세요.', + generate: '생성하다', + generatedResult: '생성된 결과', + addField: '필드 추가', + addChildField: '자녀 필드 추가', + generateJsonSchema: 'JSON 스키마 생성', + fieldNamePlaceholder: '필드 이름', + back: '뒤', + instruction: '지침', + resetDefaults: '재설정', + promptTooltip: '텍스트 설명을 표준화된 JSON 스키마 구조로 변환하세요.', + title: '구조화된 출력 스키마', + stringValidations: '문자열 검증', + showAdvancedOptions: '고급 옵션 표시', + promptPlaceholder: '당신의 JSON 스키마를 설명하세요...', + generationTip: '자연어를 사용하여 JSON 스키마를 신속하게 생성할 수 있습니다.', + resultTip: '여기 생성된 결과가 있습니다. 만약 만족하지 않으신다면, 돌아가서 프롬프트를 수정할 수 있습니다.', + regenerate: '재생하다', + required: '필수', + doc: '구조화된 출력에 대해 더 알아보세요.', + import: 'JSON 에서 가져오기', + }, }, knowledgeRetrieval: { queryVariable: '쿼리 변수', @@ -416,16 +459,43 @@ const translation = { url: '세그먼트 URL', metadata: '기타 메타데이터', }, + metadata: { + options: { + disabled: { + title: '사용안함', + subTitle: '메타데이터 필터링을 활성화하지 않음', + }, + automatic: { + desc: '쿼리 변수를 기반으로 메타데이터 필터링 조건을 자동으로 생성합니다.', + subTitle: '사용자 쿼리를 기반으로 메타데이터 필터링 조건을 자동으로 생성합니다.', + title: '자동', + }, + manual: { + subTitle: '메타데이터 필터링 조건을 수동으로 추가합니다.', + title: '수동', + }, + }, + panel: { + title: '메타데이터 필터 조건', + placeholder: '값을 입력하세요', + add: '조건 추가', + search: '메타데이터 검색', + datePlaceholder: '시간을 선택하세요...', + select: '변수 선택...', + conditions: '조건', + }, + title: '메타데이터 필터링', + }, }, http: { inputVars: '입력 변수', api: 'API', - apiPlaceholder: 'URL을 입력하세요, 변수를 삽입하려면 ‘/’를 입력하세요', - notStartWithHttp: 'API는 http:// 또는 https://로 시작해야 합니다', + apiPlaceholder: 'URL 을 입력하세요, 변수를 삽입하려면‘/’를 입력하세요', + notStartWithHttp: 'API 는 http:// 또는 https://로 시작해야 합니다', key: '키', value: '값', bulkEdit: '일괄 편집', - keyValueEdit: '키-값 편집', + keyValueEdit: '키 - 값 편집', headers: '헤더', params: '매개변수', body: '본문', @@ -461,7 +531,7 @@ const translation = { binaryFileVariable: '바이너리 파일 변수', extractListPlaceholder: '목록 항목 인덱스 입력, \'/\' 변수 삽입', curl: { - title: 'cURL에서 가져오기', + title: 'cURL 에서 가져오기', placeholder: '여기에 cURL 문자열 붙여 넣기', }, }, @@ -475,7 +545,7 @@ const translation = { templateTransform: { inputVars: '입력 변수', code: '코드', - codeSupportTip: 'Jinja2만 지원합니다', + codeSupportTip: 'Jinja2 만 지원합니다', outputVars: { output: '변환된 내용', }, @@ -497,14 +567,16 @@ const translation = { 'is not': '아니다', 'empty': '비어 있음', 'not empty': '비어 있지 않음', - 'null': 'null임', - 'not null': 'null이 아님', + 'null': 'null 임', + 'not null': 'null 이 아님', 'regex match': '정규식 일치', 'in': '안으로', 'exists': '존재', 'all of': '모두의', 'not in': '에 없음', 'not exists': '존재하지 않음', + 'after': '후에', + 'before': '전에', }, enterValue: '값 입력', addCondition: '조건 추가', @@ -520,6 +592,7 @@ const translation = { }, select: '고르다', addSubVariable: '하위 변수', + condition: '조건', }, variableAssigner: { title: '변수 할당', @@ -562,6 +635,8 @@ const translation = { 'clear': '초기화', '/=': '/=', 'set': '설정', + 'remove-first': '첫 번째 제거', + 'remove-last': '마지막 제거', }, 'variables': '변수', 'noAssignedVars': '사용 가능한 할당된 변수가 없습니다.', @@ -572,7 +647,6 @@ const translation = { 'varNotSet': '변수가 설정되지 않음', }, tool: { - toAuthorize: '승인하기', inputVars: '입력 변수', outputVars: { text: '도구가 생성한 내용', @@ -585,6 +659,7 @@ const translation = { }, json: '도구로 생성된 JSON', }, + authorize: '권한 부여', }, questionClassifiers: { model: '모델', @@ -623,7 +698,7 @@ const translation = { advancedSetting: '고급 설정', reasoningMode: '추론 모드', reasoningModeTip: '모델의 함수 호출 또는 프롬프트에 대한 지시 응답 능력을 기반으로 적절한 추론 모드를 선택할 수 있습니다.', - isSuccess: '성공 여부. 성공 시 값은 1이고, 실패 시 값은 0입니다.', + isSuccess: '성공 여부. 성공 시 값은 1 이고, 실패 시 값은 0 입니다.', errorReason: '오류 원인', }, iteration: { @@ -728,7 +803,7 @@ const translation = { files: { url: '이미지 URL', upload_file_id: '파일 ID 업로드', - transfer_method: '전송 방법. 값이 remote_url 또는 local_file입니다.', + transfer_method: '전송 방법. 값이 remote_url 또는 local_file 입니다.', type: '지원 유형. 이제 이미지만 지원합니다.', title: '에이전트 생성 파일', }, @@ -750,8 +825,8 @@ const translation = { toolNotAuthorizedTooltip: '{{도구}} 권한이 부여되지 않음', strategyNotFoundDesc: '설치된 플러그인 버전은 이 전략을 제공하지 않습니다.', maxIterations: '최대 반복 횟수', - pluginNotFoundDesc: '이 플러그인은 GitHub에서 설치됩니다. 플러그인으로 이동하여 다시 설치하십시오.', - pluginNotInstalledDesc: '이 플러그인은 GitHub에서 설치됩니다. 플러그인으로 이동하여 다시 설치하십시오.', + pluginNotFoundDesc: '이 플러그인은 GitHub 에서 설치됩니다. 플러그인으로 이동하여 다시 설치하십시오.', + pluginNotInstalledDesc: '이 플러그인은 GitHub 에서 설치됩니다. 플러그인으로 이동하여 다시 설치하십시오.', strategyNotInstallTooltip: '{{strategy}}가 설치되지 않았습니다.', tools: '도구', unsupportedStrategy: '지원되지 않는 전략', @@ -766,6 +841,38 @@ const translation = { toolbox: '도구', linkToPlugin: '플러그인에 대한 링크', }, + loop: { + ErrorMethod: { + removeAbnormalOutput: '비정상적인 출력을 제거하세요.', + operationTerminated: '종료됨', + continueOnError: '오류가 발생해도 계속 진행하세요.', + }, + currentLoop: '현재 루프', + loopMaxCount: '최대 루프 수', + input: '입력', + error_other: '{{count}} 오류', + comma: ',', + loop_one: '{{count}} 루프', + loop_other: '{{count}} 루프', + breakCondition: '루프 종료 조건', + output: '출력 변수', + error_one: '{{count}} 에러', + deleteTitle: '루프 노드를 삭제하시겠습니까?', + deleteDesc: '루프 노드를 삭제하면 모든 자식 노드가 제거됩니다.', + errorResponseMethod: '오류 응답 방법', + exitConditionTip: '루프 노드는 최소한 하나의 종료 조건이 필요합니다.', + finalLoopVariables: '최종 루프 변수', + loopVariables: '루프 변수', + setLoopVariables: '루프 범위 내에서 변수를 설정합니다.', + initialLoopVariables: '초기 루프 변수', + breakConditionTip: '종료 조건과 대화 변수가 있는 루프 내에서만 변수를 참조할 수 있습니다.', + currentLoopCount: '현재 루프 카운트: {{count}}', + loopMaxCountError: '유효한 최대 루프 수를 입력하십시오. 범위는 1 에서 {{maxCount}}입니다.', + totalLoopCount: '총 루프 횟수: {{count}}', + variableName: '변수 이름', + loopNode: '루프 노드', + inputMode: '입력 모드', + }, }, tracing: { stopBy: '{{user}}에 의해 중지됨', @@ -777,6 +884,38 @@ const translation = { noAssignedVars: '사용 가능한 할당된 변수가 없습니다.', assignedVarsDescription: '할당된 변수는 다음과 같이 쓰기 가능한 변수여야 합니다.', }, + versionHistory: { + filter: { + onlyYours: '오직 너의 것만', + all: '모든', + reset: '필터 재설정', + onlyShowNamedVersions: '이름이 붙은 버전만 표시', + empty: '일치하는 버전 기록이 없습니다.', + }, + editField: { + titleLengthLimit: '제목은 {{limit}}자를 초과할 수 없습니다.', + title: '제목', + releaseNotes: '릴리스 노트', + releaseNotesLengthLimit: '릴리스 노트는 {{limit}}자를 초과할 수 없습니다.', + }, + action: { + updateFailure: '버전 업데이트에 실패했습니다.', + restoreSuccess: '복원된 버전', + deleteSuccess: '버전 삭제됨', + restoreFailure: '버전을 복원하지 못했습니다.', + deleteFailure: '버전을 삭제하지 못했습니다.', + updateSuccess: '버전이 업데이트되었습니다.', + }, + editVersionInfo: '버전 정보 편집', + latest: '최신', + currentDraft: '현재 초안', + releaseNotesPlaceholder: '변경된 내용을 설명하세요.', + defaultName: '제목 없는 버전', + nameThisVersion: '이 버전의 이름을 지어주세요', + title: '버전들', + deletionTip: '삭제는 되돌릴 수 없으니, 확인해 주시기 바랍니다.', + restorationTip: '버전 복원 후 현재 초안이 덮어쓰여질 것입니다.', + }, } export default translation diff --git a/web/i18n/language.ts b/web/i18n/language.ts index c86d31ffa0..a31f9e9c4b 100644 --- a/web/i18n/language.ts +++ b/web/i18n/language.ts @@ -39,6 +39,24 @@ export const getLanguage = (locale: string) => { return LanguagesSupported[0].replace('-', '_') } +const DOC_LANGUAGE: Record<string, string> = { + 'zh-Hans': 'zh-hans', + 'ja-JP': 'ja-jp', + 'en-US': 'en', +} + +export const getDocLanguage = (locale: string) => { + return DOC_LANGUAGE[locale] || 'en' +} + +const PRICING_PAGE_LANGUAGE: Record<string, string> = { + 'ja-JP': 'jp', +} + +export const getPricingPageLanguage = (locale: string) => { + return PRICING_PAGE_LANGUAGE[locale] || '' +} + export const NOTICE_I18N = { title: { en_US: 'Important Notice', @@ -74,7 +92,7 @@ export const NOTICE_I18N = { ja_JP: 'Our system will be unavailable from 19:00 to 24:00 UTC on August 28 for an upgrade. For questions, kindly contact our support team (support@dify.ai). We value your patience.', ko_KR: - '시스템이 업그레이드를 위해 UTC 시간대로 8월 28일 19:00 ~ 24:00에 사용 불가될 예정입니다. 질문이 있으시면 지원 팀에 연락주세요 (support@dify.ai). 최선을 다해 답변해드리겠습니다.', + '시스템이 업그레이드를 위해 UTC 시간대로 8 월 28 일 19:00 ~ 24:00 에 사용 불가될 예정입니다. 질문이 있으시면 지원 팀에 연락주세요 (support@dify.ai). 최선을 다해 답변해드리겠습니다.', pl_PL: 'Nasz system będzie niedostępny od 19:00 do 24:00 UTC 28 sierpnia w celu aktualizacji. W przypadku pytań prosimy o kontakt z naszym zespołem wsparcia (support@dify.ai). Doceniamy Twoją cierpliwość.', uk_UA: diff --git a/web/i18n/pl-PL/app-debug.ts b/web/i18n/pl-PL/app-debug.ts index cf7232e563..48b44c0cbb 100644 --- a/web/i18n/pl-PL/app-debug.ts +++ b/web/i18n/pl-PL/app-debug.ts @@ -309,6 +309,7 @@ const translation = { 'labelName': 'Nazwa etykiety', 'inputPlaceholder': 'Proszę wpisać', 'required': 'Wymagane', + 'hide': 'Ukryj', 'errorMsg': { varNameRequired: 'Wymagana nazwa zmiennej', labelNameRequired: 'Wymagana nazwa etykiety', diff --git a/web/i18n/pl-PL/app-overview.ts b/web/i18n/pl-PL/app-overview.ts index 7459c0fe05..8ac97e6277 100644 --- a/web/i18n/pl-PL/app-overview.ts +++ b/web/i18n/pl-PL/app-overview.ts @@ -38,15 +38,15 @@ const translation = { preview: 'Podgląd', regenerate: 'Wygeneruj ponownie', regenerateNotice: 'Czy chcesz wygenerować ponownie publiczny adres URL?', - preUseReminder: 'Przed kontynuowaniem włącz aplikację WebApp.', + preUseReminder: 'Przed kontynuowaniem włącz aplikację web app.', settings: { entry: 'Ustawienia', - title: 'Ustawienia WebApp', - webName: 'Nazwa WebApp', - webDesc: 'Opis WebApp', + title: 'Ustawienia web app', + webName: 'Nazwa web app', + webDesc: 'Opis web app', webDescTip: 'Ten tekst będzie wyświetlany po stronie klienta, zapewniając podstawowe wskazówki, jak korzystać z aplikacji', - webDescPlaceholder: 'Wpisz opis WebApp', + webDescPlaceholder: 'Wpisz opis web app', language: 'Język', workflow: { title: 'Kroki przepływu pracy', diff --git a/web/i18n/pl-PL/app.ts b/web/i18n/pl-PL/app.ts index 562962bf38..856a64c868 100644 --- a/web/i18n/pl-PL/app.ts +++ b/web/i18n/pl-PL/app.ts @@ -84,21 +84,21 @@ const translation = { noTemplateFound: 'Nie znaleziono szablonów', chatbotUserDescription: 'Szybko zbuduj chatbota opartego na LLM z prostą konfiguracją. Możesz przełączyć się na Chatflow później.', optional: 'Fakultatywny', - workflowUserDescription: 'Orkiestracja przepływu pracy dla zadań jednoetapowych, takich jak automatyzacja i przetwarzanie wsadowe.', + workflowUserDescription: 'Twórz autonomiczne przepływy AI wizualnie, z prostotą przeciągnij i upuść.', completionUserDescription: 'Szybko zbuduj asystenta AI do zadań generowania tekstu za pomocą prostej konfiguracji.', - forBeginners: 'DLA POCZĄTKUJĄCYCH', + forBeginners: 'Prostsze typy aplikacji', agentShortDescription: 'Inteligentny agent z rozumowaniem i autonomicznym wykorzystaniem narzędzi', completionShortDescription: 'Asystent AI do zadań generowania tekstu', noIdeaTip: 'Nie masz pomysłów? Sprawdź nasze szablony', forAdvanced: 'DLA ZAAWANSOWANYCH UŻYTKOWNIKÓW', foundResult: '{{liczba}} Wynik', - advancedShortDescription: 'Przepływ pracy dla złożonych, wieloetapowych dialogów z pamięcią', + advancedShortDescription: 'Przepływ ulepszony dla wieloturowych czatów', learnMore: 'Dowiedz się więcej', chatbotShortDescription: 'Chatbot oparty na LLM z prostą konfiguracją', chooseAppType: 'Wybierz typ aplikacji', agentUserDescription: 'Inteligentny agent zdolny do iteracyjnego wnioskowania i autonomicznego wykorzystania narzędzi do osiągania celów zadań.', - workflowShortDescription: 'Orkiestracja dla jednoetapowych zadań automatyzacji', - advancedUserDescription: 'Orkiestracja przepływu pracy dla wielorundowych, złożonych zadań dialogowych z funkcjami pamięci.', + workflowShortDescription: 'Agentowy przepływ dla inteligentnych automatyzacji', + advancedUserDescription: 'Przepływ z dodatkowymi funkcjami pamięci i interfejsem chatbota.', }, editApp: 'Edytuj informacje', editAppTitle: 'Edytuj informacje o aplikacji', @@ -166,10 +166,14 @@ const translation = { description: 'Opik to platforma typu open source do oceny, testowania i monitorowania aplikacji LLM.', title: 'Opik', }, + weave: { + title: 'Tkaj', + description: 'Weave to platforma open-source do oceny, testowania i monitorowania aplikacji LLM.', + }, }, answerIcon: { description: 'Czy w aplikacji udostępnionej ma być używana ikona aplikacji internetowej do zamiany 🤖.', - title: 'Użyj ikony WebApp, aby zastąpić 🤖', + title: 'Użyj ikony web app, aby zastąpić 🤖', descriptionInExplore: 'Czy używać ikony aplikacji internetowej do zastępowania 🤖 w Eksploruj', }, importFromDSL: 'Importowanie z DSL', @@ -201,6 +205,54 @@ const translation = { placeholder: 'Wybierz aplikację...', label: 'Aplikacja', }, + structOutput: { + structured: 'Ustrukturyzowany', + LLMResponse: 'Odpowiedź LLM', + notConfiguredTip: 'Strukturalne wyjście nie zostało jeszcze skonfigurowane', + structuredTip: 'Strukturalne wyniki to funkcja, która zapewnia, że model zawsze generuje odpowiedzi zgodne z dostarczonym schematem JSON.', + moreFillTip: 'Pokazując maksymalnie 10 poziomów zagnieżdżenia', + configure: 'Konfiguruj', + required: 'Wymagane', + modelNotSupported: 'Model nie jest obsługiwany', + modelNotSupportedTip: 'Aktualny model nie obsługuje tej funkcji i zostaje automatycznie obniżony do wstrzyknięcia zapytania.', + }, + accessItemsDescription: { + anyone: 'Każdy może uzyskać dostęp do aplikacji webowej', + specific: 'Tylko określone grupy lub członkowie mogą uzyskać dostęp do aplikacji internetowej', + organization: 'Każdy w organizacji ma dostęp do aplikacji internetowej.', + external: 'Tylko uwierzytelnieni zewnętrzni użytkownicy mogą uzyskać dostęp do aplikacji internetowej.', + }, + accessControlDialog: { + accessItems: { + anyone: 'Każdy z linkiem', + specific: 'Specyficzne grupy lub członkowie', + organization: 'Tylko członkowie w obrębie przedsiębiorstwa', + external: 'Uwierzytelnieni użytkownicy zewnętrzni', + }, + operateGroupAndMember: { + searchPlaceholder: 'Szukaj grup i członków', + allMembers: 'Wszyscy członkowie', + expand: 'Rozszerz', + noResult: 'Brak wyniku', + }, + title: 'Kontrola dostępu do aplikacji internetowej', + description: 'Ustaw uprawnienia dostępu do aplikacji webowej', + accessLabel: 'Kto ma dostęp', + groups_one: '{{count}} GRUPA', + groups_other: '{{count}} GRUPY', + members_one: '{{count}} CZŁONEK', + members_other: '{{count}} CZŁONKÓW', + noGroupsOrMembers: 'Nie wybrano żadnych grup ani członków', + webAppSSONotEnabledTip: 'Proszę skontaktować się z administratorem przedsiębiorstwa, aby skonfigurować metodę uwierzytelniania aplikacji internetowej.', + updateSuccess: 'Aktualizacja powiodła się', + }, + publishApp: { + title: 'Kto ma dostęp do aplikacji internetowej', + notSet: 'Nie ustawiono', + notSetDesc: 'Obecnie nikt nie może uzyskać dostępu do aplikacji internetowej. Proszę ustawić uprawnienia.', + }, + accessControl: 'Kontrola dostępu do aplikacji internetowej', + noAccessPermission: 'Brak uprawnień do dostępu do aplikacji internetowej', } export default translation diff --git a/web/i18n/pl-PL/billing.ts b/web/i18n/pl-PL/billing.ts index cff567e162..00284109e8 100644 --- a/web/i18n/pl-PL/billing.ts +++ b/web/i18n/pl-PL/billing.ts @@ -75,6 +75,7 @@ const translation = { title: 'Limity kredytów wiadomości', tooltip: 'Limity wywołań wiadomości dla różnych planów używających modeli OpenAI (z wyjątkiem gpt4). Wiadomości przekraczające limit będą korzystać z twojego klucza API OpenAI.', + titlePerMonth: '{{count,number}} wiadomości/miesiąc', }, annotatedResponse: { title: 'Limity kredytów na adnotacje', @@ -86,30 +87,97 @@ const translation = { receiptInfo: 'Tylko właściciel zespołu i administrator zespołu mogą subskrybować i przeglądać informacje o rozliczeniach', annotationQuota: 'Przydział adnotacji', + documents: '{{count,number}} Dokumentów Wiedzy', + apiRateLimit: 'Limit liczby wywołań API', + documentsTooltip: 'Kwota dotycząca liczby dokumentów importowanych z Źródła Danych Wiedzy.', + unlimitedApiRate: 'Brak limitu liczby zapytań API', + annualBilling: 'Roczne rozliczenie', + getStarted: 'Zacznij', + freeTrialTip: 'bezpłatny okres próbny 200 wywołań OpenAI.', + comparePlanAndFeatures: 'Porównaj plany i funkcje', + freeTrialTipPrefix: 'Zarejestruj się i zdobądź', + teamMember_other: '{{count,number}} członków zespołu', + teamWorkspace: '{{count,number}} Zespół Workspace', + apiRateLimitUnit: '{{count,number}}/dzień', + cloud: 'Usługa chmurowa', + teamMember_one: '{{count,number}} Członek zespołu', + priceTip: 'na przestrzeń roboczą/', + self: 'Samo-hostowane', + apiRateLimitTooltip: 'Limit aktywności API dotyczy wszystkich żądań składanych za pośrednictwem API Dify, w tym generowania tekstu, rozmów czatowych, wykonywania przepływów pracy i przetwarzania dokumentów.', + freeTrialTipSuffix: 'Nie jest wymagana karta kredytowa', + documentsRequestQuota: '{{count,number}}/min Limit wiedzy na żądanie', + documentsRequestQuotaTooltip: 'Określa całkowitą liczbę działań, jakie przestrzeń robocza może wykonać na minutę w ramach bazy wiedzy, w tym tworzenie zbiorów danych, usuwanie, aktualizacje, przesyłanie dokumentów, modyfikacje, archiwizowanie i zapytania do bazy wiedzy. Ta metryka jest używana do oceny wydajności zapytań do bazy wiedzy. Na przykład, jeśli użytkownik Sandbox wykona 10 kolejnych testów w ciągu jednej minuty, jego przestrzeń robocza zostanie tymczasowo ograniczona w wykonywaniu następujących działań przez następną minutę: tworzenie zbiorów danych, usuwanie, aktualizacje oraz przesyłanie lub modyfikacje dokumentów.', }, plans: { sandbox: { name: 'Sandbox', description: '200 razy darmowa próba GPT', includesTitle: 'Zawiera:', + for: 'Darmowy okres próbny podstawowych funkcji', }, professional: { name: 'Profesjonalny', description: 'Dla osób fizycznych i małych zespołów, aby odblokować więcej mocy w przystępnej cenie.', includesTitle: 'Wszystko w darmowym planie, plus:', + for: 'Dla niezależnych deweloperów/małych zespołów', }, team: { name: 'Zespół', description: 'Współpracuj bez ograniczeń i ciesz się najwyższą wydajnością.', includesTitle: 'Wszystko w planie Profesjonalnym, plus:', + for: 'Dla średniej wielkości zespołów', }, enterprise: { name: 'Przedsiębiorstwo', description: 'Uzyskaj pełne możliwości i wsparcie dla systemów o kluczowym znaczeniu dla misji.', includesTitle: 'Wszystko w planie Zespołowym, plus:', + features: { + 3: 'Wiele przestrzeni roboczych i zarządzanie przedsiębiorstwem', + 5: 'Wynegocjowane SLA przez Dify Partners', + 0: 'Rozwiązania do wdrożeń na dużą skalę klasy przedsiębiorstw', + 8: 'Profesjonalne wsparcie techniczne', + 2: 'Ekskluzywne funkcje przedsiębiorstwa', + 6: 'Zaawansowane zabezpieczenia i kontrola', + 7: 'Aktualizacje i konserwacja przez Dify Oficjalnie', + 4: 'SSO', + 1: 'Autoryzacja licencji komercyjnej', + }, + priceTip: 'Tylko roczne fakturowanie', + btnText: 'Skontaktuj się z działem sprzedaży', + for: 'Dla dużych zespołów', + price: 'Niestety, nie mogę przetłumaczyć tego tekstu bez konkretnego zdania do przetłumaczenia.', + }, + community: { + features: { + 0: 'Wszystkie funkcje podstawowe wydane w publicznym repozytorium', + 1: 'Jedno Miejsce Pracy', + 2: 'Zgodne z licencją Dify Open Source', + }, + includesTitle: 'Darmowe funkcje:', + name: 'Społeczność', + price: 'Darmowy', + description: 'Dla użytkowników indywidualnych, małych zespołów lub projektów niekomercyjnych', + btnText: 'Rozpocznij pracę z społecznością', + for: 'Dla użytkowników indywidualnych, małych zespołów lub projektów niekomercyjnych', + }, + premium: { + features: { + 0: 'Samozarządzana niezawodność różnych dostawców chmury', + 1: 'Jedno miejsce pracy', + 3: 'Priorytetowe wsparcie przez e-mail i czat', + 2: 'Logo aplikacji internetowej i dostosowanie marki', + }, + description: 'Dla średnich organizacji i zespołów', + for: 'Dla średnich organizacji i zespołów', + name: 'Premium', + priceTip: 'Oparte na rynku chmurowym', + btnText: 'Uzyskaj premium w', + price: 'Skalowalny', + comingSoon: 'Wsparcie dla Microsoft Azure i Google Cloud wkrótce dostępne', + includesTitle: 'Wszystko z Community, plus:', }, }, vectorSpace: { @@ -119,12 +187,26 @@ const translation = { apps: { fullTipLine1: 'Ulepsz swój plan, aby', fullTipLine2: 'tworzyć więcej aplikacji.', + fullTip1des: 'Osiągnąłeś limit tworzenia aplikacji w tym planie.', + fullTip1: 'Zaktualizuj, aby stworzyć więcej aplikacji', + fullTip2: 'Osiągnięto limit planu', + contactUs: 'Skontaktuj się z nami', + fullTip2des: 'Zaleca się usunięcie nieaktywnych aplikacji, aby zwolnić miejsce, lub skontaktowanie się z nami.', }, annotatedResponse: { fullTipLine1: 'Ulepsz swój plan, aby', fullTipLine2: 'adnotować więcej rozmów.', quotaTitle: 'Limit adnotacji odpowiedzi', }, + usagePage: { + vectorSpace: 'Magazynowanie danych wiedzy', + teamMembers: 'Członkowie zespołu', + documentsUploadQuota: 'Limit przesyłania dokumentów', + buildApps: 'Twórz aplikacje', + annotationQuota: 'Kwota aneksji', + vectorSpaceTooltip: 'Dokumenty z trybem indeksowania o wysokiej jakości będą zużywać zasoby magazynu danych wiedzy. Gdy magazyn danych wiedzy osiągnie limit, nowe dokumenty nie będą przesyłane.', + }, + teamMembers: 'Członkowie zespołu', } export default translation diff --git a/web/i18n/pl-PL/common.ts b/web/i18n/pl-PL/common.ts index c8b0b79257..e081a1ed9e 100644 --- a/web/i18n/pl-PL/common.ts +++ b/web/i18n/pl-PL/common.ts @@ -54,6 +54,10 @@ const translation = { copied: 'Kopiowane', in: 'w', viewDetails: 'Wyświetl szczegóły', + format: 'Format', + downloadFailed: 'Pobieranie nie powiodło się. Proszę spróbować ponownie później.', + more: 'Więcej', + downloadSuccess: 'Pobieranie zakończone.', }, placeholder: { input: 'Proszę wprowadzić', @@ -146,6 +150,8 @@ const translation = { newDataset: 'Utwórz Wiedzę', tools: 'Narzędzia', exploreMarketplace: 'Zapoznaj się z Marketplace', + appDetail: 'Szczegóły aplikacji', + account: 'klient', }, userProfile: { settings: 'Ustawienia', @@ -158,6 +164,9 @@ const translation = { community: 'Społeczność', about: 'O', logout: 'Wyloguj się', + support: 'Wsparcie', + github: 'GitHub', + compliance: 'Zgodność', }, settings: { accountGroup: 'KONTO', @@ -208,6 +217,9 @@ const translation = { feedbackTitle: 'Sprzężenie zwrotne', feedbackLabel: 'Powiedz nam, dlaczego usunąłeś swoje konto?', feedbackPlaceholder: 'Fakultatywny', + workspaceIcon: 'Ikona robocza', + workspaceName: 'Nazwa miejsca pracy', + editWorkspaceInfo: 'Edytuj informacje o przestrzeni roboczej', }, members: { team: 'Zespół', @@ -469,7 +481,7 @@ const translation = { title: 'Rozszerzenia oparte na interfejsie API zapewniają scentralizowane zarządzanie interfejsami API, upraszczając konfigurację dla łatwego użytkowania w aplikacjach Dify.', link: 'Dowiedz się, jak opracować własne rozszerzenie interfejsu API.', - linkUrl: 'https://docs.dify.ai/features/extension/api_based_extension', + linkUrl: 'https://docs.dify.ai/en/guides/extension/api-based-extension/README', add: 'Dodaj rozszerzenie interfejsu API', selector: { title: 'Rozszerzenie interfejsu API', @@ -555,9 +567,10 @@ const translation = { vectorHash: 'Wektor hash:', hitScore: 'Wynik trafień:', }, - inputPlaceholder: 'Porozmawiaj z botem', + inputPlaceholder: 'Porozmawiaj z {{botName}}', thought: 'Myśl', thinking: 'Myślenie...', + resend: 'Prześlij ponownie', }, promptEditor: { placeholder: @@ -651,10 +664,31 @@ const translation = { license: { expiring_plural: 'Wygasa za {{count}} dni', expiring: 'Wygasa w ciągu jednego dnia', + unlimited: 'Nieograniczony', }, pagination: { perPage: 'Ilość elementów na stronie', }, + theme: { + light: 'światło', + theme: 'Temat', + dark: 'ciemny', + auto: 'system', + }, + compliance: { + soc2Type2: 'Raport SOC 2 Typ II', + sandboxUpgradeTooltip: 'Dostępne tylko w planie Professional lub Team.', + professionalUpgradeTooltip: 'Dostępne tylko w planie zespołowym lub wyższym.', + iso27001: 'Certyfikacja ISO 27001:2022', + soc2Type1: 'Raport SOC 2 Typ I', + gdpr: 'GDPR DPA', + }, + imageInput: { + dropImageHere: 'Upuść swój obraz tutaj, lub', + browse: 'przeglądaj', + supportedFormats: 'Obsługuje PNG, JPG, JPEG, WEBP i GIF', + }, + you: 'Ty', } export default translation diff --git a/web/i18n/pl-PL/custom.ts b/web/i18n/pl-PL/custom.ts index 15d71cceea..8703ebb817 100644 --- a/web/i18n/pl-PL/custom.ts +++ b/web/i18n/pl-PL/custom.ts @@ -3,6 +3,8 @@ const translation = { upgradeTip: { prefix: 'Zaktualizuj swój plan, aby', suffix: 'dostosować swoją markę.', + title: 'Zmień swój plan', + des: 'Zaktualizuj swój plan, aby dostosować swoją markę', }, webapp: { title: 'Dostosuj markę aplikacji internetowej', diff --git a/web/i18n/pl-PL/dataset-creation.ts b/web/i18n/pl-PL/dataset-creation.ts index 553e3808d1..236202d867 100644 --- a/web/i18n/pl-PL/dataset-creation.ts +++ b/web/i18n/pl-PL/dataset-creation.ts @@ -22,7 +22,7 @@ const translation = { }, uploader: { title: 'Prześlij plik tekstowy', - button: 'Przeciągnij i upuść plik lub', + button: 'Przeciągnij i upuść pliki lub foldery lub', browse: 'Przeglądaj', tip: 'Obsługuje {{supportTypes}}. Maksymalnie {{size}}MB każdy.', validation: { @@ -54,7 +54,7 @@ const translation = { }, website: { limit: 'Ograniczać', - firecrawlDocLink: 'https://docs.dify.ai/guides/knowledge-base/sync-from-website', + firecrawlDocLink: 'https://docs.dify.ai/en/guides/knowledge-base/create-knowledge-and-upload-documents/import-content-data/sync-from-website', firecrawlDoc: 'Dokumentacja Firecrawl', unknownError: 'Nieznany błąd', fireCrawlNotConfiguredDescription: 'Skonfiguruj Firecrawl z kluczem API, aby z niego korzystać.', @@ -83,6 +83,14 @@ const translation = { jinaReaderDoc: 'Dowiedz się więcej o Jina Reader', jinaReaderTitle: 'Konwertowanie całej witryny na język Markdown', jinaReaderNotConfiguredDescription: 'Skonfiguruj Jina Reader, wprowadzając bezpłatny klucz API, aby uzyskać dostęp.', + watercrawlTitle: 'Wyodrębnij treści z sieci za pomocą Watercrawl', + configureWatercrawl: 'Skonfiguruj Watercrawl', + watercrawlDocLink: 'https://docs.dify.ai/en/guides/knowledge-base/create-knowledge-and-upload-documents/import-content-data/sync-from-website', + configureJinaReader: 'Skonfiguruj Czytnik Jina', + configureFirecrawl: 'Skonfiguruj Firecrawl', + watercrawlDoc: 'Dokumentacja Watercrawl', + waterCrawlNotConfiguredDescription: 'Skonfiguruj Watercrawl z kluczem API, aby go używać.', + waterCrawlNotConfigured: 'Watercrawl nie jest skonfigurowany', }, cancel: 'Anuluj', }, @@ -216,6 +224,11 @@ const translation = { title: 'Połączyć się z innymi źródłami danych?', description: 'Obecnie baza wiedzy Dify ma tylko ograniczone źródła danych. Dodanie źródła danych do bazy wiedzy Dify to fantastyczny sposób na zwiększenie elastyczności i możliwości platformy dla wszystkich użytkowników. Nasz przewodnik po wkładach ułatwia rozpoczęcie pracy. Kliknij poniższy link, aby dowiedzieć się więcej.', }, + watercrawl: { + apiKeyPlaceholder: 'Klucz API z watercrawl.dev', + configWatercrawl: 'Skonfiguruj Watercrawl', + getApiKeyLinkText: 'Uzyskaj swój klucz API z watercrawl.dev', + }, } export default translation diff --git a/web/i18n/pl-PL/dataset-settings.ts b/web/i18n/pl-PL/dataset-settings.ts index 94099708b7..03462ada05 100644 --- a/web/i18n/pl-PL/dataset-settings.ts +++ b/web/i18n/pl-PL/dataset-settings.ts @@ -30,6 +30,7 @@ const translation = { description: ' dotyczące metody doboru.', longDescription: ' dotyczące metody doboru, możesz to zmienić w dowolnym momencie w ustawieniach wiedzy.', + method: 'Metoda pozyskiwania', }, save: 'Zapisz', permissionsInvitedMembers: 'Częściowi członkowie zespołu', diff --git a/web/i18n/pl-PL/dataset.ts b/web/i18n/pl-PL/dataset.ts index 9a5ed10a5a..3006c46c97 100644 --- a/web/i18n/pl-PL/dataset.ts +++ b/web/i18n/pl-PL/dataset.ts @@ -174,6 +174,54 @@ const translation = { enable: 'Umożliwiać', allKnowledge: 'Cała wiedza', allKnowledgeDescription: 'Wybierz tę opcję, aby wyświetlić całą wiedzę w tym obszarze roboczym. Tylko właściciel obszaru roboczego może zarządzać całą wiedzą.', + metadata: { + createMetadata: { + back: 'Tył', + namePlaceholder: 'Dodaj nazwę metadanych', + name: 'Imię', + title: 'Nowe metadane', + type: 'Typ', + }, + checkName: { + empty: 'Nazwa metadanych nie może być pusta', + invalid: 'Nazwa metadanych może zawierać tylko małe litery, cyfry i podkreślenia oraz musi zaczynać się od małej litery', + }, + batchEditMetadata: { + multipleValue: 'Wielokrotna wartość', + editMetadata: 'Edytuj metadane', + editDocumentsNum: 'Edycja {{num}} dokumentów', + applyToAllSelectDocument: 'Zastosuj do wszystkich wybranych dokumentów', + applyToAllSelectDocumentTip: 'Automatycznie utwórz wszystkie powyżej wymienione edytowane i nowe metadane dla wszystkich wybranych dokumentów, w przeciwnym razie edytowanie metadanych będzie dotyczyć tylko dokumentów, które je posiadają.', + }, + selectMetadata: { + manageAction: 'Zarządzaj', + newAction: 'Nowe metadane', + search: 'Szukaj metadanych', + }, + datasetMetadata: { + values: '{{num}} Wartości', + rename: 'Zmień nazwę', + namePlaceholder: 'Nazwa metadanych', + addMetaData: 'Dodaj metadane', + deleteContent: 'Czy na pewno chcesz usunąć metadane "{{name}}"?', + builtIn: 'Wbudowany', + deleteTitle: 'Potwierdź usunięcie', + description: 'Możesz zarządzać wszystkimi metadanymi w tej wiedzy tutaj. Modyfikacje będą synchronizowane z każdym dokumentem.', + name: 'Imię', + disabled: 'Wyłączone', + builtInDescription: 'Wbudowane metadane są automatycznie ekstraktowane i generowane. Muszą być włączone przed użyciem i nie można ich edytować.', + }, + documentMetadata: { + technicalParameters: 'Parametry techniczne', + startLabeling: 'Rozpocznij etykietowanie', + documentInformation: 'Informacje o dokumencie', + metadataToolTip: 'Metadane służą jako istotny filtr, który zwiększa dokładność i trafność wyszukiwania informacji. Możesz modyfikować i dodawać metadane do tego dokumentu tutaj.', + }, + metadata: 'Metadane', + addMetadata: 'Dodaj metadane', + chooseTime: 'Wybierz czas...', + }, + embeddingModelNotAvailable: 'Model osadzający jest niedostępny.', } export default translation diff --git a/web/i18n/pl-PL/education.ts b/web/i18n/pl-PL/education.ts new file mode 100644 index 0000000000..a7684c2ae8 --- /dev/null +++ b/web/i18n/pl-PL/education.ts @@ -0,0 +1,47 @@ +const translation = { + toVerifiedTip: { + coupon: 'ekskluzywny kupon 100%', + front: 'Teraz jesteś uprawniony do statusu zweryfikowanej edukacji. Proszę wprowadzić swoje informacje edukacyjne poniżej, aby zakończyć proces i otrzymać', + end: 'dla Profesjonalnego Planu Dify.', + }, + form: { + schoolName: { + title: 'Nazwa Twojej Szkoły', + placeholder: 'Wpisz oficjalną, pełną nazwę swojej szkoły', + }, + schoolRole: { + option: { + student: 'Uczniowie', + teacher: 'Nauczyciel', + administrator: 'Administrator szkoły', + }, + title: 'Twoja rola w szkole', + }, + terms: { + desc: { + termsOfService: 'Warunki świadczenia usług', + privacyPolicy: 'Polityka prywatności', + and: 'i', + front: 'Twoje informacje i użycie statusu Weryfikowanej Edukacji podlegają naszym', + end: 'Przez przesłanie:', + }, + option: { + age: 'Potwierdzam, że mam co najmniej 18 lat', + inSchool: 'Potwierdzam, że jestem zapisany lub zatrudniony w podanej instytucji. Dify może wymagać dowodu zapisania/zatrudnienia. Jeśli wprowadzę w błąd dotyczący mojej zdolności do uczestnictwa, zgadzam się zapłacić wszelkie opłaty, które zostały początkowo zaniechane w oparciu o mój status edukacyjny.', + }, + title: 'Warunki i umowy', + }, + }, + toVerified: 'Uzyskaj potwierdzenie edukacji', + submit: 'Zatwierdź', + rejectContent: 'Niestety, nie kwalifikujesz się do statusu Zweryfikowanej Edukacji i w związku z tym nie możesz otrzymać ekskluzywnego kuponu 100% na plan Dify Professional, jeśli korzystasz z tego adresu e-mail.', + successContent: 'Wydaliśmy kupon rabatowy na 100% dla planu Dify Professional na Twoje konto. Kupon jest ważny przez jeden rok, prosimy o jego użycie w okresie ważności.', + currentSigned: 'AKTUALNIE ZALOGOWANY JAKO', + successTitle: 'Masz zweryfikowane wykształcenie Dify', + rejectTitle: 'Twoja weryfikacja edukacyjna Dify została odrzucona', + learn: 'Dowiedz się, jak uzyskać potwierdzenie wykształcenia', + emailLabel: 'Twój aktualny email', + submitError: 'Przesłanie formularza nie powiodło się. Proszę spróbować ponownie później.', +} + +export default translation diff --git a/web/i18n/pl-PL/explore.ts b/web/i18n/pl-PL/explore.ts index 58c691119c..f9e8b30f8b 100644 --- a/web/i18n/pl-PL/explore.ts +++ b/web/i18n/pl-PL/explore.ts @@ -38,6 +38,7 @@ const translation = { HR: 'HR', Agent: 'Agent', Workflow: 'Przepływ pracy', + Entertainment: 'Rozrywka', }, } diff --git a/web/i18n/pl-PL/login.ts b/web/i18n/pl-PL/login.ts index 99719fe71a..b1bb0b93c6 100644 --- a/web/i18n/pl-PL/login.ts +++ b/web/i18n/pl-PL/login.ts @@ -110,6 +110,11 @@ const translation = { licenseExpiredTip: 'Licencja Dify Enterprise dla Twojego obszaru roboczego wygasła. Skontaktuj się z administratorem, aby kontynuować korzystanie z Dify.', licenseLostTip: 'Nie udało się nawiązać połączenia z serwerem licencji Dify. Skontaktuj się z administratorem, aby kontynuować korzystanie z Dify.', licenseInactiveTip: 'Licencja Dify Enterprise dla Twojego obszaru roboczego jest nieaktywna. Skontaktuj się z administratorem, aby kontynuować korzystanie z Dify.', + webapp: { + noLoginMethod: 'Metoda uwierzytelniania nie jest skonfigurowana dla aplikacji internetowej', + noLoginMethodTip: 'Proszę skontaktować się z administratorem systemu, aby dodać metodę uwierzytelniania.', + disabled: 'Uwierzytelnianie aplikacji internetowej jest wyłączone. Proszę skontaktować się z administratorem systemu, aby je włączyć. Możesz spróbować użyć aplikacji bezpośrednio.', + }, } export default translation diff --git a/web/i18n/pl-PL/plugin.ts b/web/i18n/pl-PL/plugin.ts index e04068e59d..d2c3df24f0 100644 --- a/web/i18n/pl-PL/plugin.ts +++ b/web/i18n/pl-PL/plugin.ts @@ -62,6 +62,7 @@ const translation = { uninstalledTitle: 'Narzędzie nie jest zainstalowane', paramsTip2: 'Gdy opcja "Automatycznie" jest wyłączona, używana jest wartość domyślna.', toolLabel: 'Narzędzie', + toolSetting: 'Ustawienia narzędzi', }, strategyNum: '{{liczba}} {{strategia}} ZAWARTE', endpointsEmpty: 'Kliknij przycisk "+", aby dodać punkt końcowy', @@ -180,6 +181,8 @@ const translation = { difyMarketplace: 'Rynek Dify', noPluginFound: 'Nie znaleziono wtyczki', pluginsResult: '{{num}} wyniki', + partnerTip: 'Zweryfikowane przez partnera Dify', + verifiedTip: 'Zweryfikowane przez Dify', }, task: { installError: 'Nie udało się zainstalować wtyczek {{errorLength}}, kliknij, aby wyświetlić', @@ -203,7 +206,12 @@ const translation = { fromMarketplace: 'Z Marketplace', searchPlugins: 'Wtyczki wyszukiwania', searchTools: 'Narzędzia wyszukiwania...', - submitPlugin: 'Prześlij wtyczkę', + metadata: { + title: 'Wtyczki', + }, + difyVersionNotCompatible: 'Obecna wersja Dify nie jest kompatybilna z tym wtyczką, proszę zaktualizować do minimalnej wymaganej wersji: {{minimalDifyVersion}}', + requestAPlugin: 'Poproś o wtyczkę', + publishPlugins: 'Publikowanie wtyczek', } export default translation diff --git a/web/i18n/pl-PL/share-app.ts b/web/i18n/pl-PL/share-app.ts index 90b6ca1929..617f66d994 100644 --- a/web/i18n/pl-PL/share-app.ts +++ b/web/i18n/pl-PL/share-app.ts @@ -27,6 +27,12 @@ const translation = { }, tryToSolve: 'Spróbuj rozwiązać', temporarySystemIssue: 'Przepraszamy, tymczasowy problem systemowy.', + expand: 'Rozwiń', + collapse: 'Zwiń', + chatSettingsTitle: 'Nowa konfiguracja czatu', + viewChatSettings: 'Zobacz ustawienia czatu', + chatFormTip: 'Ustawienia czatu nie mogą być modyfikowane po rozpoczęciu czatu.', + newChatTip: 'Już w nowej czacie', }, generation: { tabs: { @@ -69,6 +75,11 @@ const translation = { atLeastOne: 'Proszę wprowadź co najmniej jeden wiersz w załadowanym pliku.', }, + executions: '{{num}} EGZEKUCJI', + execution: 'WYKONANIE', + }, + login: { + backToHome: 'Powrót do strony głównej', }, } diff --git a/web/i18n/pl-PL/time.ts b/web/i18n/pl-PL/time.ts index e2410dd34b..e98ebddfcd 100644 --- a/web/i18n/pl-PL/time.ts +++ b/web/i18n/pl-PL/time.ts @@ -1,3 +1,37 @@ -const translation = {} +const translation = { + daysInWeek: { + Thu: 'Czw', + Tue: 'Wtorek', + Mon: 'Mon', + Sun: 'Słońce', + Fri: 'Wolny', + Sat: 'Sat', + Wed: 'Środa', + }, + months: { + July: 'lipiec', + January: 'Styczeń', + August: 'Sierpień', + February: 'Luty', + October: 'Październik', + April: 'Kwiecień', + December: 'Grudzień', + March: 'Marzec', + May: 'Maj', + September: 'Wrzesień', + June: 'Czerwiec', + November: 'Listopad', + }, + operation: { + cancel: 'Anuluj', + pickDate: 'Wybierz datę', + now: 'Teraz', + ok: 'OK', + }, + title: { + pickTime: 'Wybierz czas', + }, + defaultPlaceholder: 'Wybierz czas...', +} export default translation diff --git a/web/i18n/pl-PL/tools.ts b/web/i18n/pl-PL/tools.ts index 49e30c5eee..e9d92d150e 100644 --- a/web/i18n/pl-PL/tools.ts +++ b/web/i18n/pl-PL/tools.ts @@ -14,7 +14,6 @@ const translation = { }, author: 'Przez', auth: { - unauthorized: 'Autoryzacja', authorized: 'Zautoryzowane', setup: 'Skonfiguruj autoryzację aby użyć', setupModalTitle: 'Konfiguruj autoryzację', diff --git a/web/i18n/pl-PL/workflow.ts b/web/i18n/pl-PL/workflow.ts index c47f4ea7d0..4e63d2daaf 100644 --- a/web/i18n/pl-PL/workflow.ts +++ b/web/i18n/pl-PL/workflow.ts @@ -38,8 +38,6 @@ const translation = { setVarValuePlaceholder: 'Ustaw zmienną', needConnectTip: 'Ten krok nie jest połączony z niczym', maxTreeDepth: 'Maksymalny limit {{depth}} węzłów na gałąź', - needEndNode: 'Należy dodać blok końcowy', - needAnswerNode: 'Należy dodać blok odpowiedzi', workflowProcess: 'Proces przepływu pracy', notRunning: 'Jeszcze nie uruchomiono', previewPlaceholder: 'Wprowadź treść w poniższym polu, aby rozpocząć debugowanie Chatbota', @@ -58,7 +56,6 @@ const translation = { learnMore: 'Dowiedz się więcej', copy: 'Kopiuj', duplicate: 'Duplikuj', - addBlock: 'Dodaj blok', pasteHere: 'Wklej tutaj', pointerMode: 'Tryb wskaźnika', handMode: 'Tryb ręczny', @@ -106,6 +103,18 @@ const translation = { addFailureBranch: 'Dodawanie gałęzi niepowodzenia', loadMore: 'Załaduj więcej przepływów pracy', noHistory: 'Brak historii', + exportImage: 'Eksportuj obraz', + exitVersions: 'Wersje wyjścia', + versionHistory: 'Historia wersji', + exportSVG: 'Eksportuj jako SVG', + exportJPEG: 'Eksportuj jako JPEG', + noExist: 'Nie ma takiej zmiennej', + exportPNG: 'Eksportuj jako PNG', + publishUpdate: 'Opublikuj aktualizację', + referenceVar: 'Zmienna odniesienia', + addBlock: 'Dodaj węzeł', + needEndNode: 'Należy dodać węzeł końcowy', + needAnswerNode: 'Węzeł odpowiedzi musi zostać dodany', }, env: { envPanelTitle: 'Zmienne Środowiskowe', @@ -167,19 +176,19 @@ const translation = { stepForward_other: '{{count}} kroki do przodu', sessionStart: 'Początek sesji', currentState: 'Aktualny stan', - nodeTitleChange: 'Tytuł bloku zmieniony', - nodeDescriptionChange: 'Opis bloku zmieniony', - nodeDragStop: 'Blok przeniesiony', - nodeChange: 'Blok zmieniony', - nodeConnect: 'Blok połączony', - nodePaste: 'Blok wklejony', - nodeDelete: 'Blok usunięty', - nodeAdd: 'Blok dodany', - nodeResize: 'Notatka zmieniona', noteAdd: 'Notatka dodana', noteChange: 'Notatka zmieniona', noteDelete: 'Notatka usunięta', - edgeDelete: 'Blok rozłączony', + edgeDelete: 'Węzeł rozłączony', + nodeAdd: 'Węzeł dodany', + nodePaste: 'Węzeł wklejony', + nodeChange: 'Węzeł zmieniony', + nodeDelete: 'Węzeł usunięty', + nodeResize: 'Węzeł zmieniony rozmiar', + nodeConnect: 'Węzeł połączony', + nodeTitleChange: 'Tytuł węzła zmieniony', + nodeDescriptionChange: 'Opis węzła zmieniony', + nodeDragStop: 'Węzeł przeniesiony', }, errorMsg: { fieldRequired: '{{field}} jest wymagane', @@ -205,10 +214,9 @@ const translation = { testRunIteration: 'Iteracja testowego uruchomienia', back: 'Wstecz', iteration: 'Iteracja', + loop: 'Pętla', }, tabs: { - 'searchBlock': 'Szukaj bloku', - 'blocks': 'Bloki', 'tools': 'Narzędzia', 'allTool': 'Wszystkie', 'builtInTool': 'Wbudowane', @@ -222,6 +230,8 @@ const translation = { 'searchTool': 'Wyszukiwarka', 'agent': 'Strategia agenta', 'plugin': 'Wtyczka', + 'searchBlock': 'Wyszukaj węzeł', + 'blocks': 'Węzły', }, blocks: { 'start': 'Start', @@ -243,6 +253,9 @@ const translation = { 'document-extractor': 'Ekstraktor dokumentów', 'list-operator': 'Operator listy', 'agent': 'Agent', + 'loop-start': 'Początek pętli', + 'loop-end': 'Wyjście z pętli', + 'loop': 'Pętla', }, blocksAbout: { 'start': 'Zdefiniuj początkowe parametry uruchamiania przepływu pracy', @@ -263,6 +276,8 @@ const translation = { 'document-extractor': 'Służy do analizowania przesłanych dokumentów w treści tekstowej, która jest łatwo zrozumiała dla LLM.', 'list-operator': 'Służy do filtrowania lub sortowania zawartości tablicy.', 'agent': 'Wywoływanie dużych modeli językowych w celu odpowiadania na pytania lub przetwarzania języka naturalnego', + 'loop': 'Wykonaj pętlę logiki, dopóki nie zostanie spełniony warunek zakończenia lub nie zostanie osiągnięta maksymalna liczba iteracji.', + 'loop-end': 'Odpowiada "break". Ten węzeł nie ma elementów konfiguracyjnych. Gdy ciało pętli dotrze do tego węzła, pętla zostaje zakończona.', }, operator: { zoomIn: 'Powiększ', @@ -273,20 +288,21 @@ const translation = { }, panel: { userInputField: 'Pole wprowadzania użytkownika', - changeBlock: 'Zmień blok', helpLink: 'Link do pomocy', about: 'O', createdBy: 'Stworzone przez ', nextStep: 'Następny krok', - addNextStep: 'Dodaj następny blok w tym przepływie pracy', - selectNextStep: 'Wybierz następny blok', runThisStep: 'Uruchom ten krok', checklist: 'Lista kontrolna', checklistTip: 'Upewnij się, że wszystkie problemy zostały rozwiązane przed opublikowaniem', checklistResolved: 'Wszystkie problemy zostały rozwiązane', - organizeBlocks: 'Organizuj bloki', change: 'Zmień', optional: '(opcjonalne)', + moveToThisNode: 'Przenieś do tego węzła', + selectNextStep: 'Wybierz następny krok', + addNextStep: 'Dodaj następny krok w tym procesie roboczym', + changeBlock: 'Zmień węzeł', + organizeBlocks: 'Organizuj węzły', }, nodes: { common: { @@ -404,6 +420,34 @@ const translation = { variable: 'Zmienna', }, sysQueryInUser: 'sys.query w wiadomości użytkownika jest wymagane', + jsonSchema: { + warningTips: { + saveSchema: 'Proszę ukończyć edytowanie bieżącego pola przed zapisaniem schematu.', + }, + generate: 'Generować', + addChildField: 'Dodaj pole dziecka', + fieldNamePlaceholder: 'Nazwa pola', + resetDefaults: 'Resetuj', + generationTip: 'Możesz użyć języka naturalnego, aby szybko stworzyć schemat JSON.', + required: 'wymagane', + stringValidations: 'Walidacje ciągów', + promptPlaceholder: 'Opisz swój schemat JSON...', + promptTooltip: 'Przekształć opis tekstowy w ustandaryzowaną strukturę schematu JSON.', + title: 'Strukturalny schemat wyjścia', + instruction: 'Instrukcja', + doc: 'Dowiedz się więcej o zorganizowanym wyjściu', + descriptionPlaceholder: 'Dodaj opis', + regenerate: 'Regeneruj', + generateJsonSchema: 'Generuj schemat JSON', + generatedResult: 'Wygenerowany wynik', + showAdvancedOptions: 'Pokaż zaawansowane opcje', + apply: 'Zastosować', + generating: 'Generowanie schematu JSON...', + import: 'Importuj z JSON', + resultTip: 'Oto wygenerowany wynik. Jeśli nie jesteś zadowolony, możesz wrócić i zmodyfikować swoje zapytanie.', + back: 'Tył', + addField: 'Dodaj pole', + }, }, knowledgeRetrieval: { queryVariable: 'Zmienna zapytania', @@ -416,6 +460,33 @@ const translation = { url: 'URL segmentowany', metadata: 'Inne metadane', }, + metadata: { + options: { + disabled: { + title: 'Wyłączone', + subTitle: 'Nie włączanie filtrowania metadanych', + }, + automatic: { + desc: 'Automatycznie generuj warunki filtracji metadanych na podstawie zmiennej zapytania', + title: 'Automatyczny', + subTitle: 'Automatycznie generuj warunki filtracji metadanych na podstawie zapytania użytkownika', + }, + manual: { + subTitle: 'Ręcznie dodaj warunki filtrowania metadanych', + title: 'Ręczny', + }, + }, + panel: { + conditions: 'Warunki', + title: 'Warunki filtru metadanych', + placeholder: 'Wprowadź wartość', + search: 'Szukaj metadanych', + datePlaceholder: 'Wybierz czas...', + add: 'Dodaj warunek', + select: 'Wybierz zmienną...', + }, + title: 'Filtrowanie metadanych', + }, }, http: { inputVars: 'Zmienne wejściowe', @@ -505,6 +576,8 @@ const translation = { 'exists': 'Istnieje', 'all of': 'wszystkie z nich', 'not in': 'nie w', + 'before': 'przed', + 'after': 'po', }, enterValue: 'Wpisz wartość', addCondition: 'Dodaj warunek', @@ -520,6 +593,7 @@ const translation = { }, addSubVariable: 'Zmienna podrzędna', select: 'Wybrać', + condition: 'Stan', }, variableAssigner: { title: 'Przypisz zmienne', @@ -562,6 +636,8 @@ const translation = { '+=': '+=', 'clear': 'Jasny', 'append': 'Dołączyć', + 'remove-first': 'Usuń pierwszy', + 'remove-last': 'Usuń ostatni', }, 'variables': 'Zmiennych', 'selectAssignedVariable': 'Wybierz przypisaną zmienną...', @@ -572,7 +648,6 @@ const translation = { 'noVarTip': 'Kliknij przycisk "+", aby dodać zmienne', }, tool: { - toAuthorize: 'Do autoryzacji', inputVars: 'Zmienne wejściowe', outputVars: { text: 'treść generowana przez narzędzie', @@ -585,6 +660,7 @@ const translation = { }, json: 'JSON wygenerowany przez narzędzien', }, + authorize: 'Autoryzuj', }, questionClassifiers: { model: 'model', @@ -766,6 +842,38 @@ const translation = { strategyNotSet: 'Nie ustawiono strategii agentalnej', model: 'model', }, + loop: { + ErrorMethod: { + operationTerminated: 'Zakończony', + removeAbnormalOutput: 'Usuń nietypowy wynik', + continueOnError: 'Kontynuuj w przypadku błędu', + }, + inputMode: 'Tryb wejściowy', + loop_other: '{{count}} Pętle', + deleteDesc: 'Usunięcie węzła pętli spowoduje usunięcie wszystkich węzłów potomnych.', + loopVariables: 'Zmienne pętli', + variableName: 'Nazwa zmiennej', + output: 'Zmienna wyjściowa', + breakCondition: 'Warunek zakończenia pętli', + input: 'Wprowadzenie', + initialLoopVariables: 'Początkowe zmienne pętli', + error_one: '{{count}} Błąd', + loopNode: 'Węzeł pętli', + loop_one: '{{count}} pętla', + currentLoop: 'Pętla prądowa', + finalLoopVariables: 'Ostateczne zmienne pętli', + comma: ',', + loopMaxCountError: 'Proszę wprowadzić prawidłową maksymalną liczbę iteracji, mieszczącą się w przedziale od 1 do {{maxCount}}', + error_other: '{{count}} błędów', + totalLoopCount: 'Całkowita liczba pętli: {{count}}', + exitConditionTip: 'Węzeł pętli potrzebuje przynajmniej jednego warunku wyjścia.', + setLoopVariables: 'Ustaw zmienne w zakresie pętli', + loopMaxCount: 'Maksymalna liczba pętli', + errorResponseMethod: 'Metoda odpowiedzi na błąd', + breakConditionTip: 'Tylko zmienne w pętlach z warunkami zakończenia oraz zmienne konwersacyjne mogą być odwoływane.', + currentLoopCount: 'Aktualna liczba pętli: {{count}}', + deleteTitle: 'Usunąć węzeł pętli?', + }, }, tracing: { stopBy: 'Zatrzymane przez {{user}}', @@ -777,6 +885,38 @@ const translation = { noAssignedVars: 'Brak dostępnych przypisanych zmiennych', noAvailableVars: 'Brak dostępnych zmiennych', }, + versionHistory: { + filter: { + onlyShowNamedVersions: 'Pokazuj tylko wersje z nazwami', + all: 'Wszystko', + onlyYours: 'Tylko twój', + empty: 'Nie znaleziono odpowiadającej historii wersji', + reset: 'Resetuj filtr', + }, + editField: { + releaseNotes: 'Notatki o wydaniu', + releaseNotesLengthLimit: 'Notatki o wydaniu nie mogą przekraczać {{limit}} znaków', + title: 'Tytuł', + titleLengthLimit: 'Tytuł nie może przekraczać {{limit}} znaków', + }, + action: { + updateSuccess: 'Wersja zaktualizowana', + updateFailure: 'Nie udało się zaktualizować wersji', + deleteFailure: 'Nie udało się usunąć wersji', + deleteSuccess: 'Wersja usunięta', + restoreSuccess: 'Wersja przywrócona', + restoreFailure: 'Nie udało się przywrócić wersji', + }, + currentDraft: 'Aktualny szkic', + nameThisVersion: 'Nazwij tę wersję', + defaultName: 'Nienazwana wersja', + title: 'Wersje', + latest: 'Najnowszy', + releaseNotesPlaceholder: 'Opisz, co się zmieniło', + editVersionInfo: 'Edytuj informacje o wersji', + deletionTip: 'Usunięcie jest nieodwracalne, proszę potwierdzić.', + restorationTip: 'Po przywróceniu wersji bieżący szkic zostanie nadpisany.', + }, } export default translation diff --git a/web/i18n/pt-BR/app-debug.ts b/web/i18n/pt-BR/app-debug.ts index df4312f887..64f7a85fe7 100644 --- a/web/i18n/pt-BR/app-debug.ts +++ b/web/i18n/pt-BR/app-debug.ts @@ -285,6 +285,7 @@ const translation = { 'labelName': 'Nome do Rótulo', 'inputPlaceholder': 'Por favor, insira', 'required': 'Obrigatório', + 'hide': 'Ocultar', 'errorMsg': { varNameRequired: 'O nome da variável é obrigatório', labelNameRequired: 'O nome do rótulo é obrigatório', diff --git a/web/i18n/pt-BR/app-overview.ts b/web/i18n/pt-BR/app-overview.ts index 10e47a750b..a6a76f1cf3 100644 --- a/web/i18n/pt-BR/app-overview.ts +++ b/web/i18n/pt-BR/app-overview.ts @@ -30,26 +30,26 @@ const translation = { overview: { title: 'Visão Geral', appInfo: { - explanation: 'WebApp de IA Pronta para Uso', + explanation: 'web app de IA Pronta para Uso', accessibleAddress: 'URL Pública', preview: 'Visualização', regenerate: 'Regenerar', regenerateNotice: 'Você deseja regenerar a URL pública?', - preUseReminder: 'Por favor, ative o WebApp antes de continuar.', + preUseReminder: 'Por favor, ative o web app antes de continuar.', settings: { entry: 'Configurações', - title: 'Configurações do WebApp', - webName: 'Nome do WebApp', - webDesc: 'Descrição do WebApp', + title: 'Configurações do web app', + webName: 'Nome do web app', + webDesc: 'Descrição do web app', webDescTip: 'Este texto será exibido no lado do cliente, fornecendo orientações básicas sobre como usar o aplicativo', - webDescPlaceholder: 'Insira a descrição do WebApp', + webDescPlaceholder: 'Insira a descrição do web app', language: 'Idioma', workflow: { title: 'Etapas do fluxo de trabalho', show: 'Mostrar', hide: 'Ocultar', subTitle: 'Detalhes do fluxo de trabalho', - showDesc: 'Mostrar ou ocultar detalhes do fluxo de trabalho no WebApp', + showDesc: 'Mostrar ou ocultar detalhes do fluxo de trabalho no web app', }, chatColorTheme: 'Tema de cor do chatbot', chatColorThemeDesc: 'Defina o tema de cor do chatbot', @@ -66,14 +66,14 @@ const translation = { customDisclaimer: 'Aviso Legal Personalizado', customDisclaimerPlaceholder: 'Insira o texto do aviso legal', customDisclaimerTip: 'O texto do aviso legal personalizado será exibido no lado do cliente, fornecendo informações adicionais sobre o aplicativo', - copyrightTip: 'Exibir informações de direitos autorais no webapp', + copyrightTip: 'Exibir informações de direitos autorais no web app', copyrightTooltip: 'Por favor, atualize para o plano Professional ou superior', }, sso: { - tooltip: 'Entre em contato com o administrador para habilitar o SSO do WebApp', + tooltip: 'Entre em contato com o administrador para habilitar o SSO do web app', label: 'Autenticação SSO', - title: 'WebApp SSO', - description: 'Todos os usuários devem fazer login com SSO antes de usar o WebApp', + title: 'web app SSO', + description: 'Todos os usuários devem fazer login com SSO antes de usar o web app', }, modalTip: 'Configurações do aplicativo Web do lado do cliente.', }, @@ -95,7 +95,7 @@ const translation = { customize: { way: 'modo', entry: 'Personalizar', - title: 'Personalizar WebApp de IA', + title: 'Personalizar web app de IA', explanation: 'Você pode personalizar a interface do usuário do Web App para atender às suas necessidades de cenário e estilo.', way1: { name: 'Faça um fork do código do cliente, modifique-o e implante-o no Vercel (recomendado)', diff --git a/web/i18n/pt-BR/app.ts b/web/i18n/pt-BR/app.ts index 8f920e4280..766053456a 100644 --- a/web/i18n/pt-BR/app.ts +++ b/web/i18n/pt-BR/app.ts @@ -74,25 +74,25 @@ const translation = { appCreateDSLErrorPart2: 'Você quer continuar?', learnMore: 'Saiba Mais', optional: 'Opcional', - chooseAppType: 'Escolha o tipo de aplicativo', - forBeginners: 'PARA INICIANTES', + chooseAppType: 'Escolha um tipo de aplicativo', + forBeginners: 'Tipos de aplicativos mais básicos', noTemplateFound: 'Nenhum modelo encontrado', foundResults: '{{contagem}} Resultados', foundResult: '{{contagem}} Resultado', completionUserDescription: 'Crie rapidamente um assistente de IA para tarefas de geração de texto com configuração simples.', noIdeaTip: 'Sem ideias? Confira nossos modelos', - workflowUserDescription: 'Orquestração de fluxo de trabalho para tarefas de rodada única, como automação e processamento em lote.', + workflowUserDescription: 'Construa fluxos autônomos de IA visualmente com simplicidade de arrastar e soltar.', chatbotUserDescription: 'Crie rapidamente um chatbot baseado em LLM com configuração simples. Você pode alternar para o fluxo de chat mais tarde.', agentShortDescription: 'Agente inteligente com raciocínio e uso de ferramenta autônoma', forAdvanced: 'PARA USUÁRIOS AVANÇADOS', chatbotShortDescription: 'Chatbot baseado em LLM com configuração simples', - advancedUserDescription: 'Orquestração de fluxo de trabalho para tarefas de diálogo complexas de várias rodadas com recursos de memória.', + advancedUserDescription: 'Fluxo com recursos adicionais de memória e interface de chatbot.', noTemplateFoundTip: 'Tente pesquisar usando palavras-chave diferentes.', agentUserDescription: 'Um agente inteligente capaz de raciocínio iterativo e uso autônomo de ferramentas para atingir os objetivos da tarefa.', completionShortDescription: 'Assistente de IA para tarefas de geração de texto', - workflowShortDescription: 'Orquestração para tarefas de automação de turno único', + workflowShortDescription: 'Fluxo agêntico para automações inteligentes', noAppsFound: 'Nenhum aplicativo encontrado', - advancedShortDescription: 'Fluxo de trabalho para diálogos complexos de vários turnos com memória', + advancedShortDescription: 'Fluxo aprimorado para conversas de múltiplos turnos', }, editApp: 'Editar Informações', editAppTitle: 'Editar Informações do Aplicativo', @@ -159,11 +159,15 @@ const translation = { description: 'Opik é uma plataforma de código aberto para avaliar, testar e monitorar aplicativos LLM.', title: 'Opik', }, + weave: { + description: 'Weave é uma plataforma de código aberto para avaliar, testar e monitorar aplicações de LLM.', + title: 'Trançar', + }, }, answerIcon: { - descriptionInExplore: 'Se o ícone do WebApp deve ser usado para substituir 🤖 no Explore', - description: 'Se o ícone WebApp deve ser usado para substituir 🤖 no aplicativo compartilhado', - title: 'Use o ícone do WebApp para substituir 🤖', + descriptionInExplore: 'Se o ícone do web app deve ser usado para substituir 🤖 no Explore', + description: 'Se o ícone web app deve ser usado para substituir 🤖 no aplicativo compartilhado', + title: 'Use o ícone do web app para substituir 🤖', }, importFromDSLUrlPlaceholder: 'Cole o link DSL aqui', importFromDSLUrl: 'Do URL', @@ -194,6 +198,54 @@ const translation = { placeholder: 'Selecione um aplicativo...', params: 'PARÂMETROS DO APLICATIVO', }, + structOutput: { + LLMResponse: 'Resposta do LLM', + configure: 'Configurar', + required: 'Requerido', + modelNotSupported: 'Modelo não suportado', + structured: 'Estruturado', + modelNotSupportedTip: 'O modelo atual não suporta esse recurso e é automaticamente rebaixado para injeção de prompt.', + structuredTip: 'Saídas Estruturadas é um recurso que garante que o modelo sempre gerará respostas que seguem o seu Esquema JSON fornecido.', + moreFillTip: 'Mostrando um máximo de 10 níveis de aninhamento', + notConfiguredTip: 'A saída estruturada ainda não foi configurada.', + }, + accessItemsDescription: { + anyone: 'Qualquer pessoa pode acessar o aplicativo web', + specific: 'Apenas grupos ou membros específicos podem acessar o aplicativo web', + organization: 'Qualquer pessoa na organização pode acessar o aplicativo web', + external: 'Apenas usuários externos autenticados podem acessar o aplicativo Web.', + }, + accessControlDialog: { + accessItems: { + anyone: 'Qualquer pessoa com o link', + specific: 'Grupos específicos ou membros', + organization: 'Apenas membros dentro da empresa', + external: 'Usuários externos autenticados', + }, + operateGroupAndMember: { + searchPlaceholder: 'Pesquisar grupos e membros', + allMembers: 'Todos os membros', + expand: 'Expandir', + noResult: 'Nenhum resultado', + }, + title: 'Controle de Acesso do Aplicativo Web', + description: 'Defina as permissões de acesso do aplicativo da web', + accessLabel: 'Quem tem acesso', + groups_one: '{{count}} GRUPO', + groups_other: '{{count}} GRUPOS', + members_other: '{{count}} MEMBROS', + noGroupsOrMembers: 'Nenhum grupo ou membro selecionado', + updateSuccess: 'Atualização bem-sucedida', + members_one: '{{count}} MEMBRO', + webAppSSONotEnabledTip: 'Por favor, entre em contato com o administrador da empresa para configurar o método de autenticação da aplicação web.', + }, + publishApp: { + title: 'Quem pode acessar o aplicativo web', + notSet: 'Não definido', + notSetDesc: 'Atualmente, ninguém pode acessar o aplicativo web. Por favor, defina as permissões.', + }, + accessControl: 'Controle de Acesso do Aplicativo Web', + noAccessPermission: 'Sem permissão para acessar o aplicativo web', } export default translation diff --git a/web/i18n/pt-BR/billing.ts b/web/i18n/pt-BR/billing.ts index 0a7a964376..f26008c718 100644 --- a/web/i18n/pt-BR/billing.ts +++ b/web/i18n/pt-BR/billing.ts @@ -66,6 +66,7 @@ const translation = { messageRequest: { title: 'Créditos de Mensagem', tooltip: 'Cotas de invocação de mensagens para vários planos usando modelos da OpenAI (exceto gpt4). Mensagens além do limite usarão sua Chave de API da OpenAI.', + titlePerMonth: '{{count,number}} mensagens/mês', }, annotatedResponse: { title: 'Limites de Cota de Anotação', @@ -78,27 +79,94 @@ const translation = { annotationQuota: 'Cota de anotação', contractSales: 'Entre em contato com a equipe de vendas', unavailable: 'Indisponível', + priceTip: 'por espaço de trabalho/', + apiRateLimit: 'Limite de Taxa da API', + freeTrialTipPrefix: 'Inscreva-se e receba um', + teamMember_one: '{{count,number}} Membro da Equipe', + documentsRequestQuota: '{{count,number}}/min Limite de Taxa de Solicitação de Conhecimento', + cloud: 'Serviço de Nuvem', + teamWorkspace: '{{count,number}} Espaço de Trabalho da Equipe', + apiRateLimitUnit: '{{count,number}}/dia', + freeTrialTipSuffix: 'Nenhum cartão de crédito necessário', + teamMember_other: '{{count,number}} Membros da Equipe', + comparePlanAndFeatures: 'Compare planos e recursos', + getStarted: 'Começar', + annualBilling: 'Cobrança Anual', + self: 'Auto-Hospedado', + documentsTooltip: 'Cota sobre o número de documentos importados da Fonte de Dados do Conhecimento.', + freeTrialTip: 'teste gratuito de 200 chamadas da OpenAI.', + documents: '{{count,number}} Documentos de Conhecimento', + unlimitedApiRate: 'Sem limite de taxa da API', + apiRateLimitTooltip: 'O limite da taxa da API se aplica a todas as solicitações feitas através da API Dify, incluindo geração de texto, conversas de chat, execuções de fluxo de trabalho e processamento de documentos.', + documentsRequestQuotaTooltip: 'Especifica o número total de ações que um espaço de trabalho pode realizar por minuto dentro da base de conhecimento, incluindo criação, exclusão, atualizações de conjuntos de dados, uploads de documentos, modificações, arquivamento e consultas à base de conhecimento. Esse métrica é utilizada para avaliar o desempenho das solicitações à base de conhecimento. Por exemplo, se um usuário do Sandbox realizar 10 testes de impacto consecutivos dentro de um minuto, seu espaço de trabalho ficará temporariamente restrito de realizar as seguintes ações no minuto seguinte: criação, exclusão, atualizações de conjuntos de dados e uploads ou modificações de documentos.', }, plans: { sandbox: { name: 'Sandbox', description: '200 vezes GPT de teste gratuito', includesTitle: 'Inclui:', + for: 'Teste gratuito das capacidades principais', }, professional: { name: 'Profissional', description: 'Para indivíduos e pequenas equipes desbloquearem mais poder de forma acessível.', includesTitle: 'Tudo no plano gratuito, além de:', + for: 'Para Desenvolvedores Independentes/Pequenas Equipes', }, team: { name: 'Equipe', description: 'Colabore sem limites e aproveite o desempenho de primeira linha.', includesTitle: 'Tudo no plano Profissional, além de:', + for: 'Para Equipes de Médio Porte', }, enterprise: { name: 'Empresa', description: 'Obtenha capacidades completas e suporte para sistemas críticos em larga escala.', includesTitle: 'Tudo no plano Equipe, além de:', + features: { + 6: 'Segurança e Controles Avançados', + 7: 'Atualizações e Manutenção por Dify Oficialmente', + 5: 'Acordos de Nível de Serviço negociados pelos Parceiros Dify', + 1: 'Autorização de Licença Comercial', + 8: 'Suporte Técnico Profissional', + 4: 'SSO', + 2: 'Recursos Exclusivos da Empresa', + 3: 'Múltiplos Espaços de Trabalho e Gestão Empresarial', + 0: 'Soluções de Implantação Escaláveis de Nível Empresarial', + }, + btnText: 'Contate Vendas', + priceTip: 'Faturamento Anual Apenas', + price: 'Custom', + for: 'Para equipes de grande porte', + }, + community: { + features: { + 1: 'Espaço de Trabalho Único', + 0: 'Todos os recursos principais lançados sob o repositório público', + 2: 'Cumpre a Licença de Código Aberto Dify', + }, + name: 'Comunidade', + description: 'Para Usuários Individuais, Pequenas Equipes ou Projetos Não Comerciais', + includesTitle: 'Recursos Gratuitos:', + btnText: 'Comece com a Comunidade', + price: 'Grátis', + for: 'Para Usuários Individuais, Pequenas Equipes ou Projetos Não Comerciais', + }, + premium: { + features: { + 1: 'Espaço de Trabalho Único', + 3: 'Suporte prioritário por e-mail e chat', + 2: 'Customização de Logo e Branding do WebApp', + 0: 'Confiabilidade autogerenciada por vários provedores de nuvem', + }, + includesTitle: 'Tudo da Comunidade, além de:', + for: 'Para organizações e equipes de médio porte', + price: 'Escalável', + name: 'Premium', + comingSoon: 'Suporte da Microsoft Azure e Google Cloud em breve', + priceTip: 'Baseado no Mercado de Nuvem', + btnText: 'Obtenha Premium em', + description: 'Para organizações e equipes de médio porte', }, }, vectorSpace: { @@ -108,12 +176,26 @@ const translation = { apps: { fullTipLine1: 'Faça o upgrade do seu plano para', fullTipLine2: 'construir mais aplicativos.', + fullTip1: 'Atualize para criar mais aplicativos', + fullTip2: 'Limite do plano alcançado', + fullTip1des: 'Você atingiu o limite de criar aplicativos neste plano.', + contactUs: 'Contate-nos', + fullTip2des: 'É recomendado limpar aplicações inativas para liberar uso ou entrar em contato conosco.', }, annotatedResponse: { fullTipLine1: 'Faça o upgrade do seu plano para', fullTipLine2: 'anotar mais conversas.', quotaTitle: 'Cota de Respostas Anotadas', }, + usagePage: { + documentsUploadQuota: 'Cota de Upload de Documentos', + annotationQuota: 'Cota de Anotação', + teamMembers: 'Membros da equipe', + vectorSpace: 'Armazenamento de Dados do Conhecimento', + vectorSpaceTooltip: 'Documentos com o modo de indexação de Alta Qualidade consumirã recursos de Armazenamento de Dados de Conhecimento. Quando o Armazenamento de Dados de Conhecimento atingir o limite, novos documentos não serão carregados.', + buildApps: 'Desenvolver Apps', + }, + teamMembers: 'Membros da equipe', } export default translation diff --git a/web/i18n/pt-BR/common.ts b/web/i18n/pt-BR/common.ts index 180bcbb4da..d9409b5dd0 100644 --- a/web/i18n/pt-BR/common.ts +++ b/web/i18n/pt-BR/common.ts @@ -54,6 +54,10 @@ const translation = { copied: 'Copiado', in: 'em', viewDetails: 'Ver detalhes', + downloadFailed: 'Download falhou. Por favor, tente novamente mais tarde.', + more: 'Mais', + downloadSuccess: 'Download concluído.', + format: 'Formato', }, placeholder: { input: 'Por favor, insira', @@ -141,6 +145,8 @@ const translation = { newDataset: 'Criar Conhecimento', tools: 'Ferramentas', exploreMarketplace: 'Explorar Mercado', + appDetail: 'Detalhes do aplicativo', + account: 'Conta', }, userProfile: { settings: 'Configurações', @@ -153,6 +159,9 @@ const translation = { community: 'Comunidade', about: 'Sobre', logout: 'Sair', + github: 'GitHub', + support: 'Suporte', + compliance: 'Conformidade', }, settings: { accountGroup: 'CONTA', @@ -202,6 +211,9 @@ const translation = { feedbackTitle: 'Realimentação', feedbackLabel: 'Diga-nos por que você excluiu sua conta?', feedbackPlaceholder: 'Opcional', + workspaceName: 'Nome do Espaço de Trabalho', + workspaceIcon: 'Ícone de Área de Trabalho', + editWorkspaceInfo: 'Editar Informações do Espaço de Trabalho', }, members: { team: 'Equipe', @@ -455,7 +467,7 @@ const translation = { apiBasedExtension: { title: 'As extensões de API fornecem gerenciamento centralizado de API, simplificando a configuração para uso fácil em todos os aplicativos da Dify.', link: 'Saiba como desenvolver sua própria Extensão de API.', - linkUrl: 'https://docs.dify.ai/features/extension/api_based_extension', + linkUrl: 'https://docs.dify.ai/en/guides/extension/api-based-extension/README', add: 'Adicionar Extensão de API', selector: { title: 'Extensão de API', @@ -540,9 +552,10 @@ const translation = { vectorHash: 'Hash de vetor:', hitScore: 'Pontuação de recuperação:', }, - inputPlaceholder: 'Fale com o bot', + inputPlaceholder: 'Fale com o {{botName}}', thinking: 'Pensante...', thought: 'Pensamento', + resend: 'Reenviar', }, promptEditor: { placeholder: 'Escreva sua palavra de incentivo aqui, digite \'{\' para inserir uma variável, digite \'/\' para inserir um bloco de conteúdo de incentivo', @@ -633,10 +646,31 @@ const translation = { license: { expiring: 'Expirando em um dia', expiring_plural: 'Expirando em {{count}} dias', + unlimited: 'Ilimitado', }, pagination: { perPage: 'Itens por página', }, + theme: { + light: 'luz', + dark: 'escuro', + theme: 'Tema', + auto: 'sistema', + }, + compliance: { + soc2Type1: 'Relatório SOC 2 Tipo I', + sandboxUpgradeTooltip: 'Apenas disponível com um plano Profissional ou de Equipe.', + soc2Type2: 'Relatório SOC 2 Tipo II', + professionalUpgradeTooltip: 'Apenas disponível com um plano Team ou superior.', + gdpr: 'GDPR DPA', + iso27001: 'Certificação ISO 27001:2022', + }, + imageInput: { + dropImageHere: 'Arraste sua imagem aqui, ou', + supportedFormats: 'Suporta PNG, JPG, JPEG, WEBP e GIF', + browse: 'navegar', + }, + you: 'Você', } export default translation diff --git a/web/i18n/pt-BR/custom.ts b/web/i18n/pt-BR/custom.ts index 940316e7bb..e167035b9b 100644 --- a/web/i18n/pt-BR/custom.ts +++ b/web/i18n/pt-BR/custom.ts @@ -3,9 +3,11 @@ const translation = { upgradeTip: { prefix: 'Atualize seu plano para', suffix: 'personalizar sua marca.', + title: 'Atualize seu plano', + des: 'Atualize seu plano para personalizar sua marca', }, webapp: { - title: 'Personalizar marca do WebApp', + title: 'Personalizar marca do web app', removeBrand: 'Remover Powered by Dify', changeLogo: 'Alterar Imagem da Marca Powered by', changeLogoTip: 'Formato SVG ou PNG com tamanho mínimo de 40x40px', diff --git a/web/i18n/pt-BR/dataset-creation.ts b/web/i18n/pt-BR/dataset-creation.ts index de806f8276..9023d1a6dc 100644 --- a/web/i18n/pt-BR/dataset-creation.ts +++ b/web/i18n/pt-BR/dataset-creation.ts @@ -22,7 +22,7 @@ const translation = { }, uploader: { title: 'Enviar arquivo de texto', - button: 'Arraste e solte o arquivo, ou', + button: 'Arraste e solte arquivos ou pastas, ou', browse: 'Navegar', tip: 'Suporta {{supportTypes}}. Máximo de {{size}}MB cada.', validation: { @@ -58,7 +58,7 @@ const translation = { crawlSubPage: 'Rastrear subpáginas', selectAll: 'Selecionar tudo', resetAll: 'Redefinir tudo', - firecrawlDocLink: 'https://docs.dify.ai/guides/knowledge-base/sync-from-website', + firecrawlDocLink: 'https://docs.dify.ai/en/guides/knowledge-base/create-knowledge-and-upload-documents/import-content-data/sync-from-website', includeOnlyPaths: 'Incluir apenas caminhos', configure: 'Configurar', limit: 'Limite', @@ -82,6 +82,14 @@ const translation = { useSitemap: 'Usar o mapa do site', useSitemapTooltip: 'Siga o mapa do site para rastrear o site. Caso contrário, o Jina Reader rastreará iterativamente com base na relevância da página, produzindo menos páginas, mas de maior qualidade.', jinaReaderTitle: 'Converter todo o site em Markdown', + watercrawlTitle: 'Extrair conteúdo da web com o Watercrawl', + configureFirecrawl: 'Configurar o Firecrawl', + configureJinaReader: 'Configurar o Leitor Jina', + waterCrawlNotConfigured: 'Watercrawl não está configurado', + waterCrawlNotConfiguredDescription: 'Configure o Watercrawl com a chave da API para usá-lo.', + watercrawlDocLink: 'https://docs.dify.ai/pt/guias/base-de-conhecimentos/criar-conhecimento-e-enviar-documentos/importar-dados-de-conteudo/sincronizar-a-partir-do-site', + watercrawlDoc: 'Documentos do Watercrawl', + configureWatercrawl: 'Configurar Watercrawl', }, cancel: 'Cancelar', }, @@ -200,6 +208,11 @@ const translation = { description: 'Atualmente, a base de conhecimento da Dify possui apenas fontes de dados limitadas. Contribuir com uma fonte de dados para a base de conhecimento Dify é uma maneira fantástica de ajudar a aumentar a flexibilidade e o poder da plataforma para todos os usuários. Nosso guia de contribuição facilita o início. Clique no link abaixo para saber mais.', title: 'Conectar-se a outras fontes de dados?', }, + watercrawl: { + apiKeyPlaceholder: 'Chave da API do watercrawl.dev', + configWatercrawl: 'Configurar Watercrawl', + getApiKeyLinkText: 'Obtenha sua chave de API em watercrawl.dev', + }, } export default translation diff --git a/web/i18n/pt-BR/dataset-settings.ts b/web/i18n/pt-BR/dataset-settings.ts index a9346c4dd0..e959fa0a12 100644 --- a/web/i18n/pt-BR/dataset-settings.ts +++ b/web/i18n/pt-BR/dataset-settings.ts @@ -25,6 +25,7 @@ const translation = { learnMore: 'Saiba mais', description: ' sobre o método de recuperação.', longDescription: ' sobre o método de recuperação, você pode alterar isso a qualquer momento nas configurações do conhecimento.', + method: 'Método de Recuperação', }, save: 'Salvar', permissionsInvitedMembers: 'Membros parciais da equipe', diff --git a/web/i18n/pt-BR/dataset.ts b/web/i18n/pt-BR/dataset.ts index c8214e1645..7d5f75aae6 100644 --- a/web/i18n/pt-BR/dataset.ts +++ b/web/i18n/pt-BR/dataset.ts @@ -168,6 +168,54 @@ const translation = { localDocs: 'Documentos locais', allKnowledgeDescription: 'Selecione para exibir todo o conhecimento neste espaço de trabalho. Somente o proprietário do espaço de trabalho pode gerenciar todo o conhecimento.', allKnowledge: 'Todo o conhecimento', + metadata: { + createMetadata: { + name: 'Nome', + title: 'Nova Metadata', + type: 'Tipo', + namePlaceholder: 'Adicionar nome de metadados', + back: 'Voltar', + }, + checkName: { + empty: 'O nome dos metadados não pode estar vazio', + invalid: 'O nome de metadata só pode conter letras minúsculas, números e sublinhados e deve começar com uma letra minúscula.', + }, + batchEditMetadata: { + editDocumentsNum: 'Editando {{num}} documentos', + applyToAllSelectDocument: 'Aplicar a todos os documentos selecionados', + editMetadata: 'Editar Metadados', + multipleValue: 'Múltiplos Valores', + applyToAllSelectDocumentTip: 'Crie automaticamente todos os metadados editados e novos mencionados acima para todos os documentos selecionados, caso contrário, a edição de metadados só se aplicará aos documentos que já os possuem.', + }, + selectMetadata: { + manageAction: 'Gerenciar', + search: 'Pesquisar metadados', + newAction: 'Nova Metadados', + }, + datasetMetadata: { + addMetaData: 'Adicionar Metadados', + namePlaceholder: 'Nome da metadata', + description: 'Você pode gerenciar todos os metadados neste conhecimento aqui. As modificações serão sincronizadas em todos os documentos.', + deleteTitle: 'Confirme para deletar', + deleteContent: 'Você tem certeza de que deseja excluir os metadados "{{name}}"?', + name: 'Nome', + builtInDescription: 'Os metadados incorporados são extraídos e gerados automaticamente. Eles devem ser ativados antes do uso e não podem ser editados.', + disabled: 'Desativado', + builtIn: 'Integrado', + rename: 'Renomear', + values: '{{num}} Valores', + }, + documentMetadata: { + metadataToolTip: 'Os metadados servem como um filtro crítico que aprimora a precisão e a relevância da recuperação de informações. Você pode modificar e adicionar metadados para este documento aqui.', + technicalParameters: 'Parâmetros Técnicos', + documentInformation: 'Informações do Documento', + startLabeling: 'Comece a rotular', + }, + addMetadata: 'Adicionar Metadados', + chooseTime: 'Escolha um horário...', + metadata: 'Metadados', + }, + embeddingModelNotAvailable: 'O modelo de incorporação não está disponível.', } export default translation diff --git a/web/i18n/pt-BR/education.ts b/web/i18n/pt-BR/education.ts new file mode 100644 index 0000000000..af0cd1523a --- /dev/null +++ b/web/i18n/pt-BR/education.ts @@ -0,0 +1,47 @@ +const translation = { + toVerifiedTip: { + front: 'Você agora está elegível para o status de Educação Verificada. Por favor, insira suas informações educacionais abaixo para concluir o processo e receber um', + coupon: 'cupom exclusivo de 100%', + end: 'para o Plano Profissional Dify.', + }, + form: { + schoolName: { + placeholder: 'Digite o nome oficial e não abreviado da sua escola', + title: 'O nome da sua escola', + }, + schoolRole: { + option: { + teacher: 'Professor', + student: 'Estudante', + administrator: 'Administrador Escolar', + }, + title: 'Seu Papel na Escola', + }, + terms: { + desc: { + and: 'e', + privacyPolicy: 'Política de Privacidade', + front: 'Suas informações e o uso do status de Educação Verificada estão sujeitos ao nosso', + termsOfService: 'Termos de Serviço', + end: 'Ao enviar:', + }, + option: { + inSchool: 'Eu confirmo que estou matriculado ou empregado na instituição mencionada. A Dify pode solicitar comprovação de matrícula/emprego. Se eu representar indevidamente minha elegibilidade, concordo em pagar quaisquer taxas inicialmente isentas com base no meu status educacional.', + age: 'Eu confirmo que tenho pelo menos 18 anos', + }, + title: 'Termos e Acordos', + }, + }, + learn: 'Aprenda como fazer a verificação da sua educação', + toVerified: 'Verifique a Educação', + currentSigned: 'ATUALMENTE CONECTADO COMO', + submit: 'Enviar', + emailLabel: 'Seu e-mail atual', + successContent: 'Emitimos um cupom de desconto de 100% para o plano Dify Professional na sua conta. O cupom é válido por um ano, por favor, utilize-o dentro do período de validade.', + rejectTitle: 'A sua verificação educacional Dify foi rejeitada.', + rejectContent: 'Infelizmente, você não é elegível para o status de Educação Verificada e, portanto, não pode receber o cupom exclusivo de 100% para o Plano Profissional Dify se usar este endereço de e-mail.', + successTitle: 'Você Tem a Educação Dify Verificada', + submitError: 'A submissão do formulário falhou. Por favor, tente novamente mais tarde.', +} + +export default translation diff --git a/web/i18n/pt-BR/explore.ts b/web/i18n/pt-BR/explore.ts index f77827b4ed..2a15d07f95 100644 --- a/web/i18n/pt-BR/explore.ts +++ b/web/i18n/pt-BR/explore.ts @@ -37,6 +37,7 @@ const translation = { HR: 'RH', Workflow: 'Fluxo de trabalho', Agent: 'Agente', + Entertainment: 'Entretenimento', }, } diff --git a/web/i18n/pt-BR/login.ts b/web/i18n/pt-BR/login.ts index 7af5181bb9..0880b4776e 100644 --- a/web/i18n/pt-BR/login.ts +++ b/web/i18n/pt-BR/login.ts @@ -105,6 +105,11 @@ const translation = { licenseLost: 'Licença perdida', licenseInactive: 'Licença inativa', licenseExpiredTip: 'A licença do Dify Enterprise para seu espaço de trabalho expirou. Entre em contato com o administrador para continuar usando o Dify.', + webapp: { + noLoginMethod: 'Método de autenticação não configurado para o aplicativo web', + disabled: 'A autenticação do aplicativo da web está desativada. Por favor, entre em contato com o administrador do sistema para habilitá-la. Você pode tentar usar o aplicativo diretamente.', + noLoginMethodTip: 'Por favor, entre em contato com o administrador do sistema para adicionar um método de autenticação.', + }, } export default translation diff --git a/web/i18n/pt-BR/plugin.ts b/web/i18n/pt-BR/plugin.ts index 3528407a1b..77defcfcec 100644 --- a/web/i18n/pt-BR/plugin.ts +++ b/web/i18n/pt-BR/plugin.ts @@ -62,6 +62,7 @@ const translation = { descriptionPlaceholder: 'Breve descrição da finalidade da ferramenta, por exemplo, obter a temperatura para um local específico.', uninstalledTitle: 'Ferramenta não instalada', unsupportedTitle: 'Ação sem suporte', + toolSetting: 'Configurações da Ferramenta', }, serviceOk: 'Serviço OK', endpointsTip: 'Este plug-in fornece funcionalidades específicas por meio de endpoints e você pode configurar vários conjuntos de endpoints para o workspace atual.', @@ -180,6 +181,8 @@ const translation = { moreFrom: 'Mais do Marketplace', noPluginFound: 'Nenhum plugin encontrado', discover: 'Descobrir', + verifiedTip: 'Verificado pelo Dify', + partnerTip: 'Verificado por um parceiro da Dify', }, task: { installedError: 'Falha na instalação dos plug-ins {{errorLength}}', @@ -191,7 +194,6 @@ const translation = { }, installAction: 'Instalar', endpointsEnabled: '{{num}} conjuntos de endpoints habilitados', - submitPlugin: 'Enviar plugin', searchPlugins: 'Pesquisar plugins', searchInMarketplace: 'Pesquisar no Marketplace', installPlugin: 'Instale o plugin', @@ -204,6 +206,12 @@ const translation = { searchCategories: 'Categorias de pesquisa', findMoreInMarketplace: 'Saiba mais no Marketplace', installFrom: 'INSTALAR DE', + metadata: { + title: 'Plugins', + }, + difyVersionNotCompatible: 'A versão atual do Dify não é compatível com este plugin, por favor atualize para a versão mínima exigida: {{minimalDifyVersion}}', + requestAPlugin: 'Solicitar um plugin', + publishPlugins: 'Publicar plugins', } export default translation diff --git a/web/i18n/pt-BR/share-app.ts b/web/i18n/pt-BR/share-app.ts index 1e1861e01b..9a9d7db632 100644 --- a/web/i18n/pt-BR/share-app.ts +++ b/web/i18n/pt-BR/share-app.ts @@ -30,6 +30,12 @@ const translation = { }, tryToSolve: 'Tente resolver', temporarySystemIssue: 'Desculpe, problema temporário do sistema.', + expand: 'Expandir', + collapse: 'Contrair', + newChatTip: 'Já em um novo chat', + chatFormTip: 'As configurações do chat não podem ser modificadas após o início do chat.', + viewChatSettings: 'Ver configurações de chat', + chatSettingsTitle: 'Nova configuração de chat', }, generation: { tabs: { @@ -68,6 +74,11 @@ const translation = { moreThanMaxLengthLine: 'Linha {{rowIndex}}: o valor de {{varName}} não pode ter mais de {{maxLength}} caracteres', atLeastOne: 'Por favor, insira pelo menos uma linha no arquivo enviado.', }, + executions: '{{num}} EXECUÇÕES', + execution: 'EXECUÇÃO', + }, + login: { + backToHome: 'Voltar para a página inicial', }, } diff --git a/web/i18n/pt-BR/time.ts b/web/i18n/pt-BR/time.ts index e2410dd34b..fcf25cab1e 100644 --- a/web/i18n/pt-BR/time.ts +++ b/web/i18n/pt-BR/time.ts @@ -1,3 +1,37 @@ -const translation = {} +const translation = { + daysInWeek: { + Wed: 'Quarta-feira', + Tue: 'Terça-feira', + Sun: 'Sol', + Thu: 'Quinta-feira', + Fri: 'Sexta', + Sat: 'Sábado', + Mon: 'Mon', + }, + months: { + May: 'Maio', + February: 'Fevereiro', + April: 'abril', + September: 'Setembro', + March: 'Março', + December: 'Dezembro', + November: 'Novembro', + October: 'Outubro', + July: 'Julho', + August: 'Agosto', + June: 'junho', + January: 'Janeiro', + }, + operation: { + pickDate: 'Escolher Data', + ok: 'OK', + cancel: 'Cancelar', + now: 'Agora', + }, + title: { + pickTime: 'Escolha o Horário', + }, + defaultPlaceholder: 'Escolha um horário...', +} export default translation diff --git a/web/i18n/pt-BR/tools.ts b/web/i18n/pt-BR/tools.ts index f2eaa36979..dde7add80a 100644 --- a/web/i18n/pt-BR/tools.ts +++ b/web/i18n/pt-BR/tools.ts @@ -14,7 +14,6 @@ const translation = { }, author: 'Por', auth: { - unauthorized: 'Para Autorizar', authorized: 'Autorizado', setup: 'Configurar autorização para usar', setupModalTitle: 'Configurar Autorização', diff --git a/web/i18n/pt-BR/workflow.ts b/web/i18n/pt-BR/workflow.ts index 3d2b3fe8f0..1c349303fb 100644 --- a/web/i18n/pt-BR/workflow.ts +++ b/web/i18n/pt-BR/workflow.ts @@ -38,8 +38,6 @@ const translation = { setVarValuePlaceholder: 'Definir valor da variável', needConnectTip: 'Este passo não está conectado a nada', maxTreeDepth: 'Limite máximo de {{depth}} nós por ramo', - needEndNode: 'O bloco de fim deve ser adicionado', - needAnswerNode: 'O bloco de resposta deve ser adicionado', workflowProcess: 'Processo de fluxo de trabalho', notRunning: 'Ainda não está em execução', previewPlaceholder: 'Digite o conteúdo na caixa abaixo para começar a depurar o Chatbot', @@ -58,7 +56,6 @@ const translation = { learnMore: 'Saiba mais', copy: 'Copiar', duplicate: 'Duplicar', - addBlock: 'Adicionar bloco', pasteHere: 'Colar aqui', pointerMode: 'Modo ponteiro', handMode: 'Modo mão', @@ -106,6 +103,18 @@ const translation = { addFailureBranch: 'Adicionar ramificação com falha', noHistory: 'Sem História', loadMore: 'Carregar mais fluxos de trabalho', + exportPNG: 'Exportar como PNG', + publishUpdate: 'Publicar Atualização', + versionHistory: 'Histórico de Versão', + exportImage: 'Exportar Imagem', + referenceVar: 'Variável de Referência', + noExist: 'Nenhuma variável desse tipo', + exitVersions: 'Versões de Sair', + exportSVG: 'Exportar como SVG', + exportJPEG: 'Exportar como JPEG', + addBlock: 'Adicionar Nó', + needEndNode: 'O nó de Fim deve ser adicionado', + needAnswerNode: 'O nó de resposta deve ser adicionado', }, env: { envPanelTitle: 'Variáveis de Ambiente', @@ -167,19 +176,19 @@ const translation = { stepForward_other: '{{count}} passos para frente', sessionStart: 'Início da sessão', currentState: 'Estado atual', - nodeTitleChange: 'Título do bloco alterado', - nodeDescriptionChange: 'Descrição do bloco alterada', - nodeDragStop: 'Bloco movido', - nodeChange: 'Bloco alterado', - nodeConnect: 'Bloco conectado', - nodePaste: 'Bloco colado', - nodeDelete: 'Bloco excluído', - nodeAdd: 'Bloco adicionado', - nodeResize: 'Nota redimensionada', noteAdd: 'Nota adicionada', noteChange: 'Nota alterada', noteDelete: 'Conexão excluída', - edgeDelete: 'Bloco desconectado', + nodeConnect: 'Nó conectado', + nodeDelete: 'Nó deletado', + nodePaste: 'Nó colado', + nodeTitleChange: 'Título do nó alterado', + nodeAdd: 'Nó adicionado', + nodeDescriptionChange: 'Descrição do nó alterada', + edgeDelete: 'Nó desconectado', + nodeResize: 'Nó redimensionado', + nodeChange: 'Nó alterado', + nodeDragStop: 'Nó movido', }, errorMsg: { fieldRequired: '{{field}} é obrigatório', @@ -205,10 +214,9 @@ const translation = { testRunIteration: 'Iteração de execução de teste', back: 'Voltar', iteration: 'Iteração', + loop: 'Laço', }, tabs: { - 'searchBlock': 'Buscar bloco', - 'blocks': 'Blocos', 'tools': 'Ferramentas', 'allTool': 'Todos', 'builtInTool': 'Integrado', @@ -222,6 +230,8 @@ const translation = { 'searchTool': 'Ferramenta de pesquisa', 'plugin': 'Plug-in', 'agent': 'Estratégia do agente', + 'blocks': 'Nodos', + 'searchBlock': 'Nó de busca', }, blocks: { 'start': 'Iniciar', @@ -243,6 +253,9 @@ const translation = { 'list-operator': 'Operador de lista', 'document-extractor': 'Extrator de documentos', 'agent': 'Agente', + 'loop-end': 'Sair do Loop', + 'loop-start': 'Início do Loop', + 'loop': 'Laço', }, blocksAbout: { 'start': 'Definir os parâmetros iniciais para iniciar um fluxo de trabalho', @@ -263,6 +276,8 @@ const translation = { 'document-extractor': 'Usado para analisar documentos carregados em conteúdo de texto que é facilmente compreensível pelo LLM.', 'list-operator': 'Usado para filtrar ou classificar o conteúdo da matriz.', 'agent': 'Invocar grandes modelos de linguagem para responder a perguntas ou processar linguagem natural', + 'loop-end': 'Equivalente a "break". Este nó não possui itens de configuração. Quando o corpo do loop atinge este nó, o loop termina.', + 'loop': 'Execute um loop de lógica até que a condição de término seja atendida ou o número máximo de loops seja alcançado.', }, operator: { zoomIn: 'Aproximar', @@ -273,20 +288,21 @@ const translation = { }, panel: { userInputField: 'Campo de entrada do usuário', - changeBlock: 'Mudar bloco', helpLink: 'Link de ajuda', about: 'Sobre', createdBy: 'Criado por ', nextStep: 'Próximo passo', - addNextStep: 'Adicionar o próximo bloco neste fluxo de trabalho', - selectNextStep: 'Selecionar próximo bloco', runThisStep: 'Executar este passo', checklist: 'Lista de verificação', checklistTip: 'Certifique-se de que todos os problemas foram resolvidos antes de publicar', checklistResolved: 'Todos os problemas foram resolvidos', - organizeBlocks: 'Organizar blocos', change: 'Mudar', optional: '(opcional)', + moveToThisNode: 'Mova-se para este nó', + changeBlock: 'Mudar Nó', + addNextStep: 'Adicione o próximo passo neste fluxo de trabalho', + organizeBlocks: 'Organizar nós', + selectNextStep: 'Selecione o próximo passo', }, nodes: { common: { @@ -404,6 +420,34 @@ const translation = { variable: 'Variável', }, sysQueryInUser: 'sys.query na mensagem do usuário é necessário', + jsonSchema: { + warningTips: { + saveSchema: 'Por favor, termine de editar o campo atual antes de salvar o esquema.', + }, + instruction: 'Instrução', + showAdvancedOptions: 'Mostrar opções avançadas', + addField: 'Adicionar Campo', + descriptionPlaceholder: 'Adicionar descrição', + promptTooltip: 'Converta a descrição do texto em uma estrutura de esquema JSON padronizada.', + generating: 'Gerando esquema JSON...', + generate: 'Gerar', + title: 'Esquema de Saída Estruturada', + promptPlaceholder: 'Descreva seu Esquema JSON...', + back: 'Voltar', + doc: 'Saiba mais sobre saída estruturada', + regenerate: 'Regenerar', + resultTip: 'Aqui está o resultado gerado. Se você não estiver satisfeito, pode voltar e modificar seu pedido.', + addChildField: 'Adicionar Campo de Criança', + generationTip: 'Você pode usar linguagem natural para criar rapidamente um esquema JSON.', + generatedResult: 'Resultado Gerado', + import: 'Importar de JSON', + generateJsonSchema: 'Gerar Esquema JSON', + fieldNamePlaceholder: 'Nome do Campo', + resetDefaults: 'Reiniciar', + stringValidations: 'Validações de String', + apply: 'Aplicar', + required: 'obrigatório', + }, }, knowledgeRetrieval: { queryVariable: 'Variável de consulta', @@ -416,6 +460,33 @@ const translation = { url: 'URL segmentado', metadata: 'Outros metadados', }, + metadata: { + options: { + disabled: { + subTitle: 'Não ativando a filtragem de metadados', + title: 'Desativado', + }, + automatic: { + desc: 'Gere automaticamente condições de filtragem de metadados com base na Variável de Consulta', + title: 'Automático', + subTitle: 'Gerar automaticamente condições de filtragem de metadados com base na consulta do usuário', + }, + manual: { + title: 'Manual', + subTitle: 'Adicione manualmente as condições de filtragem de metadados', + }, + }, + panel: { + add: 'Adicionar Condição', + select: 'Selecione a variável...', + datePlaceholder: 'Escolha um horário...', + search: 'Pesquisar metadados', + conditions: 'Condições', + title: 'Condições de filtro de metadados', + placeholder: 'Insira o valor', + }, + title: 'Filtragem de Metadados', + }, }, http: { inputVars: 'Variáveis de entrada', @@ -505,6 +576,8 @@ const translation = { 'exists': 'Existe', 'not exists': 'não existe', 'all of': 'todos os', + 'after': 'depois', + 'before': 'antes', }, enterValue: 'Digite o valor', addCondition: 'Adicionar condição', @@ -520,6 +593,7 @@ const translation = { }, addSubVariable: 'Subvariável', select: 'Selecionar', + condition: 'Condição', }, variableAssigner: { title: 'Atribuir variáveis', @@ -562,6 +636,8 @@ const translation = { '+=': '+=', 'set': 'Pôr', 'overwrite': 'Sobrescrever', + 'remove-last': 'Remover Último', + 'remove-first': 'Remover Primeiro', }, 'selectAssignedVariable': 'Selecione a variável atribuída...', 'setParameter': 'Definir parâmetro...', @@ -572,7 +648,6 @@ const translation = { 'variables': 'Variáveis', }, tool: { - toAuthorize: 'Autorizar', inputVars: 'Variáveis de entrada', outputVars: { text: 'conteúdo gerado pela ferramenta', @@ -585,6 +660,7 @@ const translation = { }, json: 'JSON gerado por ferramenta', }, + authorize: 'Autorizar', }, questionClassifiers: { model: 'modelo', @@ -766,6 +842,38 @@ const translation = { toolNotAuthorizedTooltip: '{{ferramenta}} Não autorizado', toolbox: 'caixa de ferramentas', }, + loop: { + ErrorMethod: { + removeAbnormalOutput: 'Remover Saída Anormal', + operationTerminated: 'Terminado', + continueOnError: 'Continue em Caso de Erro', + }, + errorResponseMethod: 'Método de Resposta de Erro', + loop_one: '{{count}} Loop', + inputMode: 'Modo de Entrada', + setLoopVariables: 'Defina variáveis dentro do escopo do loop', + totalLoopCount: 'Contagem total de loops: {{count}}', + breakCondition: 'Condição de Término de Loop', + comma: ',', + input: 'Entrada', + variableName: 'Nome da Variável', + initialLoopVariables: 'Variáveis de Loop Iniciais', + exitConditionTip: 'Um nó de loop precisa de pelo menos uma condição de saída', + loopNode: 'Nó de Loop', + loopMaxCount: 'Contagem Máxima de Loop', + currentLoopCount: 'Contagem atual de loops: {{count}}', + deleteTitle: 'Excluir Nó de Loop?', + error_other: '{{count}} Erros', + loop_other: '{{count}} Laços', + output: 'Variável de Saída', + error_one: '{{count}} Erro', + finalLoopVariables: 'Variáveis do Loop Final', + loopMaxCountError: 'Por favor, insira um limite máximo de loop válido, variando de 1 a {{maxCount}}', + loopVariables: 'Variáveis de Loop', + breakConditionTip: 'Somente variáveis dentro de laços com condições de término e variáveis de conversa podem ser referenciadas.', + currentLoop: 'Laço Atual', + deleteDesc: 'A exclusão do nó de loop removerá todos os nós filhos', + }, }, tracing: { stopBy: 'Parado por {{user}}', @@ -777,6 +885,38 @@ const translation = { assignedVarsDescription: 'As variáveis atribuídas devem ser variáveis graváveis, como', noAvailableVars: 'Nenhuma variável disponível', }, + versionHistory: { + filter: { + all: 'Todos', + empty: 'Nenhuma versão histórica correspondente encontrada', + reset: 'Redefinir Filtro', + onlyYours: 'Somente seu', + onlyShowNamedVersions: 'Mostre apenas versões nomeadas', + }, + editField: { + titleLengthLimit: 'O título não pode exceder {{limit}} caracteres', + releaseNotes: 'Notas de Lançamento', + releaseNotesLengthLimit: 'As notas de lançamento não podem exceder {{limit}} caracteres', + title: 'Título', + }, + action: { + updateFailure: 'Falha ao atualizar a versão', + updateSuccess: 'Versão atualizada', + deleteSuccess: 'Versão excluída', + restoreFailure: 'Falha ao restaurar versão', + restoreSuccess: 'Versão restaurada', + deleteFailure: 'Falha ao deletar versão', + }, + title: 'Versões', + latest: 'Último', + nameThisVersion: 'Nomeie esta versão', + defaultName: 'Versão Sem Título', + releaseNotesPlaceholder: 'Descreva o que mudou', + editVersionInfo: 'Editar informações da versão', + restorationTip: 'Após a restauração da versão, o rascunho atual será substituído.', + currentDraft: 'Rascunho Atual', + deletionTip: 'A exclusão é irreversível, por favor confirme.', + }, } export default translation diff --git a/web/i18n/ro-RO/app-debug.ts b/web/i18n/ro-RO/app-debug.ts index bafeee8bb0..f7240055e3 100644 --- a/web/i18n/ro-RO/app-debug.ts +++ b/web/i18n/ro-RO/app-debug.ts @@ -285,6 +285,7 @@ const translation = { 'labelName': 'Nume etichetă', 'inputPlaceholder': 'Vă rugăm să introduceți', 'required': 'Obligatoriu', + 'hide': 'Ascundeți', 'errorMsg': { varNameRequired: 'Numele variabilei este obligatoriu', labelNameRequired: 'Numele etichetei este obligatoriu', diff --git a/web/i18n/ro-RO/app-overview.ts b/web/i18n/ro-RO/app-overview.ts index 35ee79d61c..04b7540ff9 100644 --- a/web/i18n/ro-RO/app-overview.ts +++ b/web/i18n/ro-RO/app-overview.ts @@ -49,7 +49,7 @@ const translation = { show: 'Afișați', hide: 'Ascundeți', subTitle: 'Detalii despre fluxul de lucru', - showDesc: 'Afișarea sau ascunderea detaliilor fluxului de lucru în WebApp', + showDesc: 'Afișarea sau ascunderea detaliilor fluxului de lucru în web app', }, chatColorTheme: 'Tema de culoare a chatului', chatColorThemeDesc: 'Setați tema de culoare a chatbotului', @@ -71,9 +71,9 @@ const translation = { }, sso: { label: 'Autentificare SSO', - title: 'WebApp SSO', - description: 'Toți utilizatorii trebuie să se conecteze cu SSO înainte de a utiliza WebApp', - tooltip: 'Contactați administratorul pentru a activa WebApp SSO', + title: 'web app SSO', + description: 'Toți utilizatorii trebuie să se conecteze cu SSO înainte de a utiliza web app', + tooltip: 'Contactați administratorul pentru a activa web app SSO', }, modalTip: 'Setările aplicației web pe partea clientului.', }, diff --git a/web/i18n/ro-RO/app.ts b/web/i18n/ro-RO/app.ts index 3f288c1396..cd267e7b66 100644 --- a/web/i18n/ro-RO/app.ts +++ b/web/i18n/ro-RO/app.ts @@ -73,26 +73,26 @@ const translation = { appCreateDSLErrorPart1: 'A fost detectată o diferență semnificativă în versiunile DSL. Forțarea importului poate cauza funcționarea defectuoasă a aplicației.', appCreateDSLErrorPart4: 'Versiune DSL suportată de sistem:', chatbotShortDescription: 'Chatbot bazat pe LLM cu configurare simplă', - forBeginners: 'PENTRU ÎNCEPĂTORI', + forBeginners: 'Tipuri de aplicații mai simple', completionShortDescription: 'Asistent AI pentru sarcini de generare de text', agentUserDescription: 'Un agent inteligent capabil de raționament iterativ și utilizare autonomă a instrumentelor pentru a atinge obiectivele sarcinii.', - workflowUserDescription: 'Orchestrarea fluxului de lucru pentru sarcini cu o singură rundă, cum ar fi automatizarea și procesarea în loturi.', + workflowUserDescription: 'Construiește vizual fluxuri AI autonome cu simplitatea drag-and-drop.', optional: 'Facultativ', learnMore: 'Află mai multe', completionUserDescription: 'Construiește rapid un asistent AI pentru sarcinile de generare a textului cu o configurare simplă.', chatbotUserDescription: 'Construiți rapid un chatbot bazat pe LLM cu o configurare simplă. Puteți trece la Chatflow mai târziu.', - advancedShortDescription: 'Flux de lucru pentru dialoguri complexe cu mai multe rotații cu memorie', - advancedUserDescription: 'Orchestrarea fluxului de lucru pentru sarcini complexe de dialog cu mai multe runde cu capacități de memorie.', + advancedShortDescription: 'Flux de lucru îmbunătățit pentru conversații multi-tur', + advancedUserDescription: 'Flux de lucru cu funcții suplimentare de memorie și interfață de chatbot.', noTemplateFoundTip: 'Încercați să căutați folosind cuvinte cheie diferite.', foundResults: '{{număr}} Rezultatele', foundResult: '{{număr}} Rezultat', noIdeaTip: 'Nicio idee? Consultați șabloanele noastre', noAppsFound: 'Nu s-au găsit aplicații', - workflowShortDescription: 'Orchestrare pentru sarcini de automatizare cu o singură tură', + workflowShortDescription: 'Flux agentic pentru automatizări inteligente', agentShortDescription: 'Agent inteligent cu raționament și utilizare autonomă a uneltelor', noTemplateFound: 'Nu s-au găsit șabloane', forAdvanced: 'PENTRU UTILIZATORII AVANSAȚI', - chooseAppType: 'Alegeți tipul de aplicație', + chooseAppType: 'Alegeți un tip de aplicație', }, editApp: 'Editează Info', editAppTitle: 'Editează Info Aplicație', @@ -159,11 +159,15 @@ const translation = { description: 'Opik este o platformă open-source pentru evaluarea, testarea și monitorizarea aplicațiilor LLM.', title: 'Opik', }, + weave: { + title: 'Împletește', + description: 'Weave este o platformă open-source pentru evaluarea, testarea și monitorizarea aplicațiilor LLM.', + }, }, answerIcon: { - descriptionInExplore: 'Dacă să utilizați pictograma WebApp pentru a înlocui 🤖 în Explore', - description: 'Dacă se utilizează pictograma WebApp pentru a înlocui 🤖 în aplicația partajată', - title: 'Utilizați pictograma WebApp pentru a înlocui 🤖', + descriptionInExplore: 'Dacă să utilizați pictograma web app pentru a înlocui 🤖 în Explore', + description: 'Dacă se utilizează pictograma web app pentru a înlocui 🤖 în aplicația partajată', + title: 'Utilizați pictograma web app pentru a înlocui 🤖', }, importFromDSL: 'Import din DSL', importFromDSLUrl: 'De la URL', @@ -194,6 +198,54 @@ const translation = { noParams: 'Nu sunt necesari parametri', placeholder: 'Selectați o aplicație...', }, + structOutput: { + notConfiguredTip: 'Ieșirea structurată nu a fost configurată încă', + LLMResponse: 'Răspuns LLM', + required: 'Necesar', + moreFillTip: 'Afișând maxim 10 niveluri de imbricare', + structured: 'Structurat', + modelNotSupported: 'Modelul nu este suportat', + structuredTip: 'Ieșirile structurate sunt o caracteristică care asigură că modelul va genera întotdeauna răspunsuri care respectă schema JSON furnizată.', + configure: 'Configurează', + modelNotSupportedTip: 'Modelul actual nu suportă această funcție și este downgradat automat la injecția de prompt.', + }, + accessItemsDescription: { + specific: 'Numai grupuri sau membri specifici pot accesa aplicația web.', + organization: 'Oricine din organizație poate accesa aplicația web', + anyone: 'Oricine poate accesa aplicația web', + external: 'Numai utilizatorii externi autentificați pot accesa aplicația web', + }, + accessControlDialog: { + accessItems: { + anyone: 'Oricine are linkul', + specific: 'Grupuri sau membri specifici', + organization: 'Numai membrii din cadrul întreprinderii', + external: 'Utilizatori extern autentificați', + }, + operateGroupAndMember: { + searchPlaceholder: 'Caută grupuri și membri', + allMembers: 'Toți membrii', + expand: 'Expandează', + noResult: 'Niciun rezultat', + }, + title: 'Controlul Accesului la Aplicația Web', + description: 'Setați permisiunile de acces la aplicația web', + accessLabel: 'Cine are acces', + groups_one: '{{count}} GRUP', + groups_other: '{{count}} GRUPURI', + members_one: '{{count}} MEMBRU', + members_other: '{{count}} MEMBRI', + noGroupsOrMembers: 'Niciun grup sau membri selectați', + webAppSSONotEnabledTip: 'Vă rugăm să contactați administratorul de întreprindere pentru a configura metoda de autentificare a aplicației web.', + updateSuccess: 'Actualizare reușită', + }, + publishApp: { + title: 'Cine poate accesa aplicația web', + notSet: 'Nu este setat', + notSetDesc: 'În prezent, nimeni nu poate accesa aplicația web. Vă rugăm să setați permisiunile.', + }, + accessControl: 'Controlul Accesului la Aplicația Web', + noAccessPermission: 'Nici o permisiune pentru a accesa aplicația web', } export default translation diff --git a/web/i18n/ro-RO/billing.ts b/web/i18n/ro-RO/billing.ts index 707d892047..682641372d 100644 --- a/web/i18n/ro-RO/billing.ts +++ b/web/i18n/ro-RO/billing.ts @@ -69,6 +69,7 @@ const translation = { messageRequest: { title: 'Credite de mesaje', tooltip: 'Cote de invocare a mesajelor pentru diferite planuri utilizând modele OpenAI (cu excepția gpt4). Mesajele peste limită vor utiliza cheia API OpenAI.', + titlePerMonth: '{{count,number}} mesaje/lună', }, annotatedResponse: { title: 'Limite de cotă de anotare', @@ -77,27 +78,94 @@ const translation = { ragAPIRequestTooltip: 'Se referă la numărul de apeluri API care invocă doar capacitățile de procesare a bazei de cunoștințe a Dify.', receiptInfo: 'Doar proprietarul echipei și administratorul echipei pot să se aboneze și să vizualizeze informațiile de facturare', annotationQuota: 'Cota de adnotare', + priceTip: 'pe spațiu de lucru/', + teamMember_one: '{{count,number}} Membru al echipei', + unlimitedApiRate: 'Fără limită de rată API', + freeTrialTipPrefix: 'Înscrie-te și obține un', + self: 'Auto-găzduit', + apiRateLimit: 'Limită de rată API', + documentsTooltip: 'Cota pe numărul de documente importate din Sursele de Date de Cunoștințe.', + getStarted: 'Întrebați-vă', + cloud: 'Serviciu de cloud', + apiRateLimitUnit: '{{count,number}}/zi', + comparePlanAndFeatures: 'Compară planurile și caracteristicile', + documentsRequestQuota: '{{count,number}}/min Limita de rată a cererilor de cunoștințe', + documents: '{{count,number}} Documente de Cunoaștere', + freeTrialTipSuffix: 'Nu este necesară o carte de credit', + teamMember_other: '{{count,number}} membri ai echipei', + teamWorkspace: '{{count,number}} Spațiu de lucru în echipă', + annualBilling: 'Facturare anuala', + freeTrialTip: 'perioadă de probă gratuită de 200 de apeluri OpenAI.', + documentsRequestQuotaTooltip: 'Specificați numărul total de acțiuni pe care un spațiu de lucru le poate efectua pe minut în cadrul bazei de cunoștințe, inclusiv crearea, ștergerea, actualizările setului de date, încărcările de documente, modificările, arhivarea și interogările bazei de cunoștințe. Acest metric este utilizat pentru a evalua performanța cererilor din baza de cunoștințe. De exemplu, dacă un utilizator Sandbox efectuează 10 teste consecutive de hituri într-un minut, spațiul său de lucru va fi restricționat temporar de la efectuarea următoarelor acțiuni pentru minutul următor: crearea setului de date, ștergerea, actualizările și încărcările sau modificările documentelor.', + apiRateLimitTooltip: 'Limita de rată API se aplică tuturor cererilor efectuate prin API-ul Dify, inclusiv generarea de texte, conversațiile de chat, execuțiile fluxului de lucru și procesarea documentelor.', }, plans: { sandbox: { name: 'Sandbox', description: '200 de încercări gratuite GPT', includesTitle: 'Include:', + for: 'Proba gratuită a capacităților de bază', }, professional: { name: 'Professional', description: 'Pentru persoane fizice și echipe mici pentru a debloca mai multă putere la un preț accesibil.', includesTitle: 'Tot ce este în planul gratuit, plus:', + for: 'Pentru dezvoltatori independenți / echipe mici', }, team: { name: 'Echipă', description: 'Colaborați fără limite și bucurați-vă de performanțe de top.', includesTitle: 'Tot ce este în planul Professional, plus:', + for: 'Pentru echipe de dimensiuni medii', }, enterprise: { name: 'Întreprindere', description: 'Obțineți capacități și asistență complete pentru sisteme critice la scară largă.', includesTitle: 'Tot ce este în planul Echipă, plus:', + features: { + 3: 'Multiple Spații de lucru și Management Enterprise', + 6: 'Securitate avansată și control', + 2: 'Funcții exclusive pentru întreprinderi', + 8: 'Asistență tehnică profesională', + 4: 'SSO', + 7: 'Actualizări și întreținere de către Dify Oficial', + 1: 'Autorizare pentru licență comercială', + 5: 'SLA-uri negociate de partenerii Dify', + 0: 'Soluții de desfășurare scalabile de nivel enterprise', + }, + for: 'Pentru echipe de mari dimensiuni', + price: 'Personalizat', + priceTip: 'Facturare anuală doar', + btnText: 'Contactați Vânzări', + }, + community: { + features: { + 2: 'Se conformează Licenței Open Source Dify', + 1: 'Spațiu de lucru unic', + 0: 'Toate funcțiile de bază lansate sub depozitul public', + }, + description: 'Pentru utilizatori individuali, echipe mici sau proiecte necomerciale', + btnText: 'Începe cu Comunitatea', + price: 'Gratuit', + name: 'Comunitate', + for: 'Pentru utilizatori individuali, echipe mici sau proiecte necomerciale', + includesTitle: 'Funcții gratuite:', + }, + premium: { + features: { + 1: 'Spațiu de lucru unic', + 0: 'Fiabilitate autogestionată de diferiți furnizori de cloud', + 2: 'Personalizarea logo-ului și branding-ului aplicației web', + 3: 'Suport prioritar prin email și chat', + }, + btnText: 'Obține Premium în', + description: 'Pentru organizații și echipe de dimensiuni medii', + includesTitle: 'Totul din Comunitate, plus:', + price: 'Scalabil', + name: 'Premium', + priceTip: 'Pe baza Pieței Cloud', + comingSoon: 'Suport Microsoft Azure și Google Cloud în curând', + for: 'Pentru organizații și echipe de dimensiuni medii', }, }, vectorSpace: { @@ -107,12 +175,26 @@ const translation = { apps: { fullTipLine1: 'Actualizați-vă planul pentru a', fullTipLine2: 'construi mai multe aplicații.', + fullTip2des: 'Se recomandă curățarea aplicațiilor inactive pentru a elibera resurse, sau contactați-ne.', + fullTip2: 'Limita planului a fost atinsă', + fullTip1des: 'Ați atins limita de aplicații construite pe acest plan', + fullTip1: 'Upgrade pentru a crea mai multe aplicații', + contactUs: 'Contactați-ne', }, annotatedResponse: { fullTipLine1: 'Actualizați-vă planul pentru a', fullTipLine2: 'anota mai multe conversații.', quotaTitle: 'Cotă de răspuns anotat', }, + usagePage: { + vectorSpaceTooltip: 'Documentele cu modul de indexare de calitate înaltă vor consuma resursele de stocare a datelor de cunoștințe. Când stocarea datelor de cunoștințe atinge limita, documentele noi nu vor fi încărcate.', + buildApps: 'Construiește aplicații', + vectorSpace: 'Stocarea datelor de cunoștințe', + teamMembers: 'Membrii echipei', + annotationQuota: 'Cota de Anotare', + documentsUploadQuota: 'Cota de încărcare a documentelor', + }, + teamMembers: 'Membrii echipei', } export default translation diff --git a/web/i18n/ro-RO/common.ts b/web/i18n/ro-RO/common.ts index ad000e26c4..f736240a78 100644 --- a/web/i18n/ro-RO/common.ts +++ b/web/i18n/ro-RO/common.ts @@ -54,6 +54,10 @@ const translation = { copied: 'Copiat', in: 'în', viewDetails: 'Vezi detalii', + downloadFailed: 'Descărcarea a eșuat. Vă rugăm să încercați din nou mai târziu.', + format: 'Format', + downloadSuccess: 'Descărcarea a fost finalizată.', + more: 'Mai mult', }, placeholder: { input: 'Vă rugăm să introduceți', @@ -141,6 +145,8 @@ const translation = { newDataset: 'Creează Cunoștințe', tools: 'Instrumente', exploreMarketplace: 'Explorați Marketplace', + appDetail: 'Detalii aplicație', + account: 'Cont', }, userProfile: { settings: 'Setări', @@ -153,6 +159,9 @@ const translation = { community: 'Comunitate', about: 'Despre', logout: 'Deconectare', + github: 'GitHub', + support: 'Suport', + compliance: 'Conformitate', }, settings: { accountGroup: 'CONT', @@ -202,6 +211,9 @@ const translation = { feedbackPlaceholder: 'Facultativ', feedbackTitle: 'Feedback', verificationLabel: 'Cod de verificare', + workspaceName: 'Numele spațiului de lucru', + editWorkspaceInfo: 'Editează informațiile spațiului de lucru', + workspaceIcon: 'Iconița de spațiu de lucru', }, members: { team: 'Echipă', @@ -455,7 +467,7 @@ const translation = { apiBasedExtension: { title: 'Extensiile bazate pe API oferă o gestionare centralizată a API-urilor, simplificând configurația pentru o utilizare ușoară în aplicațiile Dify.', link: 'Aflați cum să dezvoltați propria extensie bazată pe API.', - linkUrl: 'https://docs.dify.ai/features/extension/api_based_extension', + linkUrl: 'https://docs.dify.ai/en/guides/extension/api-based-extension/README', add: 'Adăugați extensie API', selector: { title: 'Extensie API', @@ -540,9 +552,10 @@ const translation = { vectorHash: 'Hash vector:', hitScore: 'Scor de recuperare:', }, - inputPlaceholder: 'Vorbește cu Bot', + inputPlaceholder: 'Vorbește cu {{botName}}', thinking: 'Gândire...', thought: 'Gând', + resend: 'Reexpediați', }, promptEditor: { placeholder: 'Scrieți aici prompt-ul, introduceți \'{}\' pentru a insera o variabilă, introduceți \'/\' pentru a insera un bloc de conținut prompt', @@ -633,10 +646,31 @@ const translation = { license: { expiring: 'Expiră într-o zi', expiring_plural: 'Expiră în {{count}} zile', + unlimited: 'Nelimitat', }, pagination: { perPage: 'Articole pe pagină', }, + theme: { + theme: 'Temă', + light: 'lumina', + auto: 'sistem', + dark: 'întunecat', + }, + compliance: { + sandboxUpgradeTooltip: 'Disponibilă doar cu un plan Profesional sau de Echipă.', + iso27001: 'Certificare ISO 27001:2022', + professionalUpgradeTooltip: 'Disponibilă doar cu un plan de echipă sau superior.', + gdpr: 'GDPR DPA', + soc2Type1: 'Raport SOC 2 Tip I', + soc2Type2: 'Raport SOC 2 Tip II', + }, + imageInput: { + supportedFormats: 'Suportă PNG, JPG, JPEG, WEBP și GIF', + browse: 'naviga', + dropImageHere: 'Trageți imaginea aici sau', + }, + you: 'Tu', } export default translation diff --git a/web/i18n/ro-RO/custom.ts b/web/i18n/ro-RO/custom.ts index 0e10d59ec0..0f90836172 100644 --- a/web/i18n/ro-RO/custom.ts +++ b/web/i18n/ro-RO/custom.ts @@ -3,9 +3,11 @@ const translation = { upgradeTip: { prefix: 'Actualizați-vă planul pentru a', suffix: 'să vă personalizați marca.', + des: 'Îmbunătățește-ți planul pentru a-ți personaliza marca', + title: 'Upgradează-ți planul', }, webapp: { - title: 'Personalizați marca WebApp', + title: 'Personalizați marca web app', removeBrand: 'Eliminați "Powered by Dify"', changeLogo: 'Schimbați imaginea mărcii "Powered by"', changeLogoTip: 'Format SVG sau PNG cu o dimensiune minimă de 40x40px', diff --git a/web/i18n/ro-RO/dataset-creation.ts b/web/i18n/ro-RO/dataset-creation.ts index d3fd9fe04f..ce6872c654 100644 --- a/web/i18n/ro-RO/dataset-creation.ts +++ b/web/i18n/ro-RO/dataset-creation.ts @@ -22,7 +22,7 @@ const translation = { }, uploader: { title: 'Încărcați fișier text', - button: 'Trageți și fixați fișierul, sau', + button: 'Trageți și plasați fișiere sau foldere sau', browse: 'Răsfoire', tip: 'Acceptă {{supportTypes}}. Maxim {{size}}MB fiecare.', validation: { @@ -65,7 +65,7 @@ const translation = { firecrawlTitle: 'Extrageți conținut web cu 🔥Firecrawl', unknownError: 'Eroare necunoscută', scrapTimeInfo: 'Pagini răzuite {{total}} în total în {{timp}}s', - firecrawlDocLink: 'https://docs.dify.ai/guides/knowledge-base/sync-from-website', + firecrawlDocLink: 'https://docs.dify.ai/en/guides/knowledge-base/create-knowledge-and-upload-documents/import-content-data/sync-from-website', excludePaths: 'Excluderea căilor', resetAll: 'Resetați toate', extractOnlyMainContent: 'Extrageți doar conținutul principal (fără anteturi, navigări, subsoluri etc.)', @@ -82,6 +82,14 @@ const translation = { jinaReaderTitle: 'Convertiți întregul site în Markdown', jinaReaderNotConfigured: 'Jina Reader nu este configurat', useSitemapTooltip: 'Urmați harta site-ului pentru a accesa cu crawlere site-ul. Dacă nu, Jina Reader va accesa cu crawlere iterativ în funcție de relevanța paginii, producând mai puține pagini, dar de calitate superioară.', + waterCrawlNotConfigured: 'Watercrawl nu este configurat', + watercrawlTitle: 'Extrageți conținut web cu Watercrawl', + configureJinaReader: 'Configurează Jina Reader', + waterCrawlNotConfiguredDescription: 'Configurează Watercrawl cu cheia API pentru a-l folosi.', + watercrawlDocLink: 'https://docs.dify.ai/en/guides/knowledge-base/create-knowledge-and-upload-documents/import-content-data/sync-from-website', + configureFirecrawl: 'Configurează Firecrawl', + watercrawlDoc: 'Documentele Watercrawl', + configureWatercrawl: 'Configurează Watercrawl', }, cancel: 'Anula', }, @@ -200,6 +208,11 @@ const translation = { description: 'În prezent, baza de cunoștințe a Dify are doar surse de date limitate. Contribuția cu o sursă de date la baza de cunoștințe Dify este o modalitate fantastică de a ajuta la îmbunătățirea flexibilității și puterii platformei pentru toți utilizatorii. Ghidul nostru de contribuție vă ajută să începeți. Vă rugăm să faceți clic pe linkul de mai jos pentru a afla mai multe.', learnMore: 'Află mai multe', }, + watercrawl: { + getApiKeyLinkText: 'Obțineți cheia dvs. API de la watercrawl.dev', + apiKeyPlaceholder: 'Cheia API de la watercrawl.dev', + configWatercrawl: 'Configurează Watercrawl', + }, } export default translation diff --git a/web/i18n/ro-RO/dataset-settings.ts b/web/i18n/ro-RO/dataset-settings.ts index 0627b08b79..9d39f8ad95 100644 --- a/web/i18n/ro-RO/dataset-settings.ts +++ b/web/i18n/ro-RO/dataset-settings.ts @@ -25,6 +25,7 @@ const translation = { learnMore: 'Aflați mai multe', description: ' despre metoda de recuperare.', longDescription: ' despre metoda de recuperare, o puteți schimba în orice moment în setările cunoștințelor.', + method: 'Metoda de recuperare', }, save: 'Salvare', permissionsInvitedMembers: 'Membri parțiali ai echipei', diff --git a/web/i18n/ro-RO/dataset.ts b/web/i18n/ro-RO/dataset.ts index 2feff67596..dd7b2d6900 100644 --- a/web/i18n/ro-RO/dataset.ts +++ b/web/i18n/ro-RO/dataset.ts @@ -168,6 +168,54 @@ const translation = { localDocs: 'Documente locale', allKnowledge: 'Toate cunoștințele', allKnowledgeDescription: 'Selectați pentru a afișa toate cunoștințele din acest spațiu de lucru. Doar proprietarul spațiului de lucru poate gestiona toate cunoștințele.', + metadata: { + createMetadata: { + name: 'Nume', + type: 'Tip', + back: 'Înapoi', + namePlaceholder: 'Adăugați numele de metadate', + title: 'Metadate noi', + }, + checkName: { + invalid: 'Numele metadatelor poate conține doar litere mici, cifre și underscore și trebuie să înceapă cu o literă mică.', + empty: 'Numele metadatelor nu poate fi gol', + }, + batchEditMetadata: { + multipleValue: 'Valoare multiplă', + editMetadata: 'Editează metadatele', + applyToAllSelectDocument: 'Aplică la toate documentele selectate', + editDocumentsNum: 'Editarea {{num}} documente', + applyToAllSelectDocumentTip: 'Creează automat toate metadatele editate și noi de mai sus pentru toate documentele selectate, altfel editarea metadatelor se va aplica doar documentelor care au aceste metadate.', + }, + selectMetadata: { + manageAction: 'Gestionează', + search: 'Căutare metadate', + newAction: 'Metadate noi', + }, + datasetMetadata: { + deleteTitle: 'Confirmă ștergerea', + namePlaceholder: 'Numele metadata', + builtIn: 'Încărcat în', + values: '{{num}} Valori', + name: 'Nume', + disabled: 'Dezactivat', + deleteContent: 'Ești sigur că vrei să ștergi metadata „{{name}}”?}', + builtInDescription: 'Metadatele încorporate sunt extrase și generate automat. Acestea trebuie să fie activate înainte de utilizare și nu pot fi editate.', + description: 'Puteți gestiona toate metadatele în această cunoaștere aici. Modificările vor fi sincronizate cu fiecare document.', + addMetaData: 'Adăugați Metadate', + rename: 'Renumire', + }, + documentMetadata: { + startLabeling: 'Începe etichetarea', + documentInformation: 'Informații despre document', + technicalParameters: 'Parametrii tehnici', + metadataToolTip: 'Metadata serve ca un filtru critic care îmbunătățește acuratețea și relevanța recuperării informațiilor. Puteți modifica și adăuga metadata pentru acest document aici.', + }, + metadata: 'Metadate', + addMetadata: 'Adăugați Metadate', + chooseTime: 'Alege o oră...', + }, + embeddingModelNotAvailable: 'Modelul de încorporare nu este disponibil.', } export default translation diff --git a/web/i18n/ro-RO/education.ts b/web/i18n/ro-RO/education.ts new file mode 100644 index 0000000000..cda09528aa --- /dev/null +++ b/web/i18n/ro-RO/education.ts @@ -0,0 +1,47 @@ +const translation = { + toVerifiedTip: { + coupon: 'cupom exclusiv 100%', + front: 'Sunteți acum eligibil pentru statutul de Educație Verificată. Vă rugăm să introduceți informațiile despre educația dumneavoastră mai jos pentru a finaliza procesul și a primi un', + end: 'pentru Planul Profesional Dify.', + }, + form: { + schoolName: { + placeholder: 'Introduceți numele oficial, neabbreviat al școlii dumneavoastră', + title: 'Numele Școlii Tale', + }, + schoolRole: { + option: { + teacher: 'Profesor', + administrator: 'Administrator școlar', + student: 'Student', + }, + title: 'Rolul tău la școală', + }, + terms: { + desc: { + and: 'și', + front: 'Informațiile tale și utilizarea statutului de Educație Verificată sunt supuse termenilor noștri', + end: 'Prin trimiterea:', + termsOfService: 'Termeni și condiții', + privacyPolicy: 'Politica de confidenţialitate', + }, + option: { + inSchool: 'Confirm că sunt înscris sau angajat la instituția menționată. Dify poate solicita dovada înscrierii/angajării. Dacă îmi reprezint greșit eligibilitatea, sunt de acord să plătesc orice taxe inițial renunțate pe baza statutului meu educațional.', + age: 'Confirm că am cel puțin 18 ani', + }, + title: 'Termeni și condiții', + }, + }, + toVerified: 'Obțineți verificarea educației', + submitError: 'Trimiterea formularului a eşuat. Vă rugăm să încercați din nou mai târziu.', + rejectContent: 'Din păcate, nu ești eligibil pentru statutul de Verificat Educațional și, prin urmare, nu poți primi cuponul exclusiv de 100% pentru Planul Profesional Dify dacă folosești această adresă de email.', + successTitle: 'Ai obținut educația Dify verificată', + learn: 'Învățați cum să verificați educația', + submit: 'Trimite', + emailLabel: 'Emailul tău curent', + currentSigned: 'CONEXIUNE ÎN PREZENT CA', + rejectTitle: 'Verificarea educațională Dify a fost respinsă', + successContent: 'Am emis un cupon de discount de 100% pentru planul Professional Dify pe contul dumneavoastră. Cuponul este valabil timp de un an, vă rugăm să îl utilizați în perioada de valabilitate.', +} + +export default translation diff --git a/web/i18n/ro-RO/explore.ts b/web/i18n/ro-RO/explore.ts index 75fc97b8a2..153b236200 100644 --- a/web/i18n/ro-RO/explore.ts +++ b/web/i18n/ro-RO/explore.ts @@ -37,6 +37,7 @@ const translation = { HR: 'Resurse Umane', Agent: 'Agent', Workflow: 'Flux de lucru', + Entertainment: 'Divertisment', }, } diff --git a/web/i18n/ro-RO/login.ts b/web/i18n/ro-RO/login.ts index 12878d46c0..6a6a6edc64 100644 --- a/web/i18n/ro-RO/login.ts +++ b/web/i18n/ro-RO/login.ts @@ -105,6 +105,11 @@ const translation = { licenseExpired: 'Licență expirată', licenseLost: 'Licență pierdută', licenseExpiredTip: 'Licența Dify Enterprise pentru spațiul de lucru a expirat. Contactați administratorul pentru a continua să utilizați Dify.', + webapp: { + noLoginMethod: 'Metoda de autentificare nu este configurată pentru aplicația web', + noLoginMethodTip: 'Vă rugăm să contactați administratorul sistemului pentru a adăuga o metodă de autentificare.', + disabled: 'Autentificarea webapp-ului este dezactivată. Vă rugăm să contactați administratorul sistemului pentru a o activa. Puteți încerca să folosiți aplicația direct.', + }, } export default translation diff --git a/web/i18n/ro-RO/plugin.ts b/web/i18n/ro-RO/plugin.ts index db21cbc40a..c3b85e1ffe 100644 --- a/web/i18n/ro-RO/plugin.ts +++ b/web/i18n/ro-RO/plugin.ts @@ -62,6 +62,7 @@ const translation = { descriptionPlaceholder: 'Scurtă descriere a scopului instrumentului, de exemplu, obțineți temperatura pentru o anumită locație.', toolLabel: 'Unealtă', uninstalledTitle: 'Instrumentul nu este instalat', + toolSetting: 'Setările instrumentului', }, endpointDeleteContent: 'Doriți să eliminați {{name}}?', strategyNum: '{{num}} {{strategie}} INCLUS', @@ -180,6 +181,8 @@ const translation = { moreFrom: 'Mai multe din Marketplace', and: 'și', viewMore: 'Vezi mai mult', + partnerTip: 'Verificat de un partener Dify', + verifiedTip: 'Verificat de Dify', }, task: { installError: '{{errorLength}} plugin-urile nu s-au instalat, faceți clic pentru a vizualiza', @@ -189,7 +192,6 @@ const translation = { installingWithSuccess: 'Instalarea pluginurilor {{installingLength}}, {{successLength}} succes.', installing: 'Instalarea pluginurilor {{installingLength}}, 0 terminat.', }, - submitPlugin: 'Trimite plugin', fromMarketplace: 'Din Marketplace', from: 'Din', findMoreInMarketplace: 'Află mai multe în Marketplace', @@ -204,6 +206,12 @@ const translation = { installAction: 'Instala', endpointsEnabled: '{{num}} seturi de puncte finale activate', searchCategories: 'Categorii de căutare', + metadata: { + title: 'Pluginuri', + }, + difyVersionNotCompatible: 'Versiunea curentă Dify nu este compatibilă cu acest plugin, vă rugăm să faceți upgrade la versiunea minimă necesară: {{minimalDifyVersion}}', + requestAPlugin: 'Solicitați un plugin', + publishPlugins: 'Publicați pluginuri', } export default translation diff --git a/web/i18n/ro-RO/share-app.ts b/web/i18n/ro-RO/share-app.ts index c9ec36ab03..41e38812c5 100644 --- a/web/i18n/ro-RO/share-app.ts +++ b/web/i18n/ro-RO/share-app.ts @@ -30,6 +30,12 @@ const translation = { }, tryToSolve: 'Încercați să rezolvați', temporarySystemIssue: 'Ne pare rău, problemă temporară a sistemului.', + expand: 'Extinde', + collapse: 'Restrânge', + chatFormTip: 'Setările chat-ului nu pot fi modificate după ce chat-ul a început.', + viewChatSettings: 'Vizualizează setările de chat', + newChatTip: 'Deja într-o discuție nouă', + chatSettingsTitle: 'Nouă configurare a chatului', }, generation: { tabs: { @@ -68,6 +74,11 @@ const translation = { moreThanMaxLengthLine: 'Rândul {{rowIndex}}: valoarea {{varName}} nu poate avea mai mult de {{maxLength}} caractere', atLeastOne: 'Vă rugăm să introduceți cel puțin un rând în fișierul încărcat.', }, + execution: 'EXECUȚIE', + executions: '{{num}} EXECUȚII', + }, + login: { + backToHome: 'Înapoi la Acasă', }, } diff --git a/web/i18n/ro-RO/time.ts b/web/i18n/ro-RO/time.ts index e2410dd34b..2b40803081 100644 --- a/web/i18n/ro-RO/time.ts +++ b/web/i18n/ro-RO/time.ts @@ -1,3 +1,37 @@ -const translation = {} +const translation = { + daysInWeek: { + Sun: 'Soare', + Thu: 'Joia', + Wed: 'Miercuri', + Tue: 'Marți', + Sat: 'Sat', + Mon: 'Mon', + Fri: 'Vineri', + }, + months: { + December: 'Decembrie', + February: 'Februarie', + April: 'Aprilie', + August: 'August', + September: 'septembrie', + March: 'Martie', + May: 'Mai', + July: 'Iulie', + June: 'Iunie', + October: 'Octombrie', + November: 'Noiembrie', + January: 'ianuarie', + }, + operation: { + cancel: 'Anulează', + pickDate: 'Alege o dată', + now: 'Acum', + ok: 'Bine', + }, + title: { + pickTime: 'Alegeți timpul', + }, + defaultPlaceholder: 'Alege o oră...', +} export default translation diff --git a/web/i18n/ro-RO/tools.ts b/web/i18n/ro-RO/tools.ts index f5e33889d0..44530754e3 100644 --- a/web/i18n/ro-RO/tools.ts +++ b/web/i18n/ro-RO/tools.ts @@ -14,7 +14,6 @@ const translation = { }, author: 'De', auth: { - unauthorized: 'Pentru a Autoriza', authorized: 'Autorizat', setup: 'Configurează autorizarea pentru a utiliza', setupModalTitle: 'Configurează Autorizarea', diff --git a/web/i18n/ro-RO/workflow.ts b/web/i18n/ro-RO/workflow.ts index b4aa035274..ae025a1103 100644 --- a/web/i18n/ro-RO/workflow.ts +++ b/web/i18n/ro-RO/workflow.ts @@ -38,8 +38,6 @@ const translation = { setVarValuePlaceholder: 'Setează valoarea variabilei', needConnectTip: 'Acest pas nu este conectat la nimic', maxTreeDepth: 'Limită maximă de {{depth}} noduri pe ramură', - needEndNode: 'Trebuie adăugat blocul de sfârșit', - needAnswerNode: 'Trebuie adăugat blocul de răspuns', workflowProcess: 'Proces de flux de lucru', notRunning: 'Încă nu rulează', previewPlaceholder: 'Introduceți conținutul în caseta de mai jos pentru a începe depanarea Chatbotului', @@ -58,7 +56,6 @@ const translation = { learnMore: 'Află mai multe', copy: 'Copiază', duplicate: 'Duplică', - addBlock: 'Adaugă bloc', pasteHere: 'Lipește aici', pointerMode: 'Modul pointer', handMode: 'Modul mână', @@ -106,6 +103,18 @@ const translation = { addFailureBranch: 'Adăugare ramură Fail', noHistory: 'Fără istorie', loadMore: 'Încărcați mai multe fluxuri de lucru', + exportImage: 'Exportă imaginea', + exportSVG: 'Exportă ca SVG', + exportPNG: 'Exportă ca PNG', + noExist: 'Nu există o astfel de variabilă', + exitVersions: 'Ieșire Versiuni', + versionHistory: 'Istoricul versiunilor', + publishUpdate: 'Publicați actualizarea', + referenceVar: 'Variabilă de referință', + exportJPEG: 'Exportă ca JPEG', + addBlock: 'Adaugă nod', + needAnswerNode: 'Nodul de răspuns trebuie adăugat', + needEndNode: 'Nodul de sfârșit trebuie adăugat', }, env: { envPanelTitle: 'Variabile de Mediu', @@ -167,19 +176,18 @@ const translation = { stepForward_other: '{{count}} pași înainte', sessionStart: 'Începutul sesiuni', currentState: 'Stare actuală', - nodeTitleChange: 'Titlul blocului a fost schimbat', - nodeDescriptionChange: 'Descrierea blocului a fost schimbată', - nodeDragStop: 'Bloc mutat', - nodeChange: 'Bloc schimbat', - nodeConnect: 'Bloc conectat', - nodePaste: 'Bloc lipit', - nodeDelete: 'Bloc șters', - nodeAdd: 'Bloc adăugat', - nodeResize: 'Bloc redimensionat', noteAdd: 'Notă adăugată', noteChange: 'Notă modificată', noteDelete: 'Notă ștearsă', - edgeDelete: 'Bloc deconectat', + nodeResize: 'Nod redimensionat', + nodeConnect: 'Nod conectat', + nodeTitleChange: 'Titlul nodului a fost schimbat', + nodeChange: 'Nodul s-a schimbat', + nodePaste: 'Node lipit', + nodeDelete: 'Nod șters', + nodeDescriptionChange: 'Descrierea nodului a fost modificată', + edgeDelete: 'Nod deconectat', + nodeAdd: 'Nod adăugat', }, errorMsg: { fieldRequired: '{{field}} este obligatoriu', @@ -205,10 +213,9 @@ const translation = { testRunIteration: 'Iterație rulare de test', back: 'Înapoi', iteration: 'Iterație', + loop: 'Loop', }, tabs: { - 'searchBlock': 'Caută bloc', - 'blocks': 'Blocuri', 'tools': 'Instrumente', 'allTool': 'Toate', 'builtInTool': 'Integrat', @@ -222,6 +229,8 @@ const translation = { 'searchTool': 'Instrument de căutare', 'agent': 'Strategia agentului', 'plugin': 'Plugin', + 'blocks': 'Noduri', + 'searchBlock': 'Căutare nod', }, blocks: { 'start': 'Începe', @@ -243,6 +252,9 @@ const translation = { 'list-operator': 'Operator de listă', 'document-extractor': 'Extractor de documente', 'agent': 'Agent', + 'loop': 'Loop', + 'loop-end': 'Ieșire din buclă', + 'loop-start': 'Întreținere buclă', }, blocksAbout: { 'start': 'Definiți parametrii inițiali pentru lansarea unui flux de lucru', @@ -263,6 +275,8 @@ const translation = { 'list-operator': 'Folosit pentru a filtra sau sorta conținutul matricei.', 'document-extractor': 'Folosit pentru a analiza documentele încărcate în conținut text care este ușor de înțeles de LLM.', 'agent': 'Invocarea modelelor lingvistice mari pentru a răspunde la întrebări sau pentru a procesa limbajul natural', + 'loop': 'Executați o buclă de logică până când condiția de terminare este îndeplinită sau numărul maxim de bucle este atins.', + 'loop-end': 'Echivalent cu „break”. Acest nod nu are elemente de configurare. Când corpul buclei ajunge la acest nod, bucla se termină.', }, operator: { zoomIn: 'Mărește', @@ -273,20 +287,21 @@ const translation = { }, panel: { userInputField: 'Câmp de introducere utilizator', - changeBlock: 'Schimbă blocul', helpLink: 'Link de ajutor', about: 'Despre', createdBy: 'Creat de ', nextStep: 'Pasul următor', - addNextStep: 'Adăugați următorul bloc în acest flux de lucru', - selectNextStep: 'Selectați următorul bloc', runThisStep: 'Rulează acest pas', checklist: 'Lista de verificare', checklistTip: 'Asigurați-vă că toate problemele sunt rezolvate înainte de publicare', checklistResolved: 'Toate problemele au fost rezolvate', - organizeBlocks: 'Organizează blocurile', change: 'Schimbă', optional: '(opțional)', + moveToThisNode: 'Mutați la acest nod', + organizeBlocks: 'Organizează nodurile', + addNextStep: 'Adăugați următorul pas în acest flux de lucru', + changeBlock: 'Schimbă nodul', + selectNextStep: 'Selectați Pasul Următor', }, nodes: { common: { @@ -404,6 +419,34 @@ const translation = { variable: 'Variabilă', }, sysQueryInUser: 'sys.query în mesajul utilizatorului este necesar', + jsonSchema: { + warningTips: { + saveSchema: 'Vă rugăm să terminați editarea câmpului curent înainte de a salva schema.', + }, + addChildField: 'Adăugați câmpul copil', + generationTip: 'Puteți folosi limbajul natural pentru a crea rapid un schema JSON.', + promptTooltip: 'Convertește descrierea textului într-o structură standardizată JSON Schema.', + resetDefaults: 'Resetează', + apply: 'Aplică', + instruction: 'Instrucțiune', + doc: 'Aflați mai multe despre ieșirea structurată', + stringValidations: 'Validările șirurilor', + title: 'Schema de Ieşire Structurată', + generateJsonSchema: 'Generați schema JSON', + generate: 'Generează', + import: 'Importă din JSON', + generating: 'Generarea schemei JSON...', + addField: 'Adaugă câmp', + regenerate: 'Regenerare', + generatedResult: 'Rezultatul generat', + descriptionPlaceholder: 'Adăugați o descriere', + showAdvancedOptions: 'Afișați opțiuni avansate', + resultTip: 'Iată rezultatul generat. Dacă nu ești mulțumit, poți să te întorci și să îți modifici cererea.', + fieldNamePlaceholder: 'Numele câmpului', + required: 'Necesar', + back: 'Înapoi', + promptPlaceholder: 'Descrie schema ta JSON...', + }, }, knowledgeRetrieval: { queryVariable: 'Variabilă de interogare', @@ -416,6 +459,33 @@ const translation = { url: 'URL segmentat', metadata: 'Alte metadate', }, + metadata: { + options: { + disabled: { + subTitle: 'Nu activarea filtrării metadatelor', + title: 'Dezactivat', + }, + automatic: { + subTitle: 'Generați automat condiții de filtrare a metadatelor pe baza interogării utilizatorului', + desc: 'Generați automat condiții de filtrare a metadatelor pe baza variabilei de interogare', + title: 'Automat', + }, + manual: { + subTitle: 'Adăugați manual condiții de filtrare a metadatelor', + title: 'Manual', + }, + }, + panel: { + conditions: 'Condiții', + select: 'Selectați variabila...', + title: 'Condiții de filtrare a metadatelor', + add: 'Adaugă condiție', + placeholder: 'Introduceți valoarea', + datePlaceholder: 'Alege o oră...', + search: 'Căutare metadate', + }, + title: 'Filtrarea metadatelor', + }, }, http: { inputVars: 'Variabile de intrare', @@ -505,6 +575,8 @@ const translation = { 'exists': 'Există', 'all of': 'Toate', 'not exists': 'nu există', + 'before': 'înainte', + 'after': 'după', }, enterValue: 'Introduceți valoarea', addCondition: 'Adăugați condiție', @@ -520,6 +592,7 @@ const translation = { }, select: 'Alege', addSubVariable: 'Subvariabilă', + condition: 'Condiție', }, variableAssigner: { title: 'Atribuie variabile', @@ -562,6 +635,8 @@ const translation = { 'over-write': 'Suprascrie', '/=': '/=', '-=': '-=', + 'remove-first': 'Îndepărtează primul', + 'remove-last': 'Îndepărtează ultimul', }, 'selectAssignedVariable': 'Selectați variabila atribuită...', 'varNotSet': 'Variabila NU este setată', @@ -572,7 +647,6 @@ const translation = { 'variables': 'Variabile', }, tool: { - toAuthorize: 'Autorizați', inputVars: 'Variabile de intrare', outputVars: { text: 'conținut generat de instrument', @@ -585,6 +659,7 @@ const translation = { }, json: 'JSON generat de instrument', }, + authorize: 'Autorizați', }, questionClassifiers: { model: 'model', @@ -766,6 +841,38 @@ const translation = { linkToPlugin: 'Link către pluginuri', model: 'model', }, + loop: { + ErrorMethod: { + removeAbnormalOutput: 'Elimină ieșirea anormală', + continueOnError: 'Continuați în caz de eroare', + operationTerminated: 'Încetat', + }, + inputMode: 'Mod de introducere', + currentLoopCount: 'Numărul curent de iterații: {{count}}', + error_one: '{{count}} Eroare', + error_other: '{{count}} Erori', + input: 'Intrare', + errorResponseMethod: 'Metoda de Răspuns la Erori', + deleteTitle: 'Șterge nodul de ciclu?', + breakConditionTip: 'Numai variabilele din interiorul buclelor cu condiții de terminare și variabilele de conversație pot fi referite.', + loop_one: '{{count}} buclă', + loop_other: '{{count}} Buclă', + loopNode: 'Nod de buclă', + loopMaxCount: 'Numărul maxim de iterații', + loopVariables: 'Variabile de buclă', + finalLoopVariables: 'Variabilele ciclului final', + currentLoop: 'Circuit Curent', + totalLoopCount: 'Numărul total de bucle: {{count}}', + output: 'Variabilă de ieșire', + exitConditionTip: 'Un nod de buclă are nevoie de cel puțin o condiție de ieșire.', + initialLoopVariables: 'Variabilele de loop inițiale', + setLoopVariables: 'Setați variabilele în cadrul buclei', + loopMaxCountError: 'Vă rugăm să introduceți un număr maxim valid de bucle, care să fie între 1 și {{maxCount}}', + deleteDesc: 'Ștergerea nodului buclă va elimina toate nodurile copil.', + breakCondition: 'Condiția de terminare a buclei', + comma: ',', + variableName: 'Nume Variabil', + }, }, tracing: { stopBy: 'Oprit de {{user}}', @@ -777,6 +884,38 @@ const translation = { assignedVarsDescription: 'Variabilele atribuite trebuie să fie variabile inscripționabile, cum ar fi', noAssignedVars: 'Nu există variabile atribuite disponibile', }, + versionHistory: { + filter: { + all: 'Toate', + onlyYours: 'Numai al tău', + reset: 'Resetare filtrare', + onlyShowNamedVersions: 'Afișați doar versiunile numite', + empty: 'Nu s-a găsit nicio istorie de versiune corespunzătoare.', + }, + editField: { + releaseNotesLengthLimit: 'Notele de eliberare nu pot depăși {{limit}} caractere', + title: 'Titlu', + titleLengthLimit: 'Titlul nu poate depăși {{limit}} caractere', + releaseNotes: 'Note de lansare', + }, + action: { + restoreSuccess: 'Versiune restaurată', + deleteSuccess: 'Versiune ștearsă', + restoreFailure: 'Restaurarea versiunii a eșuat', + deleteFailure: 'Ștergerea versiunii a eșuat', + updateSuccess: 'Versiune actualizată', + updateFailure: 'Actualizarea versiunii a eșuat', + }, + latest: 'Cea mai recentă', + title: 'Versiuni', + nameThisVersion: 'Numește această versiune', + restorationTip: 'După restaurarea versiunii, proiectul actual va fi suprascris.', + defaultName: 'Versiune fără titlu', + editVersionInfo: 'Editează informațiile versiunii', + releaseNotesPlaceholder: 'Descrie ce s-a schimbat', + deletionTip: 'Ștergerea este irreversibilă, vă rugăm să confirmați.', + currentDraft: 'Draftul curent', + }, } export default translation diff --git a/web/i18n/ru-RU/app-debug.ts b/web/i18n/ru-RU/app-debug.ts index 038165301e..00cd6e8a75 100644 --- a/web/i18n/ru-RU/app-debug.ts +++ b/web/i18n/ru-RU/app-debug.ts @@ -322,6 +322,7 @@ const translation = { 'inputPlaceholder': 'Пожалуйста, введите', 'content': 'Содержимое', 'required': 'Обязательно', + 'hide': 'Скрыть', 'errorMsg': { labelNameRequired: 'Имя метки обязательно', varNameCanBeRepeat: 'Имя переменной не может повторяться', diff --git a/web/i18n/ru-RU/app-overview.ts b/web/i18n/ru-RU/app-overview.ts index 5816c37c40..ae7ec32f5a 100644 --- a/web/i18n/ru-RU/app-overview.ts +++ b/web/i18n/ru-RU/app-overview.ts @@ -58,9 +58,9 @@ const translation = { invalidPrivacyPolicy: 'Недопустимая ссылка на политику конфиденциальности. Пожалуйста, используйте действительную ссылку, начинающуюся с http или https', sso: { label: 'SSO аутентификация', - title: 'WebApp SSO', - description: 'Все пользователи должны войти в систему с помощью SSO перед использованием WebApp', - tooltip: 'Обратитесь к администратору, чтобы включить WebApp SSO', + title: 'web app SSO', + description: 'Все пользователи должны войти в систему с помощью SSO перед использованием web app', + tooltip: 'Обратитесь к администратору, чтобы включить web app SSO', }, more: { entry: 'Показать больше настроек', diff --git a/web/i18n/ru-RU/app.ts b/web/i18n/ru-RU/app.ts index a5b543c867..428d4c4e57 100644 --- a/web/i18n/ru-RU/app.ts +++ b/web/i18n/ru-RU/app.ts @@ -81,13 +81,13 @@ const translation = { foundResults: '{{Количество}} Результаты', optional: 'Необязательный', chatbotShortDescription: 'Чат-бот на основе LLM с простой настройкой', - advancedShortDescription: 'Рабочий процесс для сложных диалогов с несколькими ходами с памятью', + advancedShortDescription: 'Рабочий процесс, улучшенный для многоходовых чатов', foundResult: '{{Количество}} Результат', - workflowShortDescription: 'Оркестровка для задач автоматизации за один оборот', - advancedUserDescription: 'Оркестрация рабочих процессов для многораундовых сложных диалоговых задач с возможностями памяти.', + workflowShortDescription: 'Агентный поток для интеллектуальных автоматизаций', + advancedUserDescription: 'Рабочий процесс с дополнительными функциями памяти и интерфейсом чат-бота.', noAppsFound: 'Приложения не найдены', agentUserDescription: 'Интеллектуальный агент, способный к итеративным рассуждениям и автономному использованию инструментов для достижения целей задачи.', - forBeginners: 'ДЛЯ НАЧИНАЮЩИХ', + forBeginners: 'Более простые типы приложений', chatbotUserDescription: 'Быстро создайте чат-бота на основе LLM с простой настройкой. Вы можете переключиться на Chatflow позже.', noTemplateFound: 'Шаблоны не найдены', completionShortDescription: 'AI-помощник для задач генерации текста', @@ -96,7 +96,7 @@ const translation = { agentShortDescription: 'Интеллектуальный агент с рассуждениями и автономным использованием инструментов', noTemplateFoundTip: 'Попробуйте искать по разным ключевым словам.', completionUserDescription: 'Быстро создайте помощника с искусственным интеллектом для задач генерации текста с простой настройкой.', - workflowUserDescription: 'Оркестрация рабочих процессов для однораундовых задач, таких как автоматизация и пакетная обработка.', + workflowUserDescription: 'Визуально создавайте автономные ИИ-процессы простым перетаскиванием.', }, editApp: 'Редактировать информацию', editAppTitle: 'Редактировать информацию о приложении', @@ -163,11 +163,15 @@ const translation = { title: 'Опик', description: 'Opik — это платформа с открытым исходным кодом для оценки, тестирования и мониторинга LLM-приложений.', }, + weave: { + description: 'Weave — это открытая платформа для оценки, тестирования и мониторинга приложений LLM.', + title: 'Ткать', + }, }, answerIcon: { - title: 'Использование значка WebApp для замены 🤖', - description: 'Следует ли использовать значок WebApp для замены 🤖 в общем приложении', - descriptionInExplore: 'Следует ли использовать значок WebApp для замены 🤖 в разделе "Обзор"', + title: 'Использование значка web app для замены 🤖', + description: 'Следует ли использовать значок web app для замены 🤖 в общем приложении', + descriptionInExplore: 'Следует ли использовать значок web app для замены 🤖 в разделе "Обзор"', }, mermaid: { handDrawn: 'Рисованный', @@ -194,6 +198,54 @@ const translation = { placeholder: 'Выберите приложение...', params: 'ПАРАМЕТРЫ ПРИЛОЖЕНИЯ', }, + structOutput: { + notConfiguredTip: 'Структурированный вывод еще не был настроен.', + LLMResponse: 'Ответ LLM', + structured: 'Структурированный', + moreFillTip: 'Показано максимум 10 уровней вложенности', + required: 'Необходимо', + configure: 'Настроить', + modelNotSupported: 'Модель не поддерживается', + modelNotSupportedTip: 'Текущая модель не поддерживает эту функцию и автоматически понижается до инъекции подсказок.', + structuredTip: 'Структурированные выходные данные — это функция, которая гарантирует, что модель всегда будет генерировать ответы, соответствующие вашей предоставленной JSON-схеме.', + }, + accessItemsDescription: { + anyone: 'Любой может получить доступ к веб-приложению', + specific: 'Только определенные группы или участники могут получить доступ к веб-приложению.', + organization: 'Любой в организации может получить доступ к веб-приложению', + external: 'Только аутентифицированные внешние пользователи могут получить доступ к веб-приложению.', + }, + accessControlDialog: { + accessItems: { + anyone: 'Кто угодно с ссылкой', + specific: 'Конкретные группы или члены', + organization: 'Только члены внутри предприятия', + external: 'Аутентифицированные внешние пользователи', + }, + operateGroupAndMember: { + searchPlaceholder: 'Искать группы и участников', + expand: 'Расширить', + noResult: 'Нет результата', + allMembers: 'Все члены', + }, + title: 'Управление доступом к веб-приложению', + description: 'Установите разрешения на доступ к веб-приложению', + accessLabel: 'Кто имеет доступ', + groups_one: '{{count}} ГРУППА', + groups_other: '{{count}} ГРУПП', + members_one: '{{count}} УЧАСТНИК', + members_other: '{{count}} УЧАСТНИКИ', + noGroupsOrMembers: 'Группы или участники не выбраны', + updateSuccess: 'Обновление прошло успешно', + webAppSSONotEnabledTip: 'Пожалуйста, свяжитесь с администратором предприятия, чтобы настроить метод аутентификации веб-приложения.', + }, + publishApp: { + title: 'Кто может получить доступ к веб-приложению', + notSet: 'Не установлено', + notSetDesc: 'В настоящее время никто не может получить доступ к веб-приложению. Пожалуйста, установите права доступа.', + }, + accessControl: 'Управление доступом к веб-приложению', + noAccessPermission: 'Нет разрешения на доступ к веб-приложению', } export default translation diff --git a/web/i18n/ru-RU/billing.ts b/web/i18n/ru-RU/billing.ts index e7760d9ac6..7a0560334c 100644 --- a/web/i18n/ru-RU/billing.ts +++ b/web/i18n/ru-RU/billing.ts @@ -70,6 +70,7 @@ const translation = { messageRequest: { title: 'Кредиты на сообщения', tooltip: 'Квоты вызова сообщений для различных тарифных планов, использующих модели OpenAI (кроме gpt4). Сообщения, превышающие лимит, будут использовать ваш ключ API OpenAI.', + titlePerMonth: '{{count,number}} сообщений/месяц', }, annotatedResponse: { title: 'Ограничения квоты аннотаций', @@ -77,27 +78,94 @@ const translation = { }, ragAPIRequestTooltip: 'Относится к количеству вызовов API, вызывающих только возможности обработки базы знаний Dify.', receiptInfo: 'Только владелец команды и администратор команды могут подписываться и просматривать информацию о выставлении счетов', + cloud: 'Облачный сервис', + annualBilling: 'Ежегодная оплата', + apiRateLimit: 'Ограничение скорости API', + self: 'Самостоятельно размещенный', + teamMember_other: '{{count,number}} Члены команды', + apiRateLimitUnit: '{{count,number}}/день', + unlimitedApiRate: 'Нет ограничений на количество запросов к API', + freeTrialTip: 'бесплатная пробная версия из 200 вызовов OpenAI.', + freeTrialTipSuffix: 'Кредитная карта не требуется', + teamMember_one: '{{count,number}} Член команды', + getStarted: 'Начать', + teamWorkspace: '{{count,number}} Командное рабочее пространство', + freeTrialTipPrefix: 'Зарегистрируйтесь и получите', + comparePlanAndFeatures: 'Сравните планы и функции', + documents: '{{count,number}} Документов знаний', + documentsRequestQuota: '{{count,number}}/мин Лимит Частоты Запросов на Знание', + apiRateLimitTooltip: 'Ограничение скорости API применяется ко всем запросам, сделанным через API Dify, включая генерацию текста, чатовую переписку, выполнение рабочих процессов и обработку документов.', + documentsRequestQuotaTooltip: 'Указывает общее количество действий, которые рабочая область может выполнять в минуту внутри базы знаний, включая создание, удаление, обновление наборов данных, загрузку документов, модификации, архивирование и запросы к базе знаний. Эта метрика используется для оценки производительности запросов к базе знаний. Например, если пользователь Sandbox выполняет 10 последовательных тестов за один минуту, его рабочая область будет временно ограничена в выполнении следующих действий в течение следующей минуты: создание, удаление, обновление наборов данных и загрузка или модификация документов.', + priceTip: 'по рабочему месту/', + documentsTooltip: 'Квота на количество документов, импортируемых из источника знаний.', }, plans: { sandbox: { name: 'Песочница', description: '200 бесплатных пробных использований GPT', includesTitle: 'Включает:', + for: 'Бесплатная пробная версия основных возможностей', }, professional: { name: 'Профессиональный', description: 'Для частных лиц и небольших команд, чтобы разблокировать больше возможностей по доступной цене.', includesTitle: 'Все в бесплатном плане, плюс:', + for: 'Для независимых разработчиков/малых команд', }, team: { name: 'Команда', description: 'Сотрудничайте без ограничений и наслаждайтесь высочайшей производительностью.', includesTitle: 'Все в профессиональном плане, плюс:', + for: 'Для команд среднего размера', }, enterprise: { name: 'Корпоративный', description: 'Получите полный набор возможностей и поддержку для крупномасштабных критически важных систем.', includesTitle: 'Все в командном плане, плюс:', + features: { + 7: 'Обновления и обслуживание от Dify официально', + 4: 'ССО', + 8: 'Профессиональная техническая поддержка', + 6: 'Современная безопасность и контроль', + 2: 'Эксклюзивные функции для предприятий', + 1: 'Коммерческая лицензия', + 3: 'Множественные рабочие области и управление предприятием', + 0: 'Решения для масштабируемого развертывания корпоративного уровня', + 5: 'Согласованные Соглашения об Уровне Услуг от Dify Partners', + }, + price: 'Пользовательский', + priceTip: 'Только годовая подписка', + for: 'Для команд большого размера', + btnText: 'Связаться с отделом продаж', + }, + community: { + features: { + 0: 'Все основные функции выпущены в публичном репозитории', + 1: 'Единое рабочее пространство', + 2: 'Соблюдает Лицензию на открытое программное обеспечение Dify', + }, + name: 'Сообщество', + btnText: 'Начните с сообщества', + price: 'Свободно', + includesTitle: 'Бесплатные функции:', + description: 'Для отдельных пользователей, малых команд или некоммерческих проектов', + for: 'Для отдельных пользователей, малых команд или некоммерческих проектов', + }, + premium: { + features: { + 3: 'Приоритетная поддержка по электронной почте и чату', + 1: 'Единое рабочее пространство', + 2: 'Настройка логотипа и брендинга веб-приложения', + 0: 'Самостоятельное управление надежностью различными облачными провайдерами', + }, + description: 'Для средних организаций и команд', + includesTitle: 'Всё из Сообщества, плюс:', + priceTip: 'На основе облачного маркетплейса', + btnText: 'Получите Премиум в', + comingSoon: 'Поддержка Microsoft Azure и Google Cloud скоро появится', + price: 'Масштабируемый', + for: 'Для средних организаций и команд', + name: 'Премиум', }, }, vectorSpace: { @@ -107,12 +175,26 @@ const translation = { apps: { fullTipLine1: 'Обновите свой тарифный план, чтобы', fullTipLine2: 'создавать больше приложений.', + fullTip2des: 'Рекомендуется удалить неактивные приложения, чтобы освободить место, или свяжитесь с нами.', + fullTip2: 'Достигнут лимит плана', + contactUs: 'Свяжитесь с нами', + fullTip1des: 'Вы достигли предела создания приложений по этому плану', + fullTip1: 'Обновите, чтобы создать больше приложений', }, annotatedResponse: { fullTipLine1: 'Обновите свой тарифный план, чтобы', fullTipLine2: 'аннотировать больше разговоров.', quotaTitle: 'Квота ответов аннотаций', }, + usagePage: { + buildApps: 'Создавайте приложения', + teamMembers: 'Члены команды', + vectorSpaceTooltip: 'Документы с режимом индексирования высокого качества будут потреблять ресурсы Хранилища Знаний. Когда Хранилище Знаний достигнет предела, новые документы не будут загружены.', + annotationQuota: 'Квота аннотации', + vectorSpace: 'Хранилище данных знаний', + documentsUploadQuota: 'Квота на загрузку документов', + }, + teamMembers: 'Члены команды', } export default translation diff --git a/web/i18n/ru-RU/common.ts b/web/i18n/ru-RU/common.ts index d419bcc97e..37d207d357 100644 --- a/web/i18n/ru-RU/common.ts +++ b/web/i18n/ru-RU/common.ts @@ -54,6 +54,10 @@ const translation = { copied: 'Скопированы', in: 'в', viewDetails: 'Подробнее', + format: 'Формат', + more: 'Больше', + downloadFailed: 'Скачивание не удалось. Пожалуйста, попробуйте еще раз позже.', + downloadSuccess: 'Загрузка завершена.', }, errorMsg: { fieldRequired: '{{field}} обязательно', @@ -145,6 +149,8 @@ const translation = { newDataset: 'Создать знания', tools: 'Инструменты', exploreMarketplace: 'Подробнее о Marketplace', + appDetail: 'Детали приложения', + account: 'Учетная запись', }, userProfile: { settings: 'Настройки', @@ -157,6 +163,9 @@ const translation = { community: 'Сообщество', about: 'О нас', logout: 'Выйти', + github: 'ГитХаб', + compliance: 'Соблюдение', + support: 'Поддержка', }, settings: { accountGroup: 'АККАУНТ', @@ -206,6 +215,9 @@ const translation = { deleteLabel: 'Для подтверждения, пожалуйста, введите свой адрес электронной почты ниже', deleteSuccessTip: 'Вашему аккаунту требуется время, чтобы завершить удаление. Мы свяжемся с вами по электронной почте, когда все будет готово.', deletePrivacyLinkTip: 'Для получения дополнительной информации о том, как мы обрабатываем ваши данные, ознакомьтесь с нашим', + workspaceIcon: 'Иконка рабочего пространства', + workspaceName: 'Название рабочего пространства', + editWorkspaceInfo: 'Редактировать информацию о рабочем пространстве', }, members: { team: 'Команда', @@ -459,7 +471,7 @@ const translation = { apiBasedExtension: { title: 'API-расширения обеспечивают централизованное управление API, упрощая настройку для удобного использования в приложениях Dify.', link: 'Узнайте, как разработать собственное API-расширение.', - linkUrl: 'https://docs.dify.ai/features/extension/api_based_extension', + linkUrl: 'https://docs.dify.ai/en/guides/extension/api-based-extension/README', add: 'Добавить API Extension', selector: { title: 'API Extension', @@ -547,6 +559,7 @@ const translation = { inputPlaceholder: 'Поговорить с ботом', thinking: 'Мыслящий...', thought: 'Мысль', + resend: 'Переслать', }, promptEditor: { placeholder: 'Напишите здесь свое ключевое слово подсказки, введите \'{\', чтобы вставить переменную, введите \'/\', чтобы вставить блок содержимого подсказки', @@ -633,10 +646,31 @@ const translation = { license: { expiring: 'Срок действия истекает за один день', expiring_plural: 'Срок действия истекает через {{count}} дней', + unlimited: 'Неограниченный', }, pagination: { perPage: 'Элементов на странице', }, + theme: { + light: 'свет', + dark: 'темный', + theme: 'Тема', + auto: 'система', + }, + compliance: { + soc2Type2: 'Отчет SOC 2 Тип II', + gdpr: 'GDPR DPA', + professionalUpgradeTooltip: 'Доступно только с командным планом или выше.', + iso27001: 'Сертификация ISO 27001:2022', + sandboxUpgradeTooltip: 'Доступно только с профессиональным или командным планом.', + soc2Type1: 'Отчет SOC 2 Тип I', + }, + imageInput: { + browse: 'просмотр', + dropImageHere: 'Перетащите ваше изображение сюда или', + supportedFormats: 'Поддерживает PNG, JPG, JPEG, WEBP и GIF', + }, + you: 'Ты', } export default translation diff --git a/web/i18n/ru-RU/custom.ts b/web/i18n/ru-RU/custom.ts index 8725c83577..bba1d28c1a 100644 --- a/web/i18n/ru-RU/custom.ts +++ b/web/i18n/ru-RU/custom.ts @@ -3,6 +3,8 @@ const translation = { upgradeTip: { prefix: 'Обновите свой тарифный план, чтобы', suffix: 'настроить свой бренд.', + des: 'Обновите свой план, чтобы настроить свой бренд', + title: 'Обновите свой план', }, webapp: { title: 'Настроить бренд веб-приложения', diff --git a/web/i18n/ru-RU/dataset-creation.ts b/web/i18n/ru-RU/dataset-creation.ts index 3bf367689c..7e44306512 100644 --- a/web/i18n/ru-RU/dataset-creation.ts +++ b/web/i18n/ru-RU/dataset-creation.ts @@ -27,7 +27,7 @@ const translation = { }, uploader: { title: 'Загрузить файл', - button: 'Перетащите файл или', + button: 'Перетащите файлы или папки или', browse: 'Обзор', tip: 'Поддерживаются {{supportTypes}}. Максимум {{size}} МБ каждый.', validation: { @@ -63,7 +63,7 @@ const translation = { run: 'Запустить', firecrawlTitle: 'Извлечь веб-контент с помощью 🔥Firecrawl', firecrawlDoc: 'Документация Firecrawl', - firecrawlDocLink: 'https://docs.dify.ai/guides/knowledge-base/sync-from-website', + firecrawlDocLink: 'https://docs.dify.ai/en/guides/knowledge-base/create-knowledge-and-upload-documents/import-content-data/sync-from-website', options: 'Опции', crawlSubPage: 'Сканировать подстраницы', limit: 'Лимит', @@ -87,6 +87,14 @@ const translation = { jinaReaderDoc: 'Узнайте больше о Jina Reader', jinaReaderTitle: 'Конвертируйте весь сайт в Markdown', useSitemapTooltip: 'Следуйте карте сайта, чтобы просканировать сайт. Если нет, Jina Reader будет сканировать итеративно в зависимости от релевантности страницы, выдавая меньшее количество страниц, но более высокого качества.', + watercrawlTitle: 'Извлечение веб-контента с помощью Watercrawl', + watercrawlDocLink: 'https://docs.dify.ai/ru/guides/knowledge-base/create-knowledge-and-upload-documents/import-content-data/sync-from-website', + configureWatercrawl: 'Настроить Watercrawl', + waterCrawlNotConfigured: 'Watercrawl не настроен', + configureFirecrawl: 'Настроить Firecrawl', + waterCrawlNotConfiguredDescription: 'Настройте Watercrawl с помощью ключа API для его использования.', + configureJinaReader: 'Настройте Jina Reader', + watercrawlDoc: 'Документация Watercrawl', }, cancel: 'Отмена', }, @@ -200,6 +208,11 @@ const translation = { title: 'Подключаться к другим источникам данных?', description: 'В настоящее время база знаний Dify имеет лишь ограниченные источники данных. Добавление источника данных в базу знаний Dify — это отличный способ повысить гибкость и возможности платформы для всех пользователей. Наше руководство по вкладу поможет вам легко начать работу. Пожалуйста, нажмите на ссылку ниже, чтобы узнать больше.', }, + watercrawl: { + getApiKeyLinkText: 'Получите свой API-ключ с watercrawl.dev', + configWatercrawl: 'Настроить Watercrawl', + apiKeyPlaceholder: 'API ключ с watercrawl.dev', + }, } export default translation diff --git a/web/i18n/ru-RU/dataset-settings.ts b/web/i18n/ru-RU/dataset-settings.ts index b3b8347dd2..b91a59e50d 100644 --- a/web/i18n/ru-RU/dataset-settings.ts +++ b/web/i18n/ru-RU/dataset-settings.ts @@ -27,6 +27,7 @@ const translation = { learnMore: 'Узнать больше', description: ' о методе поиска.', longDescription: ' о методе поиска, вы можете изменить это в любое время в настройках базы знаний.', + method: 'Метод извлечения', }, save: 'Сохранить', externalKnowledgeAPI: 'API внешних знаний', diff --git a/web/i18n/ru-RU/dataset.ts b/web/i18n/ru-RU/dataset.ts index 41da4333f4..33a4cdf851 100644 --- a/web/i18n/ru-RU/dataset.ts +++ b/web/i18n/ru-RU/dataset.ts @@ -168,6 +168,54 @@ const translation = { enable: 'Давать возможность', allKnowledge: 'Все знания', allKnowledgeDescription: 'Выберите, чтобы отобразить все знания в этой рабочей области. Только владелец рабочего пространства может управлять всеми знаниями.', + metadata: { + createMetadata: { + type: 'Тип', + namePlaceholder: 'Добавьте имя метаданных', + back: 'назад', + name: 'Имя', + title: 'Новые метаданные', + }, + checkName: { + empty: 'Имя метаданных не может быть пустым', + invalid: 'Имя метаданных может содержать только строчные буквы, цифры и знаки нижнего подчеркивания и должно начинаться со строчной буквы.', + }, + batchEditMetadata: { + applyToAllSelectDocumentTip: 'Автоматически создайте все вышеуказанные редактируемые и новые метаданные для всех выбранных документов, иначе редактирование метаданных будет применяться только к документам с ними.', + applyToAllSelectDocument: 'Применить ко всем выбранным документам', + editDocumentsNum: 'Редактирование {{num}} документов', + multipleValue: 'Множественное значение', + editMetadata: 'Редактировать метаданные', + }, + selectMetadata: { + manageAction: 'Управлять', + newAction: 'Новые метаданные', + search: 'Поиск метаданных', + }, + datasetMetadata: { + deleteContent: 'Вы уверены, что хотите удалить метаданные "{{name}}"?', + values: '{{num}} Значений', + builtIn: 'Встроенный', + description: 'Вы можете управлять всеми метаданными в этих знаниях здесь. Изменения будут синхронизированы с каждым документом.', + deleteTitle: 'Подтвердите удаление', + builtInDescription: 'Встроенные метаданные автоматически извлекаются и генерируются. Их необходимо активировать перед использованием, и они не подлежат редактированию.', + addMetaData: 'Добавить метаданные', + rename: 'Переименовать', + disabled: 'Отключено', + name: 'Имя', + namePlaceholder: 'Имя метаданных', + }, + documentMetadata: { + startLabeling: 'Начать маркировку', + documentInformation: 'Информация о документе', + metadataToolTip: 'Метаданные служат важным фильтром, который повышает точность и актуальность извлечения информации. Вы можете изменить и добавить метаданные для этого документа здесь.', + technicalParameters: 'Технические параметры', + }, + chooseTime: 'Выберите время...', + metadata: 'Метаданные', + addMetadata: 'Добавить метаданные', + }, + embeddingModelNotAvailable: 'Модель встраивания недоступна.', } export default translation diff --git a/web/i18n/ru-RU/education.ts b/web/i18n/ru-RU/education.ts new file mode 100644 index 0000000000..b614f04824 --- /dev/null +++ b/web/i18n/ru-RU/education.ts @@ -0,0 +1,47 @@ +const translation = { + toVerifiedTip: { + end: 'для профессионального плана Dify.', + front: 'Теперь вы имеете право на статус "Проверенное образование". Пожалуйста, введите свои образовательные данные ниже, чтобы завершить процесс и получить', + coupon: 'эксклюзивный 100% купон', + }, + form: { + schoolName: { + title: 'Название вашей школы', + placeholder: 'Введите официальное, полное название вашей школы', + }, + schoolRole: { + option: { + student: 'Студент', + teacher: 'Учитель', + administrator: 'Школьный администратор', + }, + title: 'Ваша школьная роль', + }, + terms: { + desc: { + termsOfService: 'Условия обслуживания', + front: 'Ваша информация и использование статуса Проверенное образование подлежат нашим', + privacyPolicy: 'Политика конфиденциальности', + and: 'и', + end: '. Отправляя:', + }, + option: { + age: 'Я подтверждаю, что мне не меньше 18 лет', + inSchool: 'Я подтверждаю, что я зачислен или работаю в указанной учреждении. Dify может запросить подтверждение зачисления/трудоустройства. Если я неправильно укажу свою правообладанность, я согласен оплатить любые сборы, которые изначально были отменены на основании моего образовательного статуса.', + }, + title: 'Условия и соглашения', + }, + }, + submit: 'Отправить', + rejectTitle: 'Ваша образовательная проверка Dify была отклонена', + currentSigned: 'В ДАННЫЙ МОМЕНТ ВХОД В ПРОФИЛЬ КАК', + toVerified: 'Получите подтверждение образования', + learn: 'Узнайте, как получить подтверждение образования', + submitError: 'Отправка формы не удалась. Пожалуйста, попробуйте позже.', + successTitle: 'Вы получили подтвержденное образование Dify', + emailLabel: 'Ваш текущий адрес электронной почты', + rejectContent: 'К сожалению, вы не имеете права на статус Проверенного образованием и, следовательно, не можете получить эксклюзивный купон на 100% для профессионального плана Dify, если вы используете этот адрес электронной почты.', + successContent: 'Мы выдали купон на 100% скидку на план Dify Professional для вашего аккаунта. Купон действителен в течение одного года, пожалуйста, используйте его в течение срока действия.', +} + +export default translation diff --git a/web/i18n/ru-RU/explore.ts b/web/i18n/ru-RU/explore.ts index 97fad96244..919d1e49d8 100644 --- a/web/i18n/ru-RU/explore.ts +++ b/web/i18n/ru-RU/explore.ts @@ -37,6 +37,7 @@ const translation = { HR: 'HR', Agent: 'Агент', Workflow: 'Рабочий процесс', + Entertainment: 'Развлечение', }, } diff --git a/web/i18n/ru-RU/login.ts b/web/i18n/ru-RU/login.ts index 5c46cb7ff9..9c623fe5b6 100644 --- a/web/i18n/ru-RU/login.ts +++ b/web/i18n/ru-RU/login.ts @@ -105,6 +105,11 @@ const translation = { licenseLost: 'Утеряна лицензия', licenseInactiveTip: 'Лицензия Dify Enterprise для рабочего пространства неактивна. Обратитесь к своему администратору, чтобы продолжить использование Dify.', licenseExpiredTip: 'Срок действия лицензии Dify Enterprise для рабочего пространства истек. Обратитесь к своему администратору, чтобы продолжить использование Dify.', + webapp: { + noLoginMethod: 'Метод аутентификации не настроен для веб-приложения', + noLoginMethodTip: 'Пожалуйста, свяжитесь с администратором системы, чтобы добавить метод аутентификации.', + disabled: 'Аутентификация веб-приложения отключена. Пожалуйста, свяжитесь с администратором системы, чтобы включить ее. Вы можете попробовать использовать приложение напрямую.', + }, } export default translation diff --git a/web/i18n/ru-RU/plugin.ts b/web/i18n/ru-RU/plugin.ts index 4fc042f5ec..239f589e51 100644 --- a/web/i18n/ru-RU/plugin.ts +++ b/web/i18n/ru-RU/plugin.ts @@ -62,6 +62,7 @@ const translation = { params: 'КОНФИГУРАЦИЯ РАССУЖДЕНИЙ', unsupportedContent2: 'Нажмите, чтобы переключить версию.', uninstalledLink: 'Управление в плагинах', + toolSetting: 'Настройки инструмента', }, configureTool: 'Инструмент настройки', endpointsTip: 'Этот плагин предоставляет определенные функциональные возможности через конечные точки, и вы можете настроить несколько наборов конечных точек для текущей рабочей области.', @@ -180,6 +181,8 @@ const translation = { viewMore: 'Подробнее', and: 'и', discover: 'Обнаруживать', + verifiedTip: 'Подтверждено Dify', + partnerTip: 'Подтверждено партнером Dify', }, task: { installing: 'Установка плагинов {{installingLength}}, 0 готово.', @@ -196,7 +199,6 @@ const translation = { searchTools: 'Инструменты поиска...', allCategories: 'Все категории', endpointsEnabled: '{{num}} наборы включенных конечных точек', - submitPlugin: 'Отправить плагин', installAction: 'Устанавливать', from: 'От', installFrom: 'УСТАНОВИТЬ С', @@ -204,6 +206,12 @@ const translation = { installPlugin: 'Установка плагина', searchPlugins: 'Плагины поиска', fromMarketplace: 'Из маркетплейса', + metadata: { + title: 'Плагины', + }, + difyVersionNotCompatible: 'Текущая версия Dify не совместима с этим плагином, пожалуйста, обновите до минимально необходимой версии: {{minimalDifyVersion}}', + requestAPlugin: 'Запросите плагин', + publishPlugins: 'Публикация плагинов', } export default translation diff --git a/web/i18n/ru-RU/share-app.ts b/web/i18n/ru-RU/share-app.ts index f0166b26f1..dafbe9d6b1 100644 --- a/web/i18n/ru-RU/share-app.ts +++ b/web/i18n/ru-RU/share-app.ts @@ -30,6 +30,12 @@ const translation = { }, tryToSolve: 'Попробуйте решить', temporarySystemIssue: 'Извините, временная проблема с системой.', + expand: 'Развернуть', + collapse: 'Свернуть', + viewChatSettings: 'Посмотреть настройки чата', + chatSettingsTitle: 'Новая настройка чата', + newChatTip: 'Уже в новом чате', + chatFormTip: 'Настройки чата не могут быть изменены после его начала.', }, generation: { tabs: { @@ -68,6 +74,11 @@ const translation = { moreThanMaxLengthLine: 'Строка {{rowIndex}}: значение {{varName}} не может превышать {{maxLength}} символов', atLeastOne: 'Пожалуйста, введите хотя бы одну строку в загруженный файл.', }, + execution: 'ИСПОЛНЕНИЕ', + executions: '{{num}} ВЫПОЛНЕНИЯ', + }, + login: { + backToHome: 'Назад на главную', }, } diff --git a/web/i18n/ru-RU/time.ts b/web/i18n/ru-RU/time.ts index e2410dd34b..be9e38f4db 100644 --- a/web/i18n/ru-RU/time.ts +++ b/web/i18n/ru-RU/time.ts @@ -1,3 +1,37 @@ -const translation = {} +const translation = { + daysInWeek: { + Mon: 'Мой', + Tue: 'Вторник', + Sat: 'Суббота', + Sun: 'Солнце', + Thu: 'Четверг', + Wed: 'Сряда', + Fri: 'Свободно', + }, + months: { + March: 'Март', + May: 'Май', + April: 'Апрель', + July: 'Июль', + January: 'Январь', + August: 'Август', + December: 'Декабрь', + February: 'Февраль', + September: 'Сентябрь', + October: 'Октябрь', + June: 'Июнь', + November: 'Ноябрь', + }, + operation: { + ok: 'Хорошо', + pickDate: 'Выберите дату', + now: 'Теперь', + cancel: 'Отмена', + }, + title: { + pickTime: 'Выберите время', + }, + defaultPlaceholder: 'Выберите время...', +} export default translation diff --git a/web/i18n/ru-RU/tools.ts b/web/i18n/ru-RU/tools.ts index 02cf639fdb..e1975ee538 100644 --- a/web/i18n/ru-RU/tools.ts +++ b/web/i18n/ru-RU/tools.ts @@ -15,7 +15,6 @@ const translation = { }, author: 'Автор', auth: { - unauthorized: 'Авторизовать', authorized: 'Авторизовано', setup: 'Настроить авторизацию для использования', setupModalTitle: 'Настроить авторизацию', diff --git a/web/i18n/ru-RU/workflow.ts b/web/i18n/ru-RU/workflow.ts index d22ae7bf4f..b626420462 100644 --- a/web/i18n/ru-RU/workflow.ts +++ b/web/i18n/ru-RU/workflow.ts @@ -38,8 +38,6 @@ const translation = { setVarValuePlaceholder: 'Установить значение переменной', needConnectTip: 'Этот шаг ни к чему не подключен', maxTreeDepth: 'Максимальный предел {{depth}} узлов на ветку', - needEndNode: 'Необходимо добавить блок "Конец"', - needAnswerNode: 'Необходимо добавить блок "Ответ"', workflowProcess: 'Процесс рабочего процесса', notRunning: 'Еще не запущено', previewPlaceholder: 'Введите текст в поле ниже, чтобы начать отладку чат-бота', @@ -58,7 +56,6 @@ const translation = { learnMore: 'Узнать больше', copy: 'Копировать', duplicate: 'Дублировать', - addBlock: 'Добавить блок', pasteHere: 'Вставить сюда', pointerMode: 'Режим указателя', handMode: 'Режим руки', @@ -106,6 +103,18 @@ const translation = { addFailureBranch: 'Добавить ветвь Fail', noHistory: 'Без истории', loadMore: 'Загрузите больше рабочих процессов', + noExist: 'Такой переменной не существует', + versionHistory: 'История версий', + exportPNG: 'Экспортировать как PNG', + exportImage: 'Экспортировать изображение', + exportJPEG: 'Экспортировать как JPEG', + referenceVar: 'Ссылочная переменная', + exitVersions: 'Выходные версии', + exportSVG: 'Экспортировать как SVG', + publishUpdate: 'Опубликовать обновление', + addBlock: 'Добавить узел', + needAnswerNode: 'В узел ответа необходимо добавить', + needEndNode: 'Узел конца должен быть добавлен', }, env: { envPanelTitle: 'Переменные среды', @@ -167,19 +176,19 @@ const translation = { stepForward_other: '{{count}} шагов вперед', sessionStart: 'Начало сеанса', currentState: 'Текущее состояние', - nodeTitleChange: 'Изменено название блока', - nodeDescriptionChange: 'Изменено описание блока', - nodeDragStop: 'Блок перемещен', - nodeChange: 'Блок изменен', - nodeConnect: 'Блок подключен', - nodePaste: 'Блок вставлен', - nodeDelete: 'Блок удален', - nodeAdd: 'Блок добавлен', - nodeResize: 'Размер блока изменен', noteAdd: 'Заметка добавлена', noteChange: 'Заметка изменена', noteDelete: 'Заметка удалена', - edgeDelete: 'Блок отключен', + nodeDragStop: 'Узел перемещен', + nodeResize: 'Узел изменен в размере', + nodeTitleChange: 'Название узла изменено', + edgeDelete: 'Узел отключен', + nodeConnect: 'Узел подключен', + nodeDelete: 'Узел удален', + nodePaste: 'Узел вставлен', + nodeChange: 'Узел изменен', + nodeAdd: 'Узел добавлен', + nodeDescriptionChange: 'Описание узла изменено', }, errorMsg: { fieldRequired: '{{field}} обязательно для заполнения', @@ -205,10 +214,9 @@ const translation = { testRunIteration: 'Итерация тестового запуска', back: 'Назад', iteration: 'Итерация', + loop: 'Цикл', }, tabs: { - 'searchBlock': 'Поиск блока', - 'blocks': 'Блоки', 'searchTool': 'Поиск инструмента', 'tools': 'Инструменты', 'allTool': 'Все', @@ -222,6 +230,8 @@ const translation = { 'noResult': 'Ничего не найдено', 'plugin': 'Плагин', 'agent': 'Агентская стратегия', + 'blocks': 'Узлы', + 'searchBlock': 'Поиск узла', }, blocks: { 'start': 'Начало', @@ -243,6 +253,9 @@ const translation = { 'document-extractor': 'Экстрактор документов', 'list-operator': 'Оператор списка', 'agent': 'Агент', + 'loop': 'Цикл', + 'loop-start': 'Начало цикла', + 'loop-end': 'Выйти из цикла', }, blocksAbout: { 'start': 'Определите начальные параметры для запуска рабочего процесса', @@ -263,6 +276,8 @@ const translation = { 'list-operator': 'Используется для фильтрации или сортировки содержимого массива.', 'document-extractor': 'Используется для разбора загруженных документов в текстовый контент, который легко воспринимается LLM.', 'agent': 'Вызов больших языковых моделей для ответа на вопросы или обработки естественного языка', + 'loop-end': 'Эквивалентно "break". Этот узел не имеет конфигурационных элементов. Когда тело цикла достигает этого узла, цикл завершается.', + 'loop': 'Выполните цикл логики до тех пор, пока не будет достигнуто условие завершения или максимальное количество итераций цикла.', }, operator: { zoomIn: 'Увеличить', @@ -273,20 +288,21 @@ const translation = { }, panel: { userInputField: 'Поле ввода пользователя', - changeBlock: 'Изменить блок', helpLink: 'Ссылка на справку', about: 'О программе', createdBy: 'Создано ', nextStep: 'Следующий шаг', - addNextStep: 'Добавить следующий блок в этот рабочий процесс', - selectNextStep: 'Выбрать следующий блок', runThisStep: 'Выполнить этот шаг', checklist: 'Контрольный список', checklistTip: 'Убедитесь, что все проблемы решены перед публикацией', checklistResolved: 'Все проблемы решены', - organizeBlocks: 'Организовать блоки', change: 'Изменить', optional: '(необязательно)', + moveToThisNode: 'Перейдите к этому узлу', + selectNextStep: 'Выберите следующий шаг', + organizeBlocks: 'Организовать узлы', + addNextStep: 'Добавьте следующий шаг в этот рабочий процесс', + changeBlock: 'Изменить узел', }, nodes: { common: { @@ -404,6 +420,34 @@ const translation = { variable: 'Переменная', }, sysQueryInUser: 'sys.query в сообщении пользователя обязателен', + jsonSchema: { + warningTips: { + saveSchema: 'Пожалуйста, завершите редактирование текущего поля перед сохранением схемы.', + }, + back: 'Спина', + resetDefaults: 'Сброс', + showAdvancedOptions: 'Показать расширенные параметры', + generatedResult: 'Сгенерированный результат', + generateJsonSchema: 'Сгенерировать JSON-схему', + import: 'Импорт из JSON', + stringValidations: 'Проверка строк', + promptPlaceholder: 'Опишите вашу JSON-схему...', + required: 'необходимо', + generate: 'Сгенерировать', + apply: 'Подать заявку', + addChildField: 'Добавить поле ребенка', + regenerate: 'Сгенерировать заново', + addField: 'Добавить поле', + instruction: 'Инструкция', + title: 'Структурированная схема вывода', + descriptionPlaceholder: 'Добавить описание', + fieldNamePlaceholder: 'Название поля', + doc: 'Узнайте больше о структурированном выводе', + resultTip: 'Вот сгенерированный результат. Если вы не удовлетворены, вы можете вернуться и изменить свой запрос.', + generationTip: 'Вы можете использовать естественный язык для быстрого создания схемы JSON.', + generating: 'Генерация схемы JSON...', + promptTooltip: 'Преобразуйте текстовое описание в стандартизированную структуру JSON Schema.', + }, }, knowledgeRetrieval: { queryVariable: 'Переменная запроса', @@ -416,6 +460,33 @@ const translation = { url: 'Сегментированный URL', metadata: 'Другие метаданные', }, + metadata: { + options: { + disabled: { + title: 'Отключено', + subTitle: 'Не включение фильтрации метаданных', + }, + automatic: { + desc: 'Автоматически генерировать условия фильтрации метаданных на основе переменной запроса', + title: 'Автоматический', + subTitle: 'Автоматически генерировать условия фильтрации метаданных на основе запроса пользователя', + }, + manual: { + title: 'Руководство', + subTitle: 'Вручную добавьте условия фильтрации метаданных', + }, + }, + panel: { + conditions: 'Условия', + placeholder: 'Введите значение', + datePlaceholder: 'Выберите время...', + select: 'Выберите переменную...', + add: 'Добавить условие', + title: 'Условия фильтрации метаданных', + search: 'Поиск метаданных', + }, + title: 'Фильтрация метаданных', + }, }, http: { inputVars: 'Входные переменные', @@ -505,6 +576,8 @@ const translation = { 'not exists': 'не существует', 'in': 'в', 'exists': 'Существует', + 'before': 'до', + 'after': 'после', }, enterValue: 'Введите значение', addCondition: 'Добавить условие', @@ -520,6 +593,7 @@ const translation = { }, select: 'Выбирать', addSubVariable: 'Подпеременная', + condition: 'Условие', }, variableAssigner: { title: 'Назначить переменные', @@ -562,6 +636,8 @@ const translation = { 'title': 'Операция', 'over-write': 'Перезаписать', 'append': 'Прибавлять', + 'remove-first': 'Удалить первый', + 'remove-last': 'Удалить последний', }, 'variables': 'Переменные', 'noAssignedVars': 'Нет доступных назначенных переменных', @@ -572,7 +648,6 @@ const translation = { 'selectAssignedVariable': 'Выберите назначенную переменную...', }, tool: { - toAuthorize: 'Авторизовать', inputVars: 'Входные переменные', outputVars: { text: 'контент, сгенерированный инструментом', @@ -585,6 +660,7 @@ const translation = { }, json: 'json, сгенерированный инструментом', }, + authorize: 'Авторизовать', }, questionClassifiers: { model: 'модель', @@ -766,6 +842,38 @@ const translation = { configureModel: 'Сконфигурировать модель', maxIterations: 'Максимальное количество итераций', }, + loop: { + ErrorMethod: { + operationTerminated: 'Прекращено', + continueOnError: 'Продолжать при ошибке', + removeAbnormalOutput: 'Устранить аномальный вывод', + }, + inputMode: 'Режим ввода', + exitConditionTip: 'У узла цикла должно быть как минимум одно условие выхода', + loopMaxCountError: 'Пожалуйста, введите допустимое максимальное количество циклов, от 1 до {{maxCount}}', + setLoopVariables: 'Устанавливайте переменные в области видимости цикла', + currentLoop: 'Текущий контур', + input: 'Ввод', + comma: ',', + loop_other: '{{count}} Циклов', + currentLoopCount: 'Текущее количество циклов: {{count}}', + loop_one: '{{count}} Цикл', + variableName: 'Имя переменной', + totalLoopCount: 'Общее количество циклов: {{count}}', + loopNode: 'Циклический узел', + errorResponseMethod: 'Метод ответа об ошибке', + deleteTitle: 'Удалить узел цикла?', + error_one: '{{count}} Ошибка', + output: 'Выходная переменная', + deleteDesc: 'Удаление узла цикла удалит все дочерние узлы.', + loopMaxCount: 'Максимальное количество итераций', + error_other: '{{count}} Ошибок', + breakConditionTip: 'Только переменные в циклах с условиями завершения и переменные беседы могут быть использованы.', + finalLoopVariables: 'Финальные переменные цикла', + initialLoopVariables: 'Начальные переменные цикла', + breakCondition: 'Условие завершения цикла', + loopVariables: 'Циклические переменные', + }, }, tracing: { stopBy: 'Остановлено {{user}}', @@ -777,6 +885,38 @@ const translation = { conversationVars: 'Переменные беседы', noAvailableVars: 'Нет доступных переменных', }, + versionHistory: { + filter: { + onlyShowNamedVersions: 'Показывать только именованные версии', + all: 'Все', + reset: 'Сбросить фильтр', + onlyYours: 'Только твой', + empty: 'История версий не найдена', + }, + editField: { + titleLengthLimit: 'Заголовок не может превышать {{limit}} символов', + releaseNotesLengthLimit: 'Примечания к версии не могут превышать {{limit}} символов', + releaseNotes: 'Новости релиза', + title: 'Заголовок', + }, + action: { + restoreSuccess: 'Версия восстановлена', + updateSuccess: 'Версия обновлена', + deleteFailure: 'Не удалось удалить версию', + deleteSuccess: 'Версия удалена', + updateFailure: 'Не удалось обновить версию', + restoreFailure: 'Не удалось восстановить версию', + }, + latest: 'Последний', + restorationTip: 'После восстановления версии текущий черновик будет перезаписан.', + deletionTip: 'Удаление необратимо, пожалуйста, подтвердите.', + nameThisVersion: 'Назовите эту версию', + editVersionInfo: 'Редактировать информацию о версии', + title: 'Версии', + currentDraft: 'Текущий проект', + releaseNotesPlaceholder: 'Опишите, что изменилось', + defaultName: 'Без названия версия', + }, } export default translation diff --git a/web/i18n/sl-SI/app-debug.ts b/web/i18n/sl-SI/app-debug.ts index 8672fa5c58..7e5c7dd6b0 100644 --- a/web/i18n/sl-SI/app-debug.ts +++ b/web/i18n/sl-SI/app-debug.ts @@ -239,4 +239,4 @@ const translation = { }, } -module.exports = translation +export default translation diff --git a/web/i18n/sl-SI/app.ts b/web/i18n/sl-SI/app.ts index e4ba29094b..4ac445872d 100644 --- a/web/i18n/sl-SI/app.ts +++ b/web/i18n/sl-SI/app.ts @@ -76,24 +76,24 @@ const translation = { appCreateDSLErrorPart4: 'Sistemsko podprta različica DSL:', appCreateDSLWarning: 'Pozor: Razlika v različici DSL lahko vpliva na nekatere funkcije', appCreateDSLErrorPart2: 'Želite nadaljevati?', - advancedShortDescription: 'Potek dela za zapletene dialoge z več obrati s pomnilnikom', + advancedShortDescription: 'Potek dela izboljšan za večkratne pogovore', noAppsFound: 'Ni bilo najdenih aplikacij', agentShortDescription: 'Inteligentni agent z razmišljanjem in avtonomno uporabo orodij', foundResult: '{{štetje}} Rezultat', foundResults: '{{štetje}} Rezultati', noTemplateFoundTip: 'Poskusite iskati z različnimi ključnimi besedami.', optional: 'Neobvezno', - forBeginners: 'ZA ZAČETNIKE', + forBeginners: 'Bolj osnovne vrste aplikacij', forAdvanced: 'ZA NAPREDNE UPORABNIKE', noIdeaTip: 'Nimate idej? Oglejte si naše predloge', agentUserDescription: 'Inteligentni agent, ki je sposoben iterativnega sklepanja in avtonomne uporabe orodij za doseganje ciljev nalog.', completionShortDescription: 'Pomočnik AI za naloge generiranja besedila', chatbotUserDescription: 'Hitro zgradite chatbota, ki temelji na LLM, s preprosto konfiguracijo. Na Chatflow lahko preklopite pozneje.', completionUserDescription: 'Hitro ustvarite pomočnika AI za naloge ustvarjanja besedila s preprosto konfiguracijo.', - advancedUserDescription: 'Orkestracija poteka dela za večkrožne zapletene dialogske naloge s pomnilniškimi zmogljivostmi.', - workflowUserDescription: 'Orkestracija poteka dela za enojna opravila, kot sta avtomatizacija in paketna obdelava.', + advancedUserDescription: 'Potek dela z dodatnimi funkcijami spomina in vmesnikom za klepetanje.', + workflowUserDescription: 'Vizualno ustvarjajte avtonomne AI poteke s preprostim vlečenjem in spuščanjem.', noTemplateFound: 'Predloge niso bile najdene', - workflowShortDescription: 'Orkestracija za opravila avtomatizacije z enim obratom', + workflowShortDescription: 'Agentni tok za inteligentne avtomatizacije', chatbotShortDescription: 'Chatbot, ki temelji na LLM, s preprosto nastavitvijo', chooseAppType: 'Izberite vrsto aplikacije', learnMore: 'Izvedi več', @@ -109,9 +109,9 @@ const translation = { image: 'Slika', }, answerIcon: { - title: 'Uporabite ikono WebApp za zamenjavo 🤖', - description: 'Ali uporabiti ikono WebApp za zamenjavo 🤖 v deljeni aplikaciji', - descriptionInExplore: 'Ali uporabiti ikono WebApp za zamenjavo 🤖 v razdelku Razišči', + title: 'Uporabite ikono web app za zamenjavo 🤖', + description: 'Ali uporabiti ikono web app za zamenjavo 🤖 v deljeni aplikaciji', + descriptionInExplore: 'Ali uporabiti ikono web app za zamenjavo 🤖 v razdelku Razišči', }, switch: 'Preklopi na Workflow Orchestrate', switchTipStart: 'Za vas bo ustvarjena nova kopija aplikacije, ki bo preklopila na Workflow Orchestrate. Nova kopija ne bo ', @@ -168,6 +168,10 @@ const translation = { description: 'Opik je odprtokodna platforma za ocenjevanje, testiranje in spremljanje aplikacij LLM.', title: 'Opik', }, + weave: { + title: 'Tkanje', + description: 'Weave je odprtokodna platforma za vrednotenje, testiranje in spremljanje aplikacij LLM.', + }, }, mermaid: { handDrawn: 'Ročno narisano', @@ -194,6 +198,54 @@ const translation = { label: 'APL', placeholder: 'Izberite aplikacijo ...', }, + structOutput: { + configure: 'Konfiguriraj', + structured: 'Strukturirano', + modelNotSupported: 'Model ni podprt', + required: 'Zahtevano', + moreFillTip: 'Prikazovanje največ 10 ravni gnezdenja', + LLMResponse: 'LLM odziv', + notConfiguredTip: 'Strukturiranega izhoda še ni mogoče konfigurirati', + modelNotSupportedTip: 'Trenutni model ne podpira te funkcije in se samodejno zniža na vbrizgavanje pozivov.', + structuredTip: 'Strukturirani izhodi so funkcija, ki zagotavlja, da bo model vedno generiral odgovore, ki se držijo vašega posredovanega JSON sheme.', + }, + accessItemsDescription: { + anyone: 'Vsakdo lahko dostopa do spletne aplikacije', + specific: 'Samo določenim skupinam ali članom je omogočen dostop do spletne aplikacije', + organization: 'Vsakdo v organizaciji lahko dostopa do spletne aplikacije', + external: 'Samo avtentificirani zunanji uporabniki lahko dostopajo do spletne aplikacije.', + }, + accessControlDialog: { + accessItems: { + anyone: 'Kdorkoli s povezavo', + specific: 'Specifične skupine ali člani', + organization: 'Samo člani znotraj podjetja', + external: 'Avtorizirani zunanji uporabniki', + }, + operateGroupAndMember: { + searchPlaceholder: 'Išči skupine in člane', + allMembers: 'Vsi člani', + expand: 'Razširi', + noResult: 'Brez rezultata', + }, + title: 'Nadzor dostopa do spletne aplikacije', + description: 'Nastavite dovoljenja za dostop do spletne aplikacije', + accessLabel: 'Kdo ima dostop', + groups_one: '{{count}} SKUPINA', + groups_other: '{{count}} SKUPIN', + members_one: '{{count}} ČLAN', + members_other: '{{count}} ČLANOV', + updateSuccess: 'Posodobitev uspešna', + noGroupsOrMembers: 'Nobene skupine ali članov ni izbranih', + webAppSSONotEnabledTip: 'Prosimo, da se obrnete na skrbnika podjetja, da konfigurira način avtentikacije spletne aplikacije.', + }, + publishApp: { + title: 'Kdo lahko dostopa do spletne aplikacije', + notSet: 'Ni nastavljeno', + notSetDesc: 'Trenutno nihče ne more dostopati do spletne aplikacije. Prosimo, nastavite dovoljenja.', + }, + accessControl: 'Nadzor dostopa do spletne aplikacije', + noAccessPermission: 'Brez dovoljenja za dostop do spletne aplikacije', } export default translation diff --git a/web/i18n/sl-SI/billing.ts b/web/i18n/sl-SI/billing.ts index edbf03ee9a..9b7987293f 100644 --- a/web/i18n/sl-SI/billing.ts +++ b/web/i18n/sl-SI/billing.ts @@ -70,6 +70,7 @@ const translation = { messageRequest: { title: 'Krediti za sporočila', tooltip: 'Kvota za klice sporočil pri različnih načrtih z uporabo modelov OpenAI (razen GPT-4). Sporočila preko omejitve bodo uporabljala vaš OpenAI API ključ.', + titlePerMonth: '{{count,number}} sporočil/mesec', }, annotatedResponse: { title: 'Omejitve kvote za označevanje', @@ -77,27 +78,94 @@ const translation = { }, ragAPIRequestTooltip: 'Nanaša se na število API klicev, ki vključujejo samo sposobnosti obdelave baze znanja Dify.', receiptInfo: 'Le lastnik ekipe in skrbnik ekipe lahko naročita in si ogledate podatke o plačilih', + self: 'Samostojno gostovanje', + documents: '{{count,number}} dokumentov znanja', + documentsTooltip: 'Kvote na število dokumentov, uvoženih iz Vir podatkov znanja.', + teamWorkspace: '{{count,number}} Ekipa delovni prostor', + apiRateLimit: 'Omejitev hitrosti API-ja', + unlimitedApiRate: 'Brez omejitve hitrostnega limita API-ja', + comparePlanAndFeatures: 'Primerjajte načrte in funkcije', + apiRateLimitTooltip: 'API omejitev hitrosti velja za vse poizvedbe, opravljene prek Dify API, vključno z generiranjem besedila, klepetnimi pogovori, izvajanjem delovnih tokov in obdelavo dokumentov.', + freeTrialTipSuffix: 'Brez zahteve po kreditni kartici', + annualBilling: 'Letno račунovodstvo', + teamMember_one: '{{count,number}} član ekipe', + teamMember_other: '{{count,number}} Članov ekipe', + documentsRequestQuota: '{{count,number}}/min Omejitev stopnje zahtev po znanju', + apiRateLimitUnit: '{{count,number}}/dan', + priceTip: 'na delovnem prostoru/', + freeTrialTipPrefix: 'Prijavite se in prejmite', + cloud: 'Oblačna storitev', + freeTrialTip: 'brezplačno preizkušnjo 200 klicev OpenAI.', + getStarted: 'Začnite', + documentsRequestQuotaTooltip: 'Določa skupno število dejanj, ki jih lahko delovno mesto opravi na minuto znotraj znanja baze, vključno s kreiranjem, brisanjem, posodobitvami, nalaganjem dokumentov, spremembami, arhiviranjem in poizvedbami po znanju bazi. Ta meritev se uporablja za ocenjevanje uspešnosti poizvedb v bazi znanja. Na primer, če uporabnik Sandbox izvede 10 zaporednih testov udarca v eni minuti, bo njegovo delovno mesto začasno omejeno pri izvajanju naslednjih dejanj v naslednji minuti: kreiranje podatkovnih nizov, brisanje, posodobitve in nalaganje ali spremembe dokumentov.', }, plans: { sandbox: { name: 'Peskovnik', description: '200 brezplačnih poskusov GPT', includesTitle: 'Vključuje:', + for: 'Brezplačno preizkušanje osnovnih zmogljivosti', }, professional: { name: 'Profesionalni', description: 'Za posameznike in male ekipe, da odklenete več zmogljivosti po ugodni ceni.', includesTitle: 'Vse v brezplačnem načrtu, plus:', + for: 'Za neodvisne razvijalce/male ekipe', }, team: { name: 'Ekipa', description: 'Sodelujte brez omejitev in uživajte v vrhunski zmogljivosti.', includesTitle: 'Vse v profesionalnem načrtu, plus:', + for: 'Za srednje velike ekipe', }, enterprise: { name: 'Podjetje', description: 'Pridobite vse zmogljivosti in podporo za velike sisteme kritične za misijo.', includesTitle: 'Vse v načrtu Ekipa, plus:', + features: { + 5: 'Pogajali smo se o SLAs s partnerji Dify', + 4: 'SSO', + 0: 'Rešitve za razširljivo uvedbo na ravni podjetij', + 1: 'Avtorizacija za komercialno licenco', + 2: 'Ekskluzivne funkcije za podjetja', + 7: 'Posodobitve in vzdrževanje s strani Dify uradno', + 3: 'Več delovnih prostorov in upravljanje podjetij', + 6: 'Napredna varnost in nadzor', + 8: 'Profesionalna tehnična podpora', + }, + priceTip: 'Letno zaračunavanje samo', + price: 'Po meri', + btnText: 'Kontaktirajte prodajo', + for: 'Za velike ekipe', + }, + community: { + features: { + 2: 'Upošteva Dify odprtokodno licenco', + 0: 'Vse ključne funkcije so bile objavljene v javnem repozitoriju', + 1: 'Enotno delovno okolje', + }, + includesTitle: 'Brezplačne funkcije:', + price: 'Brezplačno', + name: 'Skupnost', + description: 'Za posamezne uporabnike, majhne skupine ali nekomercialne projekte', + for: 'Za posamezne uporabnike, majhne skupine ali nekomercialne projekte', + btnText: 'Začnite s skupnostjo', + }, + premium: { + features: { + 2: 'Prilagoditev logotipa in blagovne znamke spletne aplikacije', + 1: 'Enotno delovno okolje', + 0: 'Samoobvladovana zanesljivost različnih ponudnikov oblačnih storitev', + 3: 'Prednostna e-pošta in podpora za klepet', + }, + name: 'Premium', + priceTip: 'Na podlagi oblaka Marketplace', + price: 'Škalable', + includesTitle: 'Vse iz skupnosti, poleg tega:', + comingSoon: 'Podpora za Microsoft Azure in Google Cloud kmalu prihaja', + for: 'Za srednje velika podjetja in ekipe', + btnText: 'Pridobi Premium v', + description: 'Za srednje velika podjetja in ekipe', }, }, vectorSpace: { @@ -107,12 +175,26 @@ const translation = { apps: { fullTipLine1: 'Nadgradite svoj načrt, da', fullTipLine2: 'gradite več aplikacij.', + fullTip1des: 'Dosegli ste omejitev za izdelavo aplikacij v tem načrtu.', + fullTip1: 'Nadgradite za ustvarjanje več aplikacij', + fullTip2: 'Dosežena meja načrta', + contactUs: 'Kontaktirajte nas', + fullTip2des: 'Priporočljivo je, da očistite neaktivne aplikacije, da sprostite prostor, ali nas kontaktirate.', }, annotatedResponse: { fullTipLine1: 'Nadgradite svoj načrt, da', fullTipLine2: 'označite več pogovorov.', quotaTitle: 'Kvote za odgovor z označevanjem', }, + usagePage: { + documentsUploadQuota: 'Kvota za nalaganje dokumentov', + vectorSpaceTooltip: 'Dokumenti z načinom indeksiranja visoke kakovosti bodo porabili vire skladišča podatkov o znanju. Ko skladišče podatkov o znanju doseže mejo, novih dokumentov ne bo mogoče naložiti.', + vectorSpace: 'Shranjevanje podatkov znanja', + annotationQuota: 'Quota za anotacijo', + teamMembers: 'Člani ekipe', + buildApps: 'Gradite aplikacije', + }, + teamMembers: 'Člani ekipe', } export default translation diff --git a/web/i18n/sl-SI/common.ts b/web/i18n/sl-SI/common.ts index 1167f33697..b43a2dbbb2 100644 --- a/web/i18n/sl-SI/common.ts +++ b/web/i18n/sl-SI/common.ts @@ -54,6 +54,10 @@ const translation = { viewDetails: 'Poglej podrobnosti', copied: 'Kopirati', in: 'v', + downloadFailed: 'Prenos ni uspel. Prosim, poskusite znova pozneje.', + more: 'Več', + downloadSuccess: 'Prenos končan.', + format: 'Format', }, errorMsg: { fieldRequired: '{{field}} je obvezno', @@ -145,6 +149,8 @@ const translation = { newDataset: 'Ustvari znanje', tools: 'Orodja', exploreMarketplace: 'Raziščite Marketplace', + appDetail: 'Podrobnosti o aplikaciji', + account: 'Račun', }, userProfile: { settings: 'Nastavitve', @@ -157,6 +163,9 @@ const translation = { community: 'Skupnost', about: 'O nas', logout: 'Odjava', + support: 'Podpora', + github: 'GitHub', + compliance: 'Skladnost', }, settings: { accountGroup: 'SPLOŠNO', @@ -206,6 +215,9 @@ const translation = { deleteSuccessTip: 'Vaš račun potrebuje čas, da dokonča brisanje. Ko bo vse končano, vam bomo poslali e-pošto.', feedbackTitle: 'Povratne informacije', deleteLabel: 'Za potrditev spodaj vnesite svoj e-poštni naslov', + workspaceName: 'Ime delovnega prostora', + workspaceIcon: 'Ikona delovnega prostora', + editWorkspaceInfo: 'Uredi informacije o delovnem prostoru', }, members: { team: 'Ekipa', @@ -452,7 +464,7 @@ const translation = { apiBasedExtension: { title: 'Razširitve API omogočajo centralizirano upravljanje API, kar poenostavi konfiguracijo za enostavno uporabo v aplikacijah Dify.', link: 'Naučite se, kako razviti svojo API razširitev.', - linkUrl: 'https://docs.dify.ai/features/extension/api_based_extension', + linkUrl: 'https://docs.dify.ai/en/guides/extension/api-based-extension/README', add: 'Dodaj API razširitev', selector: { title: 'API razširitev', @@ -681,7 +693,7 @@ const translation = { type: 'Vrsta', link: 'Preberite, kako razvijete lastno razširitev API-ja.', title: 'Razširitve API zagotavljajo centralizirano upravljanje API, kar poenostavlja konfiguracijo za enostavno uporabo v aplikacijah Dify.', - linkUrl: 'https://docs.dify.ai/features/extension/api_based_extension', + linkUrl: 'https://docs.dify.ai/en/guides/extension/api-based-extension/README', add: 'Dodajanje razširitve API-ja', }, about: { @@ -746,6 +758,7 @@ const translation = { conversationNamePlaceholder: 'Prosimo, vnesite ime pogovora', thinking: 'Razmišljanje...', thought: 'Misel', + resend: 'Ponovno pošlji', }, promptEditor: { context: { @@ -832,10 +845,31 @@ const translation = { license: { expiring_plural: 'Poteče v {{count}} dneh', expiring: 'Poteče v enem dnevu', + unlimited: 'Brez omejitev', }, pagination: { perPage: 'Elementi na stran', }, + theme: { + theme: 'Tema', + light: 'svetloba', + auto: 'sistem', + dark: 'temno', + }, + compliance: { + sandboxUpgradeTooltip: 'Na voljo je le z načrtom Professional ali Team.', + gdpr: 'GDPR DPA', + soc2Type2: 'Poročilo SOC 2 Tip II', + professionalUpgradeTooltip: 'Na voljo je le s Team načrtom ali višjim.', + iso27001: 'Certifikacija ISO 27001:2022', + soc2Type1: 'Poročilo SOC 2 Tip I', + }, + imageInput: { + supportedFormats: 'Podpira PNG, JPG, JPEG, WEBP in GIF', + browse: 'brskati', + dropImageHere: 'Tukaj spustite svojo sliko ali', + }, + you: 'Ti', } export default translation diff --git a/web/i18n/sl-SI/custom.ts b/web/i18n/sl-SI/custom.ts index 6c2f3f4f93..59f0a6baf9 100644 --- a/web/i18n/sl-SI/custom.ts +++ b/web/i18n/sl-SI/custom.ts @@ -3,6 +3,8 @@ const translation = { upgradeTip: { prefix: 'Nadgradite svoj načrt za', suffix: 'prilagoditev vaše blagovne znamke.', + des: 'Nadgradite svoj načrt, da prilagodite svojo blagovno znamko', + title: 'Nadgradite svoj načrt', }, webapp: { title: 'Prilagodi blagovno znamko spletne aplikacije', diff --git a/web/i18n/sl-SI/dataset-creation.ts b/web/i18n/sl-SI/dataset-creation.ts index 3bc1d86bc6..72e71049ac 100644 --- a/web/i18n/sl-SI/dataset-creation.ts +++ b/web/i18n/sl-SI/dataset-creation.ts @@ -32,7 +32,7 @@ const translation = { }, uploader: { title: 'Naloži datoteko', - button: 'Povleci in spusti datoteko ali', + button: 'Povleci in spusti datoteke ali mape oz', browse: 'Prebrskaj', tip: 'Podprti tipi datotek: {{supportTypes}}. Največ {{size}}MB na datoteko.', validation: { @@ -71,7 +71,7 @@ const translation = { run: 'Zaženi', firecrawlTitle: 'Izvleci spletno vsebino z 🔥Firecrawl', firecrawlDoc: 'Firecrawl dokumentacija', - firecrawlDocLink: 'https://docs.dify.ai/guides/knowledge-base/sync-from-website', + firecrawlDocLink: 'https://docs.dify.ai/en/guides/knowledge-base/create-knowledge-and-upload-documents/import-content-data/sync-from-website', jinaReaderTitle: 'Pretvori celotno stran v Markdown', jinaReaderDoc: 'Več o Jina Reader', jinaReaderDocLink: 'https://jina.ai/reader', @@ -92,6 +92,14 @@ const translation = { scrapTimeInfo: 'Skupaj preiskanih {{total}} strani v {{time}}s', preview: 'Predogled', maxDepthTooltip: 'Največja globina iskanja glede na vneseni URL. Globina 0 bo iskala le stran z vnesenim URL-jem, globina 1 bo iskala URL in vse za tem, dodano z enim /, in tako naprej.', + waterCrawlNotConfiguredDescription: 'Konfigurirajte Watercrawl z API ključem, da ga uporabite.', + configureWatercrawl: 'Konfiguriraj Watercrawl', + waterCrawlNotConfigured: 'Watercrawl ni konfiguriran', + watercrawlDoc: 'Watercrawl dokumentacija', + configureJinaReader: 'Konfigurirajte Jina Reader', + watercrawlDocLink: 'https://docs.dify.ai/sl/guides/knowledge-base/create-knowledge-and-upload-documents/import-content-data/sync-from-website', + configureFirecrawl: 'Konfigurirajte Firecrawl', + watercrawlTitle: 'Izvleci vsebino z interneta z Watercrawl', }, cancel: 'Odpovedati', }, @@ -200,6 +208,11 @@ const translation = { title: 'Vzpostavite povezavo z drugimi viri podatkov?', description: 'Trenutno ima baza znanja Dify le omejene vire podatkov. Prispevanje vira podatkov v bazo znanja Dify je fantastičen način za izboljšanje prilagodljivosti in moči platforme za vse uporabnike. Naš vodnik za prispevke olajša začetek. Če želite izvedeti več, kliknite spodnjo povezavo.', }, + watercrawl: { + configWatercrawl: 'Konfiguriraj Watercrawl', + getApiKeyLinkText: 'Pridobite svoj API ključ iz watercrawl.dev', + apiKeyPlaceholder: 'API ključ iz watercrawl.dev', + }, } export default translation diff --git a/web/i18n/sl-SI/dataset-settings.ts b/web/i18n/sl-SI/dataset-settings.ts index dc131c154e..9ea30f3e61 100644 --- a/web/i18n/sl-SI/dataset-settings.ts +++ b/web/i18n/sl-SI/dataset-settings.ts @@ -27,6 +27,7 @@ const translation = { learnMore: 'Izvedite več', description: ' o metodi pridobivanja.', longDescription: ' o metodi pridobivanja, to lahko kadar koli spremenite v nastavitvah znanja.', + method: 'Metoda pridobivanja', }, externalKnowledgeAPI: 'Zunanji API za znanje', externalKnowledgeID: 'ID zunanjega znanja', diff --git a/web/i18n/sl-SI/dataset.ts b/web/i18n/sl-SI/dataset.ts index 0161a0ad8d..a9f9ccbad7 100644 --- a/web/i18n/sl-SI/dataset.ts +++ b/web/i18n/sl-SI/dataset.ts @@ -168,6 +168,54 @@ const translation = { enable: 'Omogočiti', allKnowledge: 'Vse znanje', allKnowledgeDescription: 'Izberite, če želite prikazati vse znanje v tem delovnem prostoru. Samo lastnik delovnega prostora lahko upravlja vse znanje.', + metadata: { + createMetadata: { + name: 'Ime', + type: 'Tip', + namePlaceholder: 'Dodajte ime metapodatkov', + back: 'Nazaj', + title: 'Nova metapodatki', + }, + checkName: { + empty: 'Ime metapodatkov ne more biti prazno', + invalid: 'Ime metapodatkov lahko vsebuje samo male črke, številke in podčrtaje ter se mora začeti z malo črko.', + }, + batchEditMetadata: { + editMetadata: 'Uredi metapodatke', + applyToAllSelectDocument: 'Uporabi za vse izbrane dokumente', + multipleValue: 'Več vrednosti', + applyToAllSelectDocumentTip: 'Samodejno ustvarite vse zgoraj omenjene urejene in nove metapodatke za vsa izbrana dokumenta, sicer bo urejanje metapodatkov veljalo le za dokumente, ki jih imajo.', + editDocumentsNum: 'Urejanje {{num}} dokumentov', + }, + selectMetadata: { + search: 'Išči metapodatke', + newAction: 'Nova metapodatki', + manageAction: 'Upravljati', + }, + datasetMetadata: { + rename: 'Preimenuj', + namePlaceholder: 'Ime metapodatkov', + deleteTitle: 'Potrdite, da želite izbrisati', + builtIn: 'Vgrajeno', + deleteContent: 'Ali ste prepričani, da želite izbrisati metadata "{{name}}"', + builtInDescription: 'Vgrajeni metapodatki so samodejno izvlečeni in ustvarjeni. Morajo biti omogočeni pred uporabo in jih ni mogoče urejati.', + values: '{{num}} Vrednosti', + addMetaData: 'Dodaj metapodatke', + description: 'Vse metapodatke lahko upravljate tukaj v tej bazi znanja. Spremembe bodo usklajene z vsakim dokumentom.', + disabled: 'Onemogočeno', + name: 'Ime', + }, + documentMetadata: { + startLabeling: 'Začni označevanje', + technicalParameters: 'Tehnični parametri', + metadataToolTip: 'Metapodatki služijo kot pomemben filter, ki izboljšuje natančnost in pomembnost iskanja informacij. Tukaj lahko spremenite in dodate metapodatke za ta dokument.', + documentInformation: 'Informacije o dokumentu', + }, + metadata: 'Meta podatki', + chooseTime: 'Izberi čas...', + addMetadata: 'Dodaj metapodatke', + }, + embeddingModelNotAvailable: 'Model za zajemanje ni na voljo.', } export default translation diff --git a/web/i18n/sl-SI/education.ts b/web/i18n/sl-SI/education.ts new file mode 100644 index 0000000000..6a78a0472e --- /dev/null +++ b/web/i18n/sl-SI/education.ts @@ -0,0 +1,47 @@ +const translation = { + toVerifiedTip: { + end: 'za profesionalni načrt Dify.', + front: 'Zdaj ste upravičeni do statusa Preverjeno izobraževanje. Prosimo, vnesite svoje izobraževalne podatke spodaj, da zaključite postopek in prejmete', + coupon: 'izključno 100% kupon', + }, + form: { + schoolName: { + placeholder: 'Vpišite uradno, neokrnjeno ime vaše šole', + title: 'Ime vaše šole', + }, + schoolRole: { + option: { + administrator: 'Šolski administrator', + teacher: 'Učitelj', + student: 'Študent', + }, + title: 'Vaša šolska vloga', + }, + terms: { + desc: { + and: 'in', + termsOfService: 'Pogoji storitve', + end: '. Z oddajo:', + privacyPolicy: 'Politika zasebnosti', + front: 'Vaše informacije in uporaba statusa preverjene izobrazbe so predmet naših', + }, + option: { + inSchool: 'Potrjujem, da sem vpisan ali zaposlen na navedenem zavodu. Dify lahko zahteva dokazilo o vpisu/zaposlitvi. Če napačno predstavim svojo upravičenost, se strinjam, da plačam morebitne pristojbine, ki so bile sprva oproščene na podlagi mojega izobraževalnega statusa.', + age: 'Potrjujem, da sem star najmanj 18 let', + }, + title: 'Pogoji in dogovori', + }, + }, + toVerified: 'Preverite izobrazbo', + successContent: 'Za vaše račune smo izdali kupon za 100% popust na profesionalni načrt Dify. Kupon je veljaven eno leto, prosimo, uporabite ga v veljavnem obdobju.', + successTitle: 'Imate verifikacijo izobraževanja Dify', + submitError: 'Pošiljanje obrazca ni uspelo. Prosimo, poskusite znova kasneje.', + submit: 'Predloži', + rejectTitle: 'Vaša Dify izobraževalna verifikacija je bila zavrnjena.', + learn: 'Naučite se, kako preveriti izobrazbo', + emailLabel: 'Vaš trenutni elektronski naslov', + currentSigned: 'Trenutno prijavljen kot', + rejectContent: 'Na žalost niste upravičeni do statusa Verificirane izobrazbe in zato ne morete prejeti ekskluzivnega 100-odstotnega kupona za Dify profesionalni načrt, če uporabljate ta e-poštni naslov.', +} + +export default translation diff --git a/web/i18n/sl-SI/explore.ts b/web/i18n/sl-SI/explore.ts index 5ed9519044..add905631d 100644 --- a/web/i18n/sl-SI/explore.ts +++ b/web/i18n/sl-SI/explore.ts @@ -37,6 +37,7 @@ const translation = { HR: 'Kadri', Workflow: 'Potek dela', Agent: 'Agent', + Entertainment: 'Zabava', }, } diff --git a/web/i18n/sl-SI/login.ts b/web/i18n/sl-SI/login.ts index 70350021bc..12b424b0d7 100644 --- a/web/i18n/sl-SI/login.ts +++ b/web/i18n/sl-SI/login.ts @@ -105,6 +105,11 @@ const translation = { withSSO: 'Nadaljujte z enotno prijavo', licenseLostTip: 'Povezava z licenčnim strežnikom Dify ni uspela. Če želite še naprej uporabljati Dify, se obrnite na skrbnika.', licenseInactiveTip: 'Licenca Dify Enterprise za vaš delovni prostor je neaktivna. Če želite še naprej uporabljati Dify, se obrnite na skrbnika.', + webapp: { + noLoginMethod: 'Metoda overjanja ni nastavljena za spletno aplikacijo', + noLoginMethodTip: 'Prosimo, da se obrnete na sistemskega skrbnika, da dodate metodo za avtentikacijo.', + disabled: 'Avtentikacija v spletni aplikaciji je onemogočena. Prosimo, kontaktirajte skrbnika sistema, da jo omogoči. Poskusite lahko neposredno uporabljati aplikacijo.', + }, } export default translation diff --git a/web/i18n/sl-SI/plugin-tags.ts b/web/i18n/sl-SI/plugin-tags.ts index e69de29bb2..62e6aa11de 100644 --- a/web/i18n/sl-SI/plugin-tags.ts +++ b/web/i18n/sl-SI/plugin-tags.ts @@ -0,0 +1,25 @@ +const translation = { + tags: { + design: 'Oblikovanje', + videos: 'Videi', + education: 'Izobraževanje', + search: 'Iskanje', + image: 'Slika', + medical: 'Medicinski', + weather: 'Vreme', + social: 'Družbeni', + entertainment: 'Zabava', + productivity: 'Produktivnost', + finance: 'Finance', + news: 'Novice', + business: 'Poslovanje', + utilities: 'Komunalne storitve', + agent: 'Agent', + other: 'Drugo', + travel: 'Potovanje', + }, + searchTags: 'Iskalne oznake', + allTags: 'Vse nalepke', +} + +export default translation diff --git a/web/i18n/sl-SI/plugin.ts b/web/i18n/sl-SI/plugin.ts index e69de29bb2..19bf93ce1a 100644 --- a/web/i18n/sl-SI/plugin.ts +++ b/web/i18n/sl-SI/plugin.ts @@ -0,0 +1,217 @@ +const translation = { + metadata: { + title: 'Vtičniki', + }, + category: { + bundles: 'Paketi', + all: 'Vse', + extensions: 'Razširitve', + models: 'Modeli', + agents: 'Strategije agenta', + tools: 'Orodja', + }, + categorySingle: { + extension: 'Razširitev', + bundle: 'Paket', + agent: 'Agentska strategija', + tool: 'Orodje', + model: 'Model', + }, + list: { + source: { + local: 'Namestite iz lokalne paketne datoteke', + marketplace: 'Namestite iz tržnice', + github: 'Namestite iz GitHub-a', + }, + notFound: 'Nobeni vtičniki niso bili najdeni.', + noInstalled: 'Nobeni vtičniki niso nameščeni.', + }, + source: { + marketplace: 'Tržnica', + github: 'GitHub', + local: 'Lokalna paketna datoteka', + }, + detailPanel: { + categoryTip: { + local: 'Lokalni vtičnik', + marketplace: 'Nameščeno iz tržnice', + debugging: 'Orodje za odpravljanje napak', + github: 'Nameščen iz Githuba', + }, + operation: { + remove: 'Odstrani', + install: 'Namestite', + viewDetail: 'Oglej si podrobnosti', + detail: 'Podrobnosti', + update: 'Posodobitev', + checkUpdate: 'Preveri posodobitev', + info: 'Informacije o vtičniku', + }, + toolSelector: { + unsupportedContent: 'V različici vtičnika, ki je nameščena, ta akcija ni zagotovljena.', + unsupportedContent2: 'Kliknite za preklop različice.', + params: 'RAZLOGOVANJE KONFIGURACIJA', + auto: 'Samodejno', + title: 'Dodaj orodje', + settings: 'UPORABNIŠKE NASTAVITVE', + descriptionLabel: 'Opis orodja', + uninstalledLink: 'Upravljanje v vtičnikih', + unsupportedTitle: 'Nepodprta akcija', + placeholder: 'Izberite orodje...', + uninstalledTitle: 'Orodje ni nameščeno', + uninstalledContent: 'Ta vtičnik je nameščen iz lokalnega/GitHub repozitorija. Uporabite ga prosim po namestitvi.', + toolLabel: 'Orodje', + descriptionPlaceholder: 'Kratek opis namena orodja, npr. pridobitev temperature za določeno lokacijo.', + empty: 'Kliknite gumb \' \' za dodajanje orodij. Dodate lahko več orodij.', + paramsTip1: 'Nadzoruje parametre sklepanja LLM.', + paramsTip2: 'Ko je \'Avtomatsko\' izklopljeno, se uporablja privzeta vrednost.', + toolSetting: 'Nastavitve orodja', + }, + endpointDisableContent: 'Ali želite onemogočiti {{name}}?', + serviceOk: 'Storitve so v redu', + endpointDeleteTip: 'Odstrani končno točko', + actionNum: '{{num}} {{action}} VKLJUČENO', + endpointDeleteContent: 'Ali želite odstraniti {{name}}?', + configureApp: 'Konfiguriraj aplikacijo', + endpointsDocLink: 'Oglejte si dokument', + endpointModalTitle: 'Nastavi končno točko', + disabled: 'Onemogočeno', + configureTool: 'Konfigurirajte orodje', + switchVersion: 'Preklopna različica', + strategyNum: '{{num}} {{strategy}} VKLJUČENO', + endpoints: 'Končne točke', + configureModel: 'Konfiguriraj model', + modelNum: '{{num}} VZORCI VKLJUČENI', + endpointDisableTip: 'Onemogoči končno točko', + endpointsTip: 'Ta vtičnik zagotavlja specifične funkcionalnosti preko končnih točk, prav tako pa lahko konfigurirate več nizov končnih točk za trenutno delovno okolje.', + endpointModalDesc: 'Ko je konfiguriran, se lahko uporabljajo funkcije, ki jih vtičnik zagotavlja prek API končnih točk.', + endpointsEmpty: 'Kliknite gumb \' \' za dodajanje končne točke', + }, + debugInfo: { + viewDocs: 'Oglejte si dokumente', + title: 'Odpravljanje napak', + }, + privilege: { + whoCanInstall: 'Kdo lahko namesti in upravlja vtičnike?', + title: 'Nastavitve vtičnika', + admins: 'Administratori', + whoCanDebug: 'Kdo lahko odpravi napake v vtičnikih?', + everyone: 'Vsi', + noone: 'Nihče', + }, + pluginInfoModal: { + title: 'Informacije o vtičniku', + packageName: 'Paket', + release: 'Izdati', + repository: 'Shramba', + }, + action: { + usedInApps: 'Ta vtičnik se uporablja v {{num}} aplikacijah.', + checkForUpdates: 'Preverite posodobitve', + deleteContentLeft: 'Ali želite odstraniti', + deleteContentRight: 'vtičnik?', + delete: 'Odstrani vtičnik', + pluginInfo: 'Informacije o vtičniku', + }, + installModal: { + labels: { + repository: 'Shramba', + version: 'Različica', + package: 'Paket', + }, + installFailed: 'Namestitev ni uspela', + installing: 'Nameščanje...', + installedSuccessfully: 'Namestitev uspešna', + uploadFailed: 'Nalaganje ni uspelo', + pluginLoadErrorDesc: 'Ta vtičnik ne bo nameščen', + readyToInstallPackages: 'Prihajamo do namestitve naslednjih {{num}} dodatkov', + cancel: 'Prekliči', + fromTrustSource: 'Prosimo, poskrbite, da namestite le vtičnike iz <trustSource>zaupanja vrednega vira</trustSource>.', + installedSuccessfullyDesc: 'Vtičnik je bil uspešno nameščen.', + readyToInstallPackage: 'Namestitev naslednjega vtičnika', + installComplete: 'Namestitev končana', + installFailedDesc: 'Namestitev vtičnika je bila neuspešna.', + close: 'Zapri', + uploadingPackage: 'Nalagam {{packageName}}...', + readyToInstall: 'Namestitev naslednjega vtičnika', + dropPluginToInstall: 'Tukaj spustite paket vtičnika, da ga namestite', + next: 'Naprej', + back: 'Nazaj', + install: 'Namestite', + pluginLoadError: 'Napaka pri nalaganju vtičnika', + installPlugin: 'Namestite vtičnik', + }, + installFromGitHub: { + updatePlugin: 'Posodobite vtičnik iz GitHuba', + gitHubRepo: 'GitHub repozitorij', + installFailed: 'Namestitev ni uspela', + installPlugin: 'Namestite vtičnik iz GitHuba', + selectVersionPlaceholder: 'Prosim, izberite različico', + selectPackagePlaceholder: 'Prosim, izberite paket', + selectPackage: 'Izberite paket', + uploadFailed: 'Nalaganje ni uspelo', + selectVersion: 'Izberite različico', + installedSuccessfully: 'Namestitev uspešna', + installNote: 'Prosim, prepričajte se, da namestite vtičnike samo iz zaupanja vrednega vira.', + }, + upgrade: { + close: 'Zapri', + description: 'Namestitev naslednjega vtičnika', + upgrading: 'Nameščanje...', + successfulTitle: 'Namestitev uspešna', + upgrade: 'Namestite', + usedInApps: 'Uporablja se v {{num}} aplikacijah', + title: 'Namestite vtičnik', + }, + error: { + noReleasesFound: 'Ni najdenih izdaj. Prosimo preverite GitHub repozitorij ali vhodni URL.', + fetchReleasesError: 'Ne morem pridobiti izdaj. Prosim, poskusite znova pozneje.', + inValidGitHubUrl: 'Neveljavna GitHub povezava. Vnesite veljavno povezavo v formatu: https://github.com/lastnik/repo', + }, + marketplace: { + sortOption: { + mostPopular: 'Najbolj priljubljeno', + firstReleased: 'Prvič izdan', + recentlyUpdated: 'Nedavno posodobljeno', + newlyReleased: 'Nedavno izdano', + }, + and: 'in', + pluginsResult: '{{num}} rezultati', + sortBy: 'Razvrsti po', + verifiedTip: 'Verificirano s strani Dify', + discover: 'Odkrijte', + partnerTip: 'Potrjeno s strani partnerja Dify', + empower: 'Okrepite svoj razvoj AI', + noPluginFound: 'Nobenega vtičnika ni bilo najti.', + viewMore: 'Oglejte si več', + moreFrom: 'Več iz tržnice', + difyMarketplace: 'Dify Marketplace', + }, + task: { + installing: 'Namestitev {{installingLength}} vtičnikov, 0 končanih.', + clearAll: 'Počisti vse', + installError: '{{errorLength}} vtičnikov ni uspelo namestiti, kliknite za ogled', + installingWithSuccess: 'Namestitev {{installingLength}} dodatkov, {{successLength}} uspešnih.', + installedError: '{{errorLength}} vtičnikov ni uspelo namestiti', + installingWithError: 'Namestitev {{installingLength}} vtičnikov, {{successLength}} uspešnih, {{errorLength}} neuspešnih', + }, + endpointsEnabled: '{{num}} nizov končnih točk omogočenih', + search: 'Iskanje', + searchInMarketplace: 'Iskanje na trgu', + searchPlugins: 'Išči vtičnike', + fromMarketplace: 'Iz tržnice', + searchTools: 'Iskalna orodja...', + installPlugin: 'Namestite vtičnik', + from: 'Iz', + installFrom: 'NAMESTITE IZ', + searchCategories: 'Išči kategorije', + installAction: 'Namestite', + findMoreInMarketplace: 'Poiščite več v Tržnici', + install: '{{num}} namestitev', + allCategories: 'Vse kategorije', + difyVersionNotCompatible: 'Trenutna različica Dify ni združljiva s to vtičnico, prosimo, posodobite na minimalno zahtevano različico: {{minimalDifyVersion}}', + requestAPlugin: 'Zahtevajte vtičnik', + publishPlugins: 'Objavljanje vtičnikov', +} + +export default translation diff --git a/web/i18n/sl-SI/share-app.ts b/web/i18n/sl-SI/share-app.ts index 68ad6594fc..8b7fe87cbd 100644 --- a/web/i18n/sl-SI/share-app.ts +++ b/web/i18n/sl-SI/share-app.ts @@ -27,6 +27,12 @@ const translation = { }, tryToSolve: 'Poskusite rešiti', temporarySystemIssue: 'Oprostite, začasna težava s sistemom.', + expand: 'Razširi', + collapse: 'Skrči', + newChatTip: 'Že v novem klepetu', + viewChatSettings: 'Ogled nastavitve klepeta', + chatSettingsTitle: 'Nova nastavitev klepeta', + chatFormTip: 'Nastavitve klepeta ni mogoče spremeniti po začetku klepeta.', }, generation: { tabs: { @@ -65,6 +71,11 @@ const translation = { moreThanMaxLengthLine: 'Vrstica {{rowIndex}}: vrednost {{varName}} ne sme biti daljša od {{maxLength}} znakov', atLeastOne: 'Prosimo, vnesite vsaj eno vrstico v naloženo datoteko.', }, + execution: 'IZVEDBA', + executions: '{{num}} IZVRŠITEV', + }, + login: { + backToHome: 'Nazaj na začetno stran', }, } diff --git a/web/i18n/sl-SI/time.ts b/web/i18n/sl-SI/time.ts index e2410dd34b..b88a33b675 100644 --- a/web/i18n/sl-SI/time.ts +++ b/web/i18n/sl-SI/time.ts @@ -1,3 +1,37 @@ -const translation = {} +const translation = { + daysInWeek: { + Thu: 'Četrtek', + Fri: 'Petek', + Tue: 'Torek', + Sun: 'Sonce', + Wed: 'Sreda', + Mon: 'Mon', + Sat: 'Satelit', + }, + months: { + February: 'Februar', + April: 'April', + October: 'oktober', + May: 'Maj', + December: 'December', + September: 'September', + January: 'Januar', + July: 'Julij', + March: 'Marec', + June: 'Junij', + November: 'November', + August: 'Avgust', + }, + operation: { + cancel: 'Prekliči', + now: 'Zdaj', + pickDate: 'Izberi datum', + ok: 'V redu', + }, + title: { + pickTime: 'Izberi čas', + }, + defaultPlaceholder: 'Izberi čas...', +} export default translation diff --git a/web/i18n/sl-SI/tools.ts b/web/i18n/sl-SI/tools.ts index 59989e9750..e557725462 100644 --- a/web/i18n/sl-SI/tools.ts +++ b/web/i18n/sl-SI/tools.ts @@ -15,7 +15,6 @@ const translation = { }, author: 'Avtor', auth: { - unauthorized: 'Za avtorizacijo', authorized: 'Avtorizirano', setup: 'Nastavite avtorizacijo za uporabo', setupModalTitle: 'Nastavi avtorizacijo', diff --git a/web/i18n/sl-SI/workflow.ts b/web/i18n/sl-SI/workflow.ts index e4a71ddac3..d050d42f4e 100644 --- a/web/i18n/sl-SI/workflow.ts +++ b/web/i18n/sl-SI/workflow.ts @@ -1,1214 +1,911 @@ const translation = { common: { - undo: 'Razveljavi', - redo: 'Uveljavi', - editing: 'Urejanje', - autoSaved: 'Samodejno shranjeno', - unpublished: 'Nepublicirano', - published: 'Objavljeno', - publish: 'Objavi', - update: 'Posodobi', - run: 'Zaženi', - running: 'V teku', - inRunMode: 'V načinu zagona', - inPreview: 'V predogledu', - inPreviewMode: 'V načinu predogleda', - preview: 'Predogled', - viewRunHistory: 'Ogled zgodovine zagona', - runHistory: 'Zgodovina zagona', - goBackToEdit: 'Nazaj na urejevalnik', - conversationLog: 'Zapisnik pogovora', - features: 'Značilnosti', - debugAndPreview: 'Predogled', - restart: 'Ponovni zagon', - currentDraft: 'Trenutni osnutek', - currentDraftUnpublished: 'Trenutni osnutek ni objavljen', - latestPublished: 'Zadnje objavljeno', - publishedAt: 'Objavljeno ob', - restore: 'Obnovi', - runApp: 'Zaženi aplikacijo', - batchRunApp: 'Serijski zagon aplikacije', - accessAPIReference: 'Dostop do API referenc', - embedIntoSite: 'Vdelaj v spletno stran', - addTitle: 'Dodaj naslov...', - addDescription: 'Dodaj opis...', - noVar: 'Ni spremenljivke', - searchVar: 'Išči spremenljivko', - variableNamePlaceholder: 'Ime spremenljivke', - setVarValuePlaceholder: 'Nastavi vrednost spremenljivke', - needConnectTip: 'Ta korak ni povezan z ničemer', - maxTreeDepth: 'Največja omejitev je {{depth}} vozlišč na vejo', - needEndNode: 'Dodati je treba zaključni blok', - needAnswerNode: 'Dodati je treba blok z odgovorom', - workflowProcess: 'Proces delovnega toka', - notRunning: 'Še ni v teku', - previewPlaceholder: 'Vnesite vsebino v spodnje polje za začetek odpravljanja napak klepetalnika', effectVarConfirm: { title: 'Odstrani spremenljivko', - content: 'Spremenljivka se uporablja v drugih vozliščih. Ali jo kljub temu želite odstraniti?', + content: 'Spremenljivka se uporablja v drugih vozliščih. Ali jo še vedno želite odstraniti?', }, - insertVarTip: 'Pritisnite tipko \'/\' za hitro vstavljanje', - processData: 'Obdelava podatkov', - input: 'Vnos', - output: 'Izhod', - jinjaEditorPlaceholder: 'Vnesite \'/\' ali \'{\' za vstavljanje spremenljivke', - viewOnly: 'Samo ogled', - showRunHistory: 'Prikaži zgodovino zagona', - enableJinja: 'Omogoči podporo za Jinja predloge', - learnMore: 'Več informacij', - copy: 'Kopiraj', - duplicate: 'Podvoji', - addBlock: 'Dodaj blok', - pasteHere: 'Prilepi tukaj', - pointerMode: 'Način kazalca', - handMode: 'Način roke', - model: 'Model', - workflowAsTool: 'Potek dela kot orodje', - configureRequired: 'Potrebna konfiguracija', - configure: 'Konfiguriraj', - manageInTools: 'Upravljaj v Orodjih', - workflowAsToolTip: 'Po posodobitvi poteka dela je potrebno ponovno konfigurirati orodje.', - viewDetailInTracingPanel: 'Oglejte si podrobnosti', - syncingData: 'Sinhronizacija podatkov, le nekaj sekund.', - importDSL: 'Uvozi DSL', - importDSLTip: 'Trenutni osnutek bo prepisan. Pred uvozom izvozite delovni tok kot varnostno kopijo.', - backupCurrentDraft: 'Varnostno kopiraj trenutni osnutek', - chooseDSL: 'Izberite DSL(yml) datoteko', - overwriteAndImport: 'Prepiši in uvozi', - importFailure: 'Uvoz ni uspel', - importSuccess: 'Uvoz uspešen', - parallelRun: 'Vzporedni zagon', parallelTip: { click: { + desc: 'dodati', title: 'Klikni', - desc: ' za dodajanje', }, drag: { + desc: 'povezati', title: 'Povleci', - desc: ' za povezavo', }, - limit: 'Vzporednost je omejena na {{num}} vej.', - depthLimit: 'Omejitev gnezdenja vzporednih slojev na {{num}} slojev', + depthLimit: 'Meja paralelnega gnezdenja plasti {{num}} plasti', + limit: 'Paralelizem je omejen na {{num}} veje.', }, - disconnect: 'Prekini povezavo', - jumpToNode: 'Skoči na to vozlišče', - addParallelNode: 'Dodaj vzporedno vozlišče', - parallel: 'VZPOREDNO', + versionHistory: 'Zgodovina različic', + published: 'Objavljeno', + run: 'Teči', + featuresDocLink: 'Nauči se več', + notRunning: 'Še ne teče', + exportImage: 'Izvozi sliko', + openInExplore: 'Odpri v Raziskovanju', + publishUpdate: 'Objavi posodobitev', + disconnect: 'Odklop', + exportJPEG: 'Izvozi kot JPEG', + exportSVG: 'Izvozi kot SVG', + model: 'Model', + restart: 'Znova zaženi', + running: 'Tek', + undo: 'Razveljavi', + enableJinja: 'Omogoči podporo za Jinja predloge', + publish: 'Objavi', + importSuccess: 'Uvoz uspešen', + workflowAsTool: 'Delovni potek kot orodje', + update: 'Posodobitev', + jumpToNode: 'Preskoči na ta vozel', + publishedAt: 'Objavljeno', + addParallelNode: 'Dodaj paralelni vozlišče', + inPreview: 'V predogledu', + workflowAsToolTip: 'Zaradi posodobitve delovnega poteka je potrebna ponovna konfiguracija orodja.', + variableNamePlaceholder: 'Ime spremenljivke', + needEndNode: 'Skrivnostna vozlišča je treba dodati.', + onFailure: 'O neuspehu', + embedIntoSite: 'Vstavite v spletno stran', + conversationLog: 'Pogovor Log', + accessAPIReference: 'Dostop do referenčnega API-ja', + inPreviewMode: 'V načinu predogleda', + previewPlaceholder: 'Vnesite vsebino v spodnje polje, da začnete odpravljati napake v chatbotu', + input: 'Vnos', + importDSLTip: 'Trenutni osnutek bo prepisan. Izvozite delovni postopek kot varnostno kopijo pred uvozom.', + duplicate: 'Podvojiti', + loadMore: 'Naloži več', + addTitle: 'Dodajte naslov...', + goBackToEdit: 'Pojdi nazaj k uredniku', + needAnswerNode: 'Skrivnostni element mora biti dodan.', + needConnectTip: 'Ta korak ni povezan z ničemer.', + searchVar: 'Iskalna spremenljivka', branch: 'VEJA', - fileUploadTip: 'Funkcije nalaganja slik so nadgrajene na nalaganje datotek.', - featuresDocLink: 'Izvedi več', - featuresDescription: 'Izboljšajte uporabniško izkušnjo spletne aplikacije', - ImageUploadLegacyTip: 'Zdaj lahko ustvarite spremenljivke vrste datoteke v začetnem obrazcu. V prihodnje ne bomo več podpirali funkcije nalaganja slik.', - importWarning: 'Previdnost', + viewRunHistory: 'Poglej zgodovino izvajanja', + learnMore: 'Nauči se več', + workflowProcess: 'Delovni postopek', + preview: 'Predogled', + output: 'Izhod', + viewDetailInTracingPanel: 'Oglejte si podrobnosti', + debugAndPreview: 'Predogled', + restore: 'Obnovi', + latestPublished: 'Najnovejša objavljena', + importDSL: 'Uvozi DSL', + viewOnly: 'Samo za ogled', + insertVarTip: 'Pritisnite tipko \'/\' za hitro vstavljanje', + currentDraftUnpublished: 'Trenutna osnutek neobjavljeno', + showRunHistory: 'Prikaži zgodovino izvajanja', + runHistory: 'Zgodovina izvajanja', + fileUploadTip: 'Funkcije nalaganja slik so bile nadgrajene na nalaganje datotek.', + backupCurrentDraft: 'Varnostno kopiraj trenutni osnutek', + overwriteAndImport: 'Prepiši in uvozi', + features: 'Značilnosti', + exportPNG: 'Izvozi kot PNG', + parallelRun: 'Paralelni tek', + chooseDSL: 'Izberi DSL datoteko', + unpublished: 'Nepublikirano', + pasteHere: 'Prilepite tukaj', + featuresDescription: 'Izboljšanje uporabniške izkušnje spletne aplikacije', + exitVersions: 'Izhodne različice', + editing: 'Urejanje', + addFailureBranch: 'Dodaj neuspešno vejo', + syncingData: 'Sinhronizacija podatkov, samo nekaj sekund.', + noVar: 'Brez spremenljivke', + runApp: 'Zaženi aplikacijo', + ImageUploadLegacyTip: 'Zdaj lahko v začetni obliki ustvarite spremenljivke datotečnega tipa. V prihodnje ne bomo več podpirali funkcije nalaganja slik.', + importWarning: 'Opozorilo', + copy: 'Kopirati', + redo: 'Ponovno naredi', + currentDraft: 'Trenutni osnutek', + manageInTools: 'Upravljajte v orodjih', + parallel: 'PARALELNO', importWarningDetails: 'Razlika v različici DSL lahko vpliva na nekatere funkcije', - openInExplore: 'Odpri v razišči', - addFailureBranch: 'Dodajanje veje »Fail«', - onFailure: 'O neuspehu', + addDescription: 'Dodajte opis...', + maxTreeDepth: 'Največje število {{depth}} vozlišč na vejo', + jinjaEditorPlaceholder: 'Vnesite \'/\' ali \'{\', da vstavite spremenljivko', + batchRunApp: 'Program za serijsko izvajanje', + importFailure: 'Uvoz ni uspel', + handMode: 'Ročni način', + processData: 'Obdelava podatkov', + addBlock: 'Dodaj vozlišče', noHistory: 'Brez zgodovine', - loadMore: 'Nalaganje več potekov dela', + configureRequired: 'Konfigurirajte zahteve', + setVarValuePlaceholder: 'Nastavi spremenljivko', + pointerMode: 'Način s kazalcem', + autoSaved: 'Samodejno shranjeno', + configure: 'Konfiguriraj', }, env: { - envPanelTitle: 'Spremenljivke okolja', - envDescription: 'Spremenljivke okolja se uporabljajo za shranjevanje zasebnih informacij in poverilnic. So samo za branje in jih je mogoče ločiti od DSL datoteke med izvozom.', - envPanelButton: 'Dodaj spremenljivko', modal: { - title: 'Dodaj spremenljivko okolja', - editTitle: 'Uredi spremenljivko okolja', - type: 'Vrsta', - name: 'Ime', - namePlaceholder: 'ime okolja', value: 'Vrednost', + title: 'Dodaj okoljsko spremenljivko', + name: 'Ime', valuePlaceholder: 'vrednost okolja', - secretTip: 'Uporablja se za definiranje občutljivih informacij ali podatkov, s konfiguriranimi nastavitvami DSL za preprečevanje uhajanja.', + namePlaceholder: 'ime okolja', + type: 'Tip', + editTitle: 'Uredi okoljsko spremenljivko', + secretTip: 'Uporablja se za opredelitev občutljivih informacij ali podatkov, s konfiguriranimi nastavitvami DSL za preprečevanje puščanja.', }, export: { - title: 'Izvoziti skrivne spremenljivke okolja?', - checkbox: 'Izvozi skrivne vrednosti', - ignore: 'Izvozi DSL', export: 'Izvozi DSL z skrivnimi vrednostmi', + ignore: 'Izvoz DSL', + checkbox: 'Izvozi tajne vrednosti', + title: 'Izvozi skrivne okoljske spremenljivke?', }, - chatVariable: { - panelTitle: 'Spremenljivke pogovora', - panelDescription: 'Spremenljivke pogovora se uporabljajo za shranjevanje interaktivnih informacij, ki jih mora LLM zapomniti, vključno z zgodovino pogovorov, naloženimi datotekami, uporabniškimi nastavitvami. So za branje in pisanje.', - docLink: 'Obiščite naše dokumente za več informacij.', - button: 'Dodaj spremenljivko', - modal: { - title: 'Dodaj spremenljivko pogovora', - editTitle: 'Uredi spremenljivko pogovora', - name: 'Ime', - namePlaceholder: 'Ime spremenljivke', - type: 'Vrsta', - value: 'Privzeta vrednost', - valuePlaceholder: 'Privzeta vrednost, pustite prazno, če je ne želite nastaviti', - description: 'Opis', - descriptionPlaceholder: 'Opišite spremenljivko', - editInJSON: 'Uredi v JSON', - oneByOne: 'Dodaj eno po eno', - editInForm: 'Uredi v obrazcu', - arrayValue: 'Vrednost', - addArrayValue: 'Dodaj vrednost', - objectKey: 'Ključ', - objectType: 'Vrsta', - objectValue: 'Privzeta vrednost', - }, - storedContent: 'Shranjena vsebina', - updatedAt: 'Posodobljeno ob', - }, - changeHistory: { - title: 'Zgodovina sprememb', - placeholder: 'Še niste ničesar spremenili', - clearHistory: 'Počisti zgodovino', - hint: 'Namig', - hintText: 'Vaša dejanja urejanja se spremljajo v zgodovini sprememb, ki se hrani na vaši napravi med trajanjem te seje. Ta zgodovina se bo izbrisala, ko zapustite urejevalnik.', - stepBackward_one: '{{count}} korak nazaj', - stepBackward_other: '{{count}} korakov nazaj', - stepForward_one: '{{count}} korak naprej', - stepForward_other: '{{count}} korakov naprej', - sessionStart: 'Začetek seje', - currentState: 'Trenutno stanje', - nodeTitleChange: 'Naslov bloka spremenjen', - nodeDescriptionChange: 'Opis bloka spremenjen', - nodeDragStop: 'Blok premaknjen', - nodeChange: 'Blok spremenjen', - nodeConnect: 'Blok povezan', - nodePaste: 'Blok prilepljen', - nodeDelete: 'Blok izbrisan', - nodeAdd: 'Blok dodan', - nodeResize: 'Velikost bloka spremenjena', - noteAdd: 'Opomba dodana', - noteChange: 'Opomba spremenjena', - noteDelete: 'Opomba izbrisana', - edgeDelete: 'Blok prekinjen', - }, - errorMsg: { - fieldRequired: '{{field}} je obvezen', - rerankModelRequired: 'Pred vklopom modela za ponovno razvrščanje, prosimo potrdite, da je bil model uspešno konfiguriran v nastavitvah.', - authRequired: 'Potrebna je avtorizacija', - invalidJson: '{{field}} je neveljaven JSON', - fields: { - variable: 'Ime spremenljivke', - variableValue: 'Vrednost spremenljivke', - code: 'Koda', - model: 'Model', - rerankModel: 'Model za ponovno razvrščanje', - }, - invalidVariable: 'Neveljavna spremenljivka', - }, - singleRun: { - testRun: 'Testni zagon', - startRun: 'Začni zagon', - running: 'V teku', - testRunIteration: 'Iteracija testnega zagona', - back: 'Nazaj', - iteration: 'Iteracija', - }, - tabs: { - 'searchBlock': 'Iskanje bloka', - 'blocks': 'Bloki', - 'searchTool': 'Iskanje orodja', - 'tools': 'Orodja', - 'allTool': 'Vsa', - 'builtInTool': 'Vgrajena', - 'customTool': 'Prilagojena', - 'workflowTool': 'Potek dela', - 'question-understand': 'Razumevanje vprašanja', - 'logic': 'Logika', - 'transform': 'Pretvorba', - 'utilities': 'Pripomočki', - 'noResult': 'Ni najdenih zadetkov', - }, - blocks: { - 'start': 'Začetek', - 'end': 'Konec', - 'answer': 'Odgovor', - 'llm': 'LLM', - 'knowledge-retrieval': 'Pridobivanje znanja', - 'question-classifier': 'Klasifikator vprašanj', - 'if-else': 'IF/ELSE', - 'code': 'Koda', - 'template-transform': 'Predloga', - 'http-request': 'HTTP zahteva', - 'variable-assigner': 'Dodeljevalec spremenljivk', - 'variable-aggregator': 'Zbiralnik spremenljivk', - 'assigner': 'Dodeljevalec spremenljivk', - 'iteration-start': 'Začetek iteracije', - 'iteration': 'Iteracija', - 'parameter-extractor': 'Izvleček parametrov', - }, - blocksAbout: { - 'start': 'Določite začetne parametre za zagon delovnega toka', - 'end': 'Določite konec in vrsto rezultata delovnega toka', - 'answer': 'Določite vsebino odgovora v klepetalni konverzaciji', - 'llm': 'Klicanje velikih jezikovnih modelov za odgovarjanje na vprašanja ali obdelavo naravnega jezika', - 'knowledge-retrieval': 'Omogoča iskanje vsebine, povezane z uporabnikovimi vprašanji, iz baze znanja', - 'question-classifier': 'Določite pogoje za klasifikacijo uporabniških vprašanj; LLM lahko določi, kako se bo konverzacija razvijala glede na klasifikacijski opis', - 'if-else': 'Omogoča razdelitev delovnega toka na dve veji glede na pogoje if/else', - 'code': 'Izvedite del kode Python ali NodeJS za implementacijo prilagojene logike', - 'template-transform': 'Pretvorite podatke v niz z uporabo Jinja predloge', - 'http-request': 'Omogoča pošiljanje strežniških zahtev preko HTTP protokola', - 'variable-assigner': 'Združi spremenljivke več vej v eno spremenljivko za enotno konfiguracijo vozlišč nižje v poteku.', - 'assigner': 'Vozlišče za dodelitev spremenljivk se uporablja za dodelitev vrednosti pisnim spremenljivkam (kot so spremenljivke konverzacije).', - 'variable-aggregator': 'Združi spremenljivke več vej v eno spremenljivko za enotno konfiguracijo vozlišč nižje v poteku.', - 'iteration': 'Izvedite več korakov na seznamu objektov, dokler niso vsi rezultati izpisani.', - 'parameter-extractor': 'Uporabite LLM za izvleček strukturiranih parametrov iz naravnega jezika za klice orodij ali HTTP zahteve.', - }, - operator: { - zoomIn: 'Povečaj', - zoomOut: 'Pomanjšaj', - zoomTo50: 'Povečaj na 50%', - zoomTo100: 'Povečaj na 100%', - zoomToFit: 'Prilagodi velikost', - }, - panel: { - userInputField: 'Vnosno polje uporabnika', - changeBlock: 'Spremeni blok', - helpLink: 'Povezava za pomoč', - about: 'O', - createdBy: 'Ustvaril ', - nextStep: 'Naslednji korak', - addNextStep: 'Dodaj naslednji blok v tem delovnem toku', - selectNextStep: 'Izberi naslednji blok', - runThisStep: 'Zaženi ta korak', - checklist: 'Kontrolni seznam', - checklistTip: 'Poskrbite, da so vsi problemi rešeni pred objavo', - checklistResolved: 'Vsi problemi so rešeni', - organizeBlocks: 'Organiziraj bloke', - change: 'Spremeni', - optional: '(neobvezno)', - }, - nodes: { - common: { - outputVars: 'Izhodne spremenljivke', - insertVarTip: 'Vstavi spremenljivko', - memory: { - memory: 'Pomnjenje', - memoryTip: 'Nastavitve pomnjenja klepeta', - windowSize: 'Velikost okna', - conversationRoleName: 'Ime vloge v konverzaciji', - user: 'Predpona uporabnika', - assistant: 'Predpona pomočnika', - }, - memories: { - title: 'Pomnjenje', - tip: 'Pomnjenje klepeta', - builtIn: 'Vgrajeno', - }, - }, - start: { - required: 'obvezno', - inputField: 'Vnosno polje', - builtInVar: 'Vgrajene spremenljivke', - outputVars: { - query: 'Uporabniški vnos', - memories: { - des: 'Zgodovina konverzacije', - type: 'vrsta sporočila', - content: 'vsebina sporočila', - }, - files: 'Seznam datotek', - }, - noVarTip: 'Nastavite vnose, ki jih lahko uporabite v delovnem toku', - }, - end: { - outputs: 'Izhodi', - output: { - type: 'vrsta izhoda', - variable: 'izhoda spremenljivka', - }, - type: { - 'none': 'Brez', - 'plain-text': 'Navadno besedilo', - 'structured': 'Strukturirano', - }, - }, - answer: { - answer: 'Odgovor', - outputVars: 'Izhodne spremenljivke', - }, - llm: { - model: 'model', - variables: 'spremenljivke', - context: 'kontekst', - contextTooltip: 'Kot kontekst lahko uvozite Znanje', - notSetContextInPromptTip: 'Za omogočanje funkcije konteksta, prosimo, izpolnite kontekstno spremenljivko v POZIVU.', - prompt: 'poziv', - roleDescription: { - system: 'Podajte splošna navodila za konverzacijo', - user: 'Podajte navodila, poizvedbe ali katero koli besedilno vsebino za model', - assistant: 'Odzivi modela na uporabniška sporočila', - }, - addMessage: 'Dodaj sporočilo', - vision: 'vizija', - files: 'Datoteke', - resolution: { - name: 'Ločljivost', - high: 'Visoka', - low: 'Nizka', - }, - outputVars: { - output: 'Generirana vsebina', - usage: 'Podatki o uporabi modela', - }, - singleRun: { - variable: 'Spremenljivka', - }, - sysQueryInUser: 'sys.query v uporabniškem sporočilu je obvezno', - }, - knowledgeRetrieval: { - queryVariable: 'Poizvedbena spremenljivka', - knowledge: 'Znanje', - outputVars: { - output: 'Pridobljeni segmentirani podatki', - content: 'Segmentirana vsebina', - title: 'Segmentirani naslov', - icon: 'Segmentirana ikona', - url: 'Segmentiran URL', - metadata: 'Drugi metapodatki', - }, - }, - http: { - inputVars: 'Vnosne spremenljivke', - api: 'API', - apiPlaceholder: 'Vnesite URL, vstavite spremenljivko z tipko ‘/’', - notStartWithHttp: 'API mora začeti z http:// ali https://', - key: 'Ključ', - value: 'Vrednost', - bulkEdit: 'Serijsko urejanje', - keyValueEdit: 'Urejanje ključ-vrednost', - headers: 'Glave', - params: 'Parametri', - body: 'Telo', - outputVars: { - body: 'Vsebina odgovora', - statusCode: 'Statusna koda odgovora', - headers: 'Seznam glave odgovora v JSON', - files: 'Seznam datotek', - }, - }, - authorization: { - 'authorization': 'Avtorizacija', - 'authorizationType': 'Vrsta avtorizacije', - 'no-auth': 'Brez', - 'api-key': 'API-ključ', - 'auth-type': 'Vrsta avtorizacije', - 'basic': 'Osnovna', - 'bearer': 'Imetnik', - 'custom': 'Prilagojena', - 'api-key-title': 'API ključ', - 'header': 'Glava', - }, - insertVarPlaceholder: 'vnesite \'/\' za vstavljanje spremenljivke', - timeout: { - title: 'Časovna omejitev', - connectLabel: 'Časovna omejitev povezave', - connectPlaceholder: 'Vnesite časovno omejitev povezave v sekundah', - readLabel: 'Časovna omejitev branja', - readPlaceholder: 'Vnesite časovno omejitev branja v sekundah', - writeLabel: 'Časovna omejitev pisanja', - writePlaceholder: 'Vnesite časovno omejitev pisanja v sekundah', - }, - }, - code: { - inputVars: 'Vhodne spremenljivke', - outputVars: 'Izhodne spremenljivke', - advancedDependencies: 'Napredne odvisnosti', - advancedDependenciesTip: 'Dodajte nekaj prednaloženih odvisnosti, ki potrebujejo več časa za nalaganje ali niso privzeto vgrajene', - searchDependencies: 'Išči odvisnosti', - }, - templateTransform: { - inputVars: 'Vhodne spremenljivke', - code: 'Koda', - codeSupportTip: 'Podpira samo Jinja2', - outputVars: { - output: 'Pretvorjena vsebina', - }, - }, - ifElse: { - if: 'Če', - else: 'Sicer', - elseDescription: 'Uporablja se za definiranje logike, ki naj se izvede, ko pogoj "če" ni izpolnjen.', - and: 'in', - or: 'ali', - operator: 'Operater', - notSetVariable: 'Najprej nastavite spremenljivko', - comparisonOperator: { - 'contains': 'vsebuje', - 'not contains': 'ne vsebuje', - 'start with': 'se začne z', - 'end with': 'se konča z', - 'is': 'je', - 'is not': 'ni', - 'empty': 'je prazna', - 'not empty': 'ni prazna', - 'null': 'je null', - 'not null': 'ni null', - }, - enterValue: 'Vnesite vrednost', - addCondition: 'Dodaj pogoj', - conditionNotSetup: 'Pogoj NI nastavljen', - selectVariable: 'Izberite spremenljivko...', - }, - variableAssigner: { - title: 'Dodeli spremenljivke', - outputType: 'Vrsta izhoda', - varNotSet: 'Spremenljivka ni nastavljena', - noVarTip: 'Dodajte spremenljivke, ki jih želite dodeliti', - type: { - string: 'Niz', - number: 'Število', - object: 'Objekt', - array: 'Polje', - }, - aggregationGroup: 'Skupina za združevanje', - aggregationGroupTip: 'Omogočanje te funkcije omogoča agregatorju spremenljivk združevanje več naborov spremenljivk.', - addGroup: 'Dodaj skupino', - outputVars: { - varDescribe: 'Izhod {{groupName}}', - }, - setAssignVariable: 'Nastavi dodeljeno spremenljivko', - }, - assigner: { - 'assignedVariable': 'Dodeljena spremenljivka', - 'writeMode': 'Način pisanja', - 'writeModeTip': 'Način dodajanja: Na voljo samo za spremenljivke vrste polje.', - 'over-write': 'Prepiši', - 'append': 'Dodaj', - 'plus': 'Plus', - 'clear': 'Počisti', - 'setVariable': 'Nastavi spremenljivko', - 'variable': 'Spremenljivka', - }, - tool: { - toAuthorize: 'Za avtorizacijo', - inputVars: 'Vhodne spremenljivke', - outputVars: { - text: 'orodje je ustvarilo vsebino', - files: { - title: 'orodje je ustvarilo datoteke', - type: 'Podprta vrsta. Trenutno podpira samo slike', - transfer_method: 'Način prenosa. Vrednosti so remote_url ali local_file', - url: 'URL slike', - upload_file_id: 'ID naložene datoteke', - }, - json: 'orodje je ustvarilo json', - }, - }, - questionClassifiers: { - model: 'model', - inputVars: 'Vhodne spremenljivke', - outputVars: { - className: 'Ime razreda', - }, - class: 'Razred', - classNamePlaceholder: 'Vnesite ime razreda', - advancedSetting: 'Napredna nastavitev', - topicName: 'Ime teme', - topicPlaceholder: 'Vnesite ime teme', - addClass: 'Dodaj razred', - instruction: 'Navodilo', - instructionTip: 'Vnesite dodatna navodila, da bo klasifikator vprašanj lažje razumel, kako kategorizirati vprašanja.', - instructionPlaceholder: 'Vnesite vaše navodilo', - }, - parameterExtractor: { - inputVar: 'Vhodna spremenljivka', - extractParameters: 'Izvleči parametre', - importFromTool: 'Uvozi iz orodij', - addExtractParameter: 'Dodaj izvlečen parameter', - addExtractParameterContent: { - name: 'Ime', - namePlaceholder: 'Ime izvlečenega parametra', - type: 'Vrsta', - typePlaceholder: 'Vrsta izvlečenega parametra', - description: 'Opis', - descriptionPlaceholder: 'Opis izvlečenega parametra', - required: 'Obvezno', - requiredContent: 'Obvezno je uporabljeno samo kot referenca za sklepanja modela in ne za obvezno preverjanje izhoda parametra.', - }, - extractParametersNotSet: 'Parametri za izvleček niso nastavljeni', - instruction: 'Navodilo', - instructionTip: 'Vnesite dodatna navodila, da parameter extractor lažje razume, kako izvleči parametre.', - advancedSetting: 'Napredna nastavitev', - reasoningMode: 'Način sklepanja', - reasoningModeTip: 'Lahko izberete ustrezen način sklepanja glede na sposobnost modela za odgovore na navodila za klice funkcij ali pozive.', - isSuccess: 'Je uspeh. Pri uspehu je vrednost 1, pri neuspehu pa 0.', - errorReason: 'Razlog za napako', - }, - iteration: { - deleteTitle: 'Izbrisati vozlišče iteracije?', - deleteDesc: 'Brisanje vozlišča iteracije bo izbrisalo vsa podrejena vozlišča', - input: 'Vhod', - output: 'Izhodne spremenljivke', - iteration_one: '{{count}} iteracija', - iteration_other: '{{count}} iteracij', - currentIteration: 'Trenutna iteracija', - }, - note: { - addNote: 'Dodaj opombo', - editor: { - placeholder: 'Zapišite opombo...', - small: 'Majhno', - medium: 'Srednje', - large: 'Veliko', - bold: 'Krepko', - italic: 'Poševno', - strikethrough: 'Prečrtano', - link: 'Povezava', - openLink: 'Odpri', - unlink: 'Odstrani povezavo', - enterUrl: 'Vnesite URL...', - invalidUrl: 'Neveljaven URL', - bulletList: 'Označen seznam', - showAuthor: 'Pokaži avtorja', - }, - }, - }, - tracing: { - stopBy: 'Ustavljeno s strani {{user}}', + envPanelTitle: 'Spremenljivke okolja', + envPanelButton: 'Dodaj spremenljivko', + envDescription: 'Okoljske spremenljivke se lahko uporabljajo za shranjevanje zasebnih informacij in poverilnic. So samo za branje in jih je mogoče ločiti od DSL datoteke med izvozem.', }, chatVariable: { modal: { - type: 'Vrsta', - objectValue: 'Privzeta vrednost', - description: 'Opis', - editTitle: 'Urejanje spremenljivke pogovora', namePlaceholder: 'Ime spremenljivke', - valuePlaceholder: 'Privzeta vrednost, pustite prazno, da ni nastavljeno', - title: 'Dodajanje spremenljivke pogovora', - editInJSON: 'Urejanje v JSON', - value: 'Privzeta vrednost', - oneByOne: 'Dodajanje enega za drugim', + title: 'Dodaj spremenljivko za pogovor', + editInJSON: 'Uredi v JSON', objectKey: 'Ključ', - objectType: 'Vrsta', - arrayValue: 'Vrednost', + valuePlaceholder: 'Privzeta vrednost, pustite prazno, da je ne nastavite', + description: 'Opis', + type: 'Tip', + value: 'Privzeta vrednost', name: 'Ime', - descriptionPlaceholder: 'Opis spremenljivke', + arrayValue: 'Vrednost', + editTitle: 'Uredi spremenljivko pogovora', editInForm: 'Uredi v obrazcu', - addArrayValue: 'Dodajanje vrednosti', + addArrayValue: 'Dodati vrednost', + objectType: 'Tip', + oneByOne: 'Dodaj eno po eno', + objectValue: 'Privzeta vrednost', + descriptionPlaceholder: 'Opisujte spremenljivko', }, + updatedAt: 'Posodobljeno ob', + docLink: 'Obiščite našo dokumentacijo, da se naučite več.', + panelTitle: 'Pogovor Spremenljivke', storedContent: 'Shranjena vsebina', - updatedAt: 'Posodobljeno na', - panelTitle: 'Spremenljivke pogovora', - button: 'Dodajanje spremenljivke', - panelDescription: 'Spremenljivke pogovora se uporabljajo za shranjevanje interaktivnih informacij, ki si jih mora LLM zapomniti, vključno z zgodovino pogovorov, naloženimi datotekami, uporabniškimi nastavitvami. So branje in pisanje.', - docLink: 'Če želite izvedeti več, obiščite naše dokumente.', + panelDescription: 'Spremenljivke pogovora se uporabljajo za shranjevanje interaktivnih informacij, ki se jih LLM mora zapomniti, vključno z zgodovino pogovorov, naloženimi datotekami in uporabnikovimi nastavitvami. So branje-in-pisanje.', + button: 'Dodaj spremenljivko', }, changeHistory: { - nodeChange: 'Blokiranje spremenjeno', - placeholder: 'Ničesar še niste spremenili', - nodeDescriptionChange: 'Opis bloka je bil spremenjen', - nodePaste: 'Blokiranje lepljenja', - noteDelete: 'Opomba izbrisana', - nodeDragStop: 'Blok premaknjen', - nodeConnect: 'Blok povezan', + stepBackward_other: '{{count}} korakov nazaj', sessionStart: 'Začetek seje', - nodeDelete: 'Blokiraj izbrisane', - stepBackward_other: '{{count}} stopi nazaj', - hint: 'Namig', - noteAdd: 'Opomba dodana', - clearHistory: 'Počisti zgodovino', - stepForward_one: '{{count}} korak naprej', - stepBackward_one: '{{count}} korak nazaj', - nodeAdd: 'Blokiranje dodano', + nodeTitleChange: 'Naslov vozlišča je bil spremenjen', noteChange: 'Opomba spremenjena', - hintText: 'Dejanjem urejanja se sledi v zgodovini sprememb, ki je shranjena v napravi za čas trajanja te seje. Ta zgodovina bo izbrisana, ko zapustite urejevalnik.', - stepForward_other: '{{count}} koraki naprej', - edgeDelete: 'Blok je prekinjen.', - nodeTitleChange: 'Naslov bloka spremenjen', - nodeResize: 'Spremeni velikost bloka', title: 'Zgodovina sprememb', + noteAdd: 'Opomba dodana', + nodeAdd: 'Vozlišče dodano', + nodeDragStop: 'Vozlišče je bilo premaknjeno', + stepBackward_one: '{{count}} korak nazaj', + stepForward_other: '{{count}} korakov naprej', + nodeDelete: 'Vozlišče je bilo izbrisano', + edgeDelete: 'Vozlišče je odklopljeno', + nodeResize: 'Vozlišče je spremenjeno velikost', + hint: 'Namig', + nodeDescriptionChange: 'Opis vozlišča je bil spremenjen', + noteDelete: 'Opomba izbrisana', currentState: 'Trenutno stanje', + nodeConnect: 'Povezana vozlišča', + nodeChange: 'Vozlišče se je spremenilo', + nodePaste: 'Vozlišče prilepljeno', + clearHistory: 'Počisti zgodovino', + hintText: 'Vaša dejanja urejanja so sledena v zgodovini sprememb, ki se hrani na vaši napravi za čas trajanja te seje. Ta zgodovina bo izbrisana, ko zapustite urejevalnik.', + placeholder: 'Še niste spremenili ničesar.', }, errorMsg: { fields: { - code: 'Koda', - variableValue: 'Vrednost spremenljivke', - visionVariable: 'Spremenljivka vida', + variableValue: 'Spremenljivka Vrednost', model: 'Model', - rerankModel: 'Ponovno razvrsti model', - variable: 'Ime spremenljivke', + variable: 'Spremenljivka Ime', + code: 'Koda', + rerankModel: 'Konfiguriran model ponovne uvrstitve', + visionVariable: 'Vizijska spremenljivka', }, - invalidJson: '{{field}} je neveljaven JSON', invalidVariable: 'Neveljavna spremenljivka', - authRequired: 'Dovoljenje je potrebno', - fieldRequired: '{{field}} je obvezno', - rerankModelRequired: 'Preden vklopite Rerank Model, preverite, ali je bil model uspešno konfiguriran v nastavitvah.', + noValidTool: '{{field}} ni izbranega veljavnega orodja', toolParameterRequired: '{{field}}: parameter [{{param}}] je obvezen', - noValidTool: '{{field}} Izbrano ni veljavno orodje', + rerankModelRequired: 'Zahteva se konfigurirana model ponovnega razvrščanja.', + authRequired: 'Zahtevana je avtorizacija', + invalidJson: '{{field}} je neveljaven JSON', + fieldRequired: '{{field}} je obvezno', }, singleRun: { - startRun: 'Začni zagnati', + iteration: 'Iteracija', + startRun: 'Začni zagon', + loop: 'Zanka', running: 'Tek', - testRunIteration: 'Ponovitev preskusnega zagona', - iteration: 'Ponovitev', - back: 'Hrbet', - testRun: 'Preskusni zagon', + testRunIteration: 'Testiranje ponovitve', + back: 'Nazaj', + testRun: 'Testna vožnja', }, tabs: { - 'blocks': 'Bloki', - 'workflowTool': 'Potek dela', - 'transform': 'Preoblikovanje', - 'question-understand': 'Vprašanje razumeti', - 'builtInTool': 'Vgrajeno', - 'allTool': 'Ves', - 'tools': 'Orodja', + 'customTool': 'Po meri', 'logic': 'Logika', - 'searchBlock': 'Iskalni blok', - 'noResult': 'Ni najdenega ujemanja', - 'customTool': 'Običaj', - 'utilities': 'Utilities', - 'searchTool': 'Orodje za iskanje', - 'agent': 'Strategija agenta', + 'tools': 'Orodja', + 'searchBlock': 'Išči vozlišče', + 'utilities': 'Komunalne storitve', 'plugin': 'Vtičnik', + 'allTool': 'Vse', + 'searchTool': 'Orodje za iskanje', + 'workflowTool': 'Delovni tok', + 'noResult': 'Ni bilo najdenih ujemanj', + 'transform': 'Pretvori', + 'blocks': 'Vozlišča', + 'question-understand': 'Vprašanje Razumevanje', + 'agent': 'Agentska strategija', }, blocks: { - 'variable-aggregator': 'Spremenljivi agregator', - 'code': 'Koda', - 'parameter-extractor': 'Ekstraktor parametrov', + 'iteration': 'Iteracija', + 'if-else': 'Če/Drugače', 'llm': 'LLM', - 'knowledge-retrieval': 'Pridobivanje znanja', - 'answer': 'Odgovoriti', - 'end': 'Konec', 'document-extractor': 'Ekstraktor dokumentov', - 'assigner': 'Dodeljevalnik spremenljivke', - 'iteration-start': 'Začetek ponovitve', + 'knowledge-retrieval': 'Pridobivanje znanja', + 'loop-start': 'Začetek zanke', + 'assigner': 'Dodeljevalec spremenljivk', + 'question-classifier': 'Razvrščevalec vprašanj', + 'start': 'Začni', + 'loop-end': 'Izhod iz zanke', + 'http-request': 'HTTP zahteva', + 'code': 'Koda', 'template-transform': 'Predloga', - 'iteration': 'Ponovitev', - 'start': 'Začetek', - 'if-else': 'IF/ELSE', - 'list-operator': 'Operater seznama', - 'http-request': 'Zahteva HTTP', - 'variable-assigner': 'Spremenljivi agregator', - 'question-classifier': 'Klasifikator vprašanj', + 'answer': 'Odgovor', + 'end': 'Konec', + 'iteration-start': 'Začetek iteracije', + 'list-operator': 'Seznam operater', + 'variable-aggregator': 'Spremenljivka agregator', + 'parameter-extractor': 'Ekstraktor parametrov', + 'loop': 'Zanka', 'agent': 'Agent', + 'variable-assigner': 'Spremenljivka agregator', }, blocksAbout: { - 'document-extractor': 'Uporablja se za razčlenjevanje naloženih dokumentov v besedilno vsebino, ki je zlahka razumljiva LLM.', - 'list-operator': 'Uporablja se za filtriranje ali razvrščanje vsebine matrike.', - 'template-transform': 'Pretvorite podatke v niz s sintakso predloge Jinja', - 'question-classifier': 'Določite pogoje razvrščanja uporabniških vprašanj, LLM lahko določi, kako poteka pogovor na podlagi opisa klasifikacije', - 'start': 'Določanje začetnih parametrov za zagon poteka dela', - 'if-else': 'Omogoča razdelitev poteka dela na dve veji glede na pogoje if/else', - 'knowledge-retrieval': 'Omogoča poizvedovanje po besedilni vsebini, ki je povezana z uporabniškimi vprašanji iz zbirke znanja', - 'variable-assigner': 'Združite spremenljivke z več vejami v eno spremenljivko za poenoteno konfiguracijo nadaljnjih vozlišč.', - 'code': 'Izvedite kodo Python ali NodeJS za izvajanje logike po meri', - 'answer': 'Določanje vsebine odgovora v pogovoru v klepetu', - 'iteration': 'Izvedite več korakov na predmetu seznama, dokler niso prikazani vsi rezultati.', - 'http-request': 'Dovoli pošiljanje zahtev strežnika prek protokola HTTP', - 'end': 'Določanje končne in končne vrste poteka dela', - 'variable-aggregator': 'Združite spremenljivke z več vejami v eno spremenljivko za poenoteno konfiguracijo nadaljnjih vozlišč.', - 'parameter-extractor': 'Uporabite LLM za pridobivanje strukturiranih parametrov iz naravnega jezika za klicanje orodij ali zahteve HTTP.', - 'assigner': 'Vozlišče za dodeljevanje spremenljivk se uporablja za dodeljevanje vrednosti zapisljivim spremenljivkam (kot so spremenljivke pogovora).', - 'llm': 'Sklicevanje na velike jezikovne modele za odgovarjanje na vprašanja ali obdelavo naravnega jezika', - 'agent': 'Sklicevanje na velike jezikovne modele za odgovarjanje na vprašanja ali obdelavo naravnega jezika', + 'list-operator': 'Uporabljeno za filtriranje ali razvrščanje vsebine polja.', + 'template-transform': 'Pretvori podatke v niz z uporabo Jinja predloge', + 'if-else': 'Omogoča vam, da razdelite delovni tok na dve veji na podlagi pogojev if/else.', + 'code': 'Izvedite kos Python ali NodeJS kode za izvajanje prilagojene logike.', + 'iteration': 'Izvedite več korakov na seznamu objektov, dokler niso vsi rezultati izpisani.', + 'loop-end': 'Enakovredno „prekini“. Ta vozlišče nima konfiguracijskih elementov. Ko telo zanke doseže to vozlišče, zanka preneha.', + 'document-extractor': 'Uporabljeno za razčlenitev prenesenih dokumentov v besedilno vsebino, ki jo je enostavno razumeti za LLM.', + 'answer': 'Določi vsebino odgovora v pogovoru.', + 'end': 'Določite tip konca in rezultata delovnega toka', + 'knowledge-retrieval': 'Omogoča vam, da poizvedujete o besedilnih vsebinah, povezanih z vprašanji uporabnikov iz znanja.', + 'http-request': 'Dovoli pošiljanje zahtevkov strežniku prek protokola HTTP', + 'llm': 'Uporaba velikih jezikovnih modelov za odgovarjanje na vprašanja ali obdelavo naravnega jezika', + 'loop': 'Izvedite zanko logike, dokler ni izpolnjen pogoj za prekinitev ali dokler ni dosežena največja število ponovitev.', + 'question-classifier': 'Določite pogoje klasifikacije uporabniških vprašanj, LLM lahko določi, kako se pogovor razvija na podlagi opisa klasifikacije.', + 'parameter-extractor': 'Uporabite LLM za pridobivanje strukturiranih parametrov iz naravnega jezika za klice orodij ali HTTP zahtev.', + 'agent': 'Uporaba velikih jezikovnih modelov za odgovarjanje na vprašanja ali obdelavo naravnega jezika', + 'start': 'Določite začetne parametre za zagon delovnega toka', + 'variable-assigner': 'Združite večpodružinske spremenljivke v eno samo spremenljivko za enotno konfiguracijo spodnjih vozlišč.', + 'variable-aggregator': 'Združite večpodružnične spremenljivke v eno samo spremenljivko za enotno konfiguracijo spodnjih vozlišč.', }, operator: { - zoomOut: 'Pomanjšanje', - zoomTo100: 'Povečava na 100 %', + zoomOut: 'Zoomirati ven', zoomToFit: 'Povečaj, da se prilega', - zoomIn: 'Povečava', - zoomTo50: 'Povečava na 50%', + zoomIn: 'Zoom in', + zoomTo50: 'Povečaj na 50%', + zoomTo100: 'Povečaj na 100%', + }, + variableReference: { + conversationVars: 'pogovorne spremenljivke', + assignedVarsDescription: 'Dodeljene spremenljivke morajo biti spremenljivke, ki jih je mogoče pisati, na primer', + noAvailableVars: 'Ni razpoložljivih spremenljivk.', + noAssignedVars: 'Nobenih dodeljenih spremenljivk ni na voljo.', + noVarsForOperation: 'Za izbrano operacijo ni nobenih spremenljivk, ki bi jih bilo mogoče dodeliti.', }, panel: { - helpLink: 'Povezava za pomoč', - organizeBlocks: 'Organiziranje blokov', - optional: '(neobvezno)', + change: 'Spremeni', + about: 'O tem', + userInputField: 'Uporabniško vhodno polje', nextStep: 'Naslednji korak', + runThisStep: 'Izvedi ta korak', + changeBlock: 'Spremeni vozlišče', + addNextStep: 'Dodajte naslednji korak v ta delovni potek', + moveToThisNode: 'Premakni se na to vozlišče', + checklistTip: 'Prepričajte se, da so vse težave rešene, preden objavite.', + selectNextStep: 'Izberi naslednji korak', + helpLink: 'Pomočna povezava', checklist: 'Kontrolni seznam', - runThisStep: 'Zaženite ta korak', - about: 'Približno', - selectNextStep: 'Izberite Naslednji blok', - changeBlock: 'Spremeni blok', - createdBy: 'Ustvaril', - checklistTip: 'Pred objavo se prepričajte, da so vse težave odpravljene', - userInputField: 'Uporabniško polje za vnos', - checklistResolved: 'Vse težave so odpravljene', - addNextStep: 'Dodajanje naslednjega bloka v ta potek dela', - change: 'Spremeniti', + checklistResolved: 'Vse težave so rešene', + createdBy: 'Ustvarjeno z', + organizeBlocks: 'Organizirajte vozlišča', }, nodes: { common: { memory: { - conversationRoleName: 'Ime vloge pogovora', - memoryTip: 'Nastavitve pomnilnika klepeta', - assistant: 'Predpona pomočnika', - user: 'Uporabniška predpona', + user: 'Uporabniški predpon', + assistant: 'Pomagalec predpona', memory: 'Spomin', + conversationRoleName: 'Ime vloge v pogovoru', + memoryTip: 'Nastavitve spomina za klepet', windowSize: 'Velikost okna', }, memories: { tip: 'Pomnilnik klepeta', - title: 'Spomine', + title: 'Spomini', builtIn: 'Vgrajeno', }, - outputVars: 'Izhodne spremenljivke', - insertVarTip: 'Vstavi spremenljivko', errorHandle: { none: { - desc: 'Vozlišče se bo prenehalo izvajati, če pride do izjeme in ne bo obravnavano', - title: 'Nobena', + title: 'Noben', + desc: 'Vozlišče se bo prenehalo izvajati, če pride do izjeme, ki ni obravnavana.', }, defaultValue: { - output: 'Izhodna privzeta vrednost', - tip: 'Po napaki se vrne pod vrednost.', + output: 'Privzeta vrednost izhoda', + inLog: 'Napaka vozlišča, izhod po privzetih vrednostih.', title: 'Privzeta vrednost', - inLog: 'Izjema vozlišča, izhod v skladu s privzetimi vrednostmi.', - desc: 'Ko pride do napake, določite statično izhodno vsebino.', + desc: 'Ko pride do napake, določi statično vsebino izhoda.', + tip: 'Ob napaki bo vrnil spodnjo vrednost.', }, failBranch: { - title: 'Neuspešna veja', - inLog: 'Izjema vozlišča, bo samodejno izvedla neuspešno vejo. Izhod vozlišča bo vrnil vrsto napake in sporočilo o napaki ter ju posredoval navzdol.', - desc: 'Ko pride do napake, bo izvedla vejo izjeme', - customizeTip: 'Ko je aktivirana veja neuspeha, izjeme, ki jih vržejo vozlišča, ne bodo prekinile procesa. Namesto tega bo samodejno izvedel vnaprej določeno vejo neuspeha, kar vam bo omogočilo prilagodljivo zagotavljanje sporočil o napakah, poročil, popravkov ali preskakovanja dejanj.', - customize: 'Pojdite na platno, da prilagodite logiko neuspešne veje.', + title: 'Napaka veja', + customize: 'Pojdite na platno, da prilagodite logiko veje neuspeha.', + desc: 'Ko pride do napake, se bo izvedla veja izjeme.', + inLog: 'Napaka na vozlišču, samodejno se bo izvedla veja za neuspeh. Izhod vozlišča bo vrnil tip napake in sporočilo o napaki ter ju posredoval naprej.', + customizeTip: 'Ko je aktivirana veja napak, izjeme, ki jih sprožijo vozlišča, ne bodo prekinile procesa. Namesto tega bo samodejno izvedena vnaprej določena veja napak, kar vam omogoča, da prilagodljivo ponudite sporočila o napakah, poročila, popravke ali preskočite dejanja.', }, partialSucceeded: { - tip: 'V procesu so {{num}} vozlišča, ki se izvajajo nenormalno, pojdite na sledenje, da preverite dnevnike.', + tip: 'V procesu je {{num}} vozlišč, ki delujejo nenormalno, prosim, pojdite na sledenje, da preverite dnevnike.', }, - title: 'Ravnanje z napakami', - tip: 'Strategija ravnanja z izjemami, ki se sproži, ko vozlišče naleti na izjemo.', + title: 'Obvladovanje napak', + tip: 'Strategija ravnanja z izjemo, ki se sproži, ko vozlišče naleti na izjemo.', }, retry: { - retryOnFailure: 'Ponovni poskus ob neuspehu', - retryInterval: 'Interval ponovnega poskusa', - retrying: 'Ponovnim...', - retry: 'Ponoviti', - retryFailedTimes: '{{times}} ponovni poskusi niso uspeli', - retries: '{{num}} Poskusov', - times: 'Krat', - retryTimes: 'Ponovni poskus {{times}}-krat ob neuspehu', - retryFailed: 'Ponovni poskus ni uspel', retrySuccessful: 'Ponovni poskus je bil uspešen', - maxRetries: 'Največ ponovnih poskusov', - ms: 'Ms', - }, + retryFailedTimes: '{{times}} poskusi so spodleteli', + maxRetries: 'maksimalno število poskusov', + ms: 'ms', + retrying: 'Ponovno poskušam...', + times: 'časi', + retry: 'Poskusi znova', + retryFailed: 'Ponovi neuspeh', + retryOnFailure: 'poskusi znova v primeru napake', + retryInterval: 'ponovni interval', + retries: '{{num}} Poskusi', + retryTimes: 'Poskusite {{times}} krat v primeru napake', + }, + insertVarTip: 'Vstavite spremenljivko', + outputVars: 'Izhodne spremenljivke', }, start: { outputVars: { memories: { - content: 'Vsebina sporočila', - des: 'Zgodovina pogovorov', - type: 'Vrsta sporočila', + des: 'Zgodovina pogovora', + type: 'vrsta sporočila', + content: 'vsebina sporočila', }, - query: 'Uporabniški vnos', files: 'Seznam datotek', + query: 'Uporabniški vnos', }, - required: 'Zahteva', - inputField: 'Vnosno polje', - noVarTip: 'Nastavitev vhodov, ki jih je mogoče uporabiti v poteku dela', + noVarTip: 'Nastavite vhodne podatke, ki jih lahko uporabite v delovnem toku.', + required: 'zahtevano', builtInVar: 'Vgrajene spremenljivke', + inputField: 'Vnosno polje', }, end: { output: { + type: 'izhodna vrsta', variable: 'izhodna spremenljivka', - type: 'Vrsta izhoda', }, type: { - 'structured': 'Strukturiran', + 'structured': 'Strukturirano', + 'none': 'Noben', 'plain-text': 'Navadno besedilo', - 'none': 'Nobena', }, - outputs: 'Izhodov', + outputs: 'Izhodi', }, answer: { - answer: 'Odgovoriti', outputVars: 'Izhodne spremenljivke', + answer: 'Odgovor', }, llm: { roleDescription: { - assistant: 'Odgovori modela na podlagi sporočil uporabnikov', - system: 'Podajte navodila na visoki ravni za pogovor', - user: 'Navedite navodila, poizvedbe ali kakršen koli besedilni vnos v model', + system: 'Dajte visoko raven navodil za pogovor.', + assistant: 'Odgovori modela so zasnovani na uporabnikovih sporočilih.', + user: 'Navedite navodila, poizvedbe ali kakršnokoli besedilne vnose modelu', }, resolution: { - low: 'Nizek', - high: 'Visok', + low: 'Nizko', + high: 'Visoko', name: 'Resolucija', }, outputVars: { + output: 'Ustvari vsebino', usage: 'Informacije o uporabi modela', - output: 'Ustvarjanje vsebine', }, singleRun: { variable: 'Spremenljivka', }, - notSetContextInPromptTip: 'Če želite omogočiti funkcijo konteksta, izpolnite kontekstno spremenljivko v PROMPT.', - sysQueryInUser: 'sys.query v sporočilu uporabnika je obvezen', - model: 'model', + jsonSchema: { + warningTips: { + saveSchema: 'Prosimo, da dokončate urejanje trenutnega polja, preden shranite shemo.', + }, + generatedResult: 'Generiran rezultat', + instruction: 'Navodilo', + resetDefaults: 'Ponastavi', + promptPlaceholder: 'Opiši svoj JSON shemo...', + generating: 'Generiranje JSON sheme...', + resultTip: 'Tukaj je generiran rezultat. Če niste zadovoljni, se lahko vrnete in spremenite svoj poziv.', + promptTooltip: 'Pretvorite besedilni opis v standardizirano strukturo JSON sheme.', + addField: 'Dodaj polje', + fieldNamePlaceholder: 'Ime polja', + import: 'Uvoz iz JSON', + generationTip: 'Lahko uporabite naravni jezik za hitro ustvarjanje JSON sheme.', + back: 'Nazaj', + descriptionPlaceholder: 'Dodajte opis', + generate: 'Generirati', + doc: 'Izvedite več o strukturiranem izhodu', + title: 'Strukturirana izhodna shema', + required: 'zahtevano', + apply: 'Uporabi', + generateJsonSchema: 'Generiraj JSON shemo', + addChildField: 'Dodaj polje za otroka', + showAdvancedOptions: 'Prikaži napredne možnosti', + stringValidations: 'Preverjanje nizov', + regenerate: 'Ponovno generiranje', + }, + prompt: 'ukaz', + sysQueryInUser: 'vprašanje v uporabniškem sporočilu je obvezno', + notSetContextInPromptTip: 'Da omogočite funkcijo konteksta, prosimo izpolnite spremenljivko konteksta v PROMPT.', + contextTooltip: 'Lahko uvažate znanje kot kontekst', + variables: 'spremenljivke', files: 'Datoteke', + model: 'model', + context: 'kontekst', addMessage: 'Dodaj sporočilo', - context: 'Kontekstu', - variables: 'Spremenljivke', - prompt: 'Uren', - vision: 'vid', - contextTooltip: 'Znanje lahko uvozite kot kontekst', + vision: 'vizija', }, knowledgeRetrieval: { outputVars: { - title: 'Segmentirani naslov', - url: 'Segmentirani URL', - output: 'Pridobivanje segmentiranih podatkov', + title: 'Segmentirana naslov', + url: 'Segmentirana URL', icon: 'Segmentirana ikona', - metadata: 'Drugi metapodatki', content: 'Segmentirana vsebina', + metadata: 'Drug metapodatki', + output: 'Podatki o segmentaciji iskanja', + }, + metadata: { + options: { + disabled: { + title: 'Onemogočeno', + subTitle: 'Ne omogočanje filtriranja metapodatkov', + }, + automatic: { + title: 'Samodejno', + subTitle: 'Samodejno ustvarite filtrirne pogoje za metapodatke na podlagi uporabniškega poizvedovanja.', + desc: 'Samodejno ustvarite filtrirne pogoje za metapodatke na podlagi spremenljivke poizvedbe', + }, + manual: { + subTitle: 'Ročno dodajte pogoje za filtriranje metapodatkov', + title: 'Ročno', + }, + }, + panel: { + title: 'Pogoji za filtriranje metapodatkov', + conditions: 'Pogoji', + placeholder: 'Vnesite vrednost', + search: 'Išči metapodatke', + select: 'Izberi spremenljivko...', + datePlaceholder: 'Izberi čas...', + add: 'Dodaj pogoj', + }, + title: 'Filtriranje metapodatkov', }, - queryVariable: 'Spremenljivka poizvedbe', + queryVariable: 'Vprašanje spremenljivka', knowledge: 'Znanje', }, http: { outputVars: { - headers: 'JSON seznama glav odgovorov', - body: 'Vsebina odgovora', files: 'Seznam datotek', - statusCode: 'Koda stanja odgovora', + body: 'Vsebina odziva', + headers: 'Seznam odzivnih glav JSON', + statusCode: 'Statusna koda odgovora', }, authorization: { - 'authorization': 'Dovoljenje', - 'header': 'Glava', - 'bearer': 'Nosilec', + 'no-auth': 'Noben', + 'custom': 'Po meri', + 'header': 'Naslov', + 'bearer': 'Nosilac', 'api-key-title': 'API ključ', - 'basic': 'Osnoven', - 'no-auth': 'Nobena', - 'custom': 'Običaj', - 'authorizationType': 'Vrsta dovoljenja', - 'auth-type': 'Vrsta preverjanja pristnosti', - 'api-key': 'Ključ API-ja', + 'authorization': 'Avtorizacija', + 'api-key': 'API-kljuc', + 'basic': 'Osnovno', + 'auth-type': 'Vrsta avtentikacije', + 'authorizationType': 'Vrsta pooblastila', }, timeout: { - readPlaceholder: 'Vnos časovne omejitve branja v sekundah', - writePlaceholder: 'Vnesite časovno omejitev pisanja v sekundah', - writeLabel: 'Časovna omejitev pisanja', - connectLabel: 'Časovna omejitev povezave', - title: 'Timeout', readLabel: 'Časovna omejitev branja', - connectPlaceholder: 'Vnos časovne omejitve povezave v sekundah', + title: 'Časovna omejitev', + readPlaceholder: 'Vnesite časovne omejitve za branje v sekundah', + connectPlaceholder: 'Vnesite časovne omejitve povezave v sekundah', + connectLabel: 'Čas povezave je potekel', + writePlaceholder: 'Vnesite časovne omejitve za pisanje v sekundah', + writeLabel: 'Časovna omejitev pisanja', + }, + curl: { + title: 'Uvozi iz cURL', + placeholder: 'Prilepite cURL niz tukaj', }, - value: 'Vrednost', - key: 'Ključ', - notStartWithHttp: 'API se mora začeti z http:// ali https://', body: 'Telo', - type: 'Vrsta', inputVars: 'Vhodne spremenljivke', - bulkEdit: 'Urejanje v velikem obsegu', - insertVarPlaceholder: 'vnesite "/" za vstavljanje spremenljivke', + apiPlaceholder: 'Vnesite URL, vtipkajte \'/\' in vstavite spremenljivko.', api: 'API', + extractListPlaceholder: 'Vnesite indeks seznamske postavke, vnesite \'/\' za vstavitev spremenljivke', + key: 'Ključ', + binaryFileVariable: 'Dvojiška datoteka spremenljivka', + notStartWithHttp: 'API se mora začeti z http:// ali https://', keyValueEdit: 'Urejanje ključ-vrednost', - binaryFileVariable: 'Spremenljivka binarne datoteke', - headers: 'Glave', - apiPlaceholder: 'Vnesite URL, vnesite \'/\' vstavi spremenljivko', - extractListPlaceholder: 'Vnesite indeks elementa seznama, vnesite \'/\' vstavi spremenljivko', - params: 'Params', - curl: { - title: 'Uvoz iz cURL', - placeholder: 'Tukaj prilepite niz cURL', - }, + bulkEdit: 'Masovno urejanje', + type: 'Tip', + headers: 'Naslovi', + value: 'Vrednost', + params: 'Parametri', + insertVarPlaceholder: 'vnesite \'/\' za vstavljanje spremenljivke', }, code: { - inputVars: 'Vhodne spremenljivke', - outputVars: 'Izhodne spremenljivke', - searchDependencies: 'Odvisnosti iskanja', - advancedDependenciesTip: 'Tukaj dodajte nekaj vnaprej naloženih odvisnosti, ki trajajo dlje časa ali niso privzeto vgrajene', + searchDependencies: 'Išči odvisnosti', advancedDependencies: 'Napredne odvisnosti', + outputVars: 'Izhodne spremenljivke', + inputVars: 'Vhodne spremenljivke', + advancedDependenciesTip: 'Dodajte nekaj vnaprej naloženih odvisnosti, ki potrebujejo več časa za obdelavo ali niso privzete vgrajene.', }, templateTransform: { outputVars: { - output: 'Preoblikovana vsebina', + output: 'Transformirana vsebina', }, + codeSupportTip: 'Podpira samo Jinja2', code: 'Koda', inputVars: 'Vhodne spremenljivke', - codeSupportTip: 'Podpira samo Jinja2', }, ifElse: { comparisonOperator: { - 'all of': 'vse', + 'all of': 'vse od', + 'not in': 'ni v', + 'in': 'v', + 'null': 'je nič', + 'after': 'po', + 'is': 'je', + 'not exists': 'ne obstaja', + 'empty': 'je prazno', 'is not': 'ni', + 'start with': 'začeti z', 'not empty': 'ni prazen', - 'start with': 'Začnite z', - 'is': 'Je', - 'null': 'je nična', - 'not exists': 'ne obstaja', - 'contains': 'Vsebuje', - 'empty': 'je prazen', - 'exists': 'Obstaja', - 'in': 'v', + 'before': 'pred', + 'end with': 'končati z', 'not contains': 'ne vsebuje', - 'end with': 'Končaj z', - 'not in': 'ni v', - 'not null': 'ni nična', + 'contains': 'vsebuje', + 'exists': 'obstaja', + 'not null': 'ni ničelno', }, optionName: { - video: 'Video', - doc: 'Doc', - audio: 'Avdio', - image: 'Podoba', - url: 'Spletni naslov', localUpload: 'Lokalno nalaganje', + video: 'Video', + url: 'URL', + image: 'Slika', + doc: 'Dokument', + audio: 'Zvočni zapis', }, + addCondition: 'Dodaj pogoj', + selectVariable: 'Izberi spremenljivko...', + or: 'ali', + if: 'Če', and: 'in', - else: 'Drugega', + else: 'Drugje', + notSetVariable: 'Prosim, najprej nastavite spremenljivko.', enterValue: 'Vnesite vrednost', - elseDescription: 'Uporablja se za določanje logike, ki jo je treba izvesti, ko pogoj if ni izpolnjen.', - addCondition: 'Dodajanje pogoja', - if: 'Če', - select: 'Izbrati', - selectVariable: 'Izberite spremenljivko ...', - conditionNotSetup: 'Pogoj NI nastavljen', + elseDescription: 'Uporabljeno za opredelitev logike, ki se izvede, ko pogoj if ni izpolnjen.', addSubVariable: 'Podspremenljivka', - notSetVariable: 'Prosimo, najprej nastavite spremenljivko', - operator: 'Operaterja', - or: 'ali', + conditionNotSetup: 'Pogoji NISO nastavljeni', + operator: 'Operater', + select: 'Izberite', }, variableAssigner: { type: { - string: 'Niz', object: 'Predmet', - array: 'Matrika', - number: 'Številka', + string: 'Niz', + number: 'Število', + array: 'Množica', }, outputVars: { varDescribe: '{{groupName}} izhod', }, - addGroup: 'Dodajanje skupine', - outputType: 'Vrsta izhoda', - title: 'Dodeljevanje spremenljivk', - noVarTip: 'Seštevanje spremenljivk, ki jih je treba dodeliti', - aggregationGroupTip: 'Če omogočite to funkcijo, lahko združevalnik spremenljivk združi več naborov spremenljivk.', - aggregationGroup: 'Združevalna skupina', varNotSet: 'Spremenljivka ni nastavljena', - setAssignVariable: 'Nastavitev spremenljivke dodelitve', + title: 'Dodelite spremenljivke', + noVarTip: 'Dodajte spremenljivke, ki jih je treba dodeliti.', + aggregationGroup: 'Agregacijska skupina', + outputType: 'Vrsta izhoda', + addGroup: 'Dodaj skupino', + setAssignVariable: 'Določite spremenljivko', + aggregationGroupTip: 'Omogočanje te funkcije omogoča agregatorju spremenljivk, da združi več skupin spremenljivk.', }, assigner: { - 'writeMode': 'Način pisanja', - 'plus': 'Plus', - 'variable': 'Spremenljivka', - 'clear': 'Jasen', - 'append': 'Dodaj', - 'assignedVariable': 'Dodeljena spremenljivka', - 'setVariable': 'Nastavi spremenljivko', - 'over-write': 'Prepisati', - 'writeModeTip': 'Način dodajanja: Na voljo samo za spremenljivke polja.', 'operations': { - '+=': '+=', - 'overwrite': 'Prepisati', - '*=': '*=', - 'extend': 'Razširiti', + 'set': 'Nabor', 'append': 'Dodaj', + '/=': '/=', + 'over-write': 'Prepiši', + '*=': '*=', + 'remove-first': 'Odstrani prvi', + 'remove-last': 'Odstrani zadnje', '-=': '-=', + '+=': '+=', + 'extend': 'Razširi', + 'clear': 'Jasno', + 'overwrite': 'Prepiši', 'title': 'Operacija', - '/=': '/=', - 'set': 'Nastaviti', - 'clear': 'Jasen', - 'over-write': 'Prepisati', }, + 'clear': 'Jasno', + 'plus': 'Plus', + 'noAssignedVars': 'Nobenih dodeljenih spremenljivk ni na voljo.', 'variables': 'Spremenljivke', - 'selectAssignedVariable': 'Izberite dodeljeno spremenljivko ...', - 'assignedVarsDescription': 'Dodeljene spremenljivke morajo biti zapisljive, kot so spremenljivke pogovora.', - 'noVarTip': 'Kliknite gumb »+«, da dodate spremenljivke', - 'noAssignedVars': 'Ni razpoložljivih dodeljenih spremenljivk', + 'assignedVariable': 'Dodeljena spremenljivka', + 'writeMode': 'Načrtovanje pisanja', + 'setParameter': 'Nastavite parameter...', + 'writeModeTip': 'Način dodajanja: Na voljo samo za spremenljivke tipa tabel.', + 'over-write': 'Prepiši', + 'append': 'Dodaj', 'varNotSet': 'Spremenljivka NI nastavljena', - 'setParameter': 'Nastavi parameter ...', + 'noVarTip': 'Kliknite na gumb " + " za dodajanje spremenljivk', + 'variable': 'Spremenljivka', + 'assignedVarsDescription': 'Dodeljene spremenljivke morajo biti spremenljivke, ki jih je mogoče pisati, kot so spremenljivke za pogovor.', + 'setVariable': 'Nastavi spremenljivko', + 'selectAssignedVariable': 'Izberite dodeljeno spremenljivko...', }, tool: { outputVars: { files: { - transfer_method: 'Način prenosa. Vrednost je remote_url ali local_file', - upload_file_id: 'Naloži ID datoteke', - type: 'Vrsta podpore. Zdaj podpiramo samo sliko', + transfer_method: 'Metoda prenosa. Vrednost je remote_url ali local_file.', + title: 'orodja ustvarjena datoteke', + upload_file_id: 'Naložite ID datoteke', url: 'URL slike', - title: 'Datoteke, ustvarjene z orodjem', + type: 'Vrsta podpore. Zdaj podpiramo samo slike.', }, - json: 'JSON, ustvarjen z orodjem', - text: 'Vsebina, ustvarjena z orodjem', + text: 'vsebina, ki jo je generiral orodje', + json: 'orodje je ustvarilo json', }, inputVars: 'Vhodne spremenljivke', - toAuthorize: 'Za odobritev', + authorize: 'Pooblasti', }, questionClassifiers: { outputVars: { className: 'Ime razreda', }, instruction: 'Navodilo', - classNamePlaceholder: 'Napišite ime svojega razreda', - addClass: 'Dodajanje razreda', - instructionPlaceholder: 'Napišite navodila', - topicName: 'Ime teme', - topicPlaceholder: 'Napišite ime teme', + addClass: 'Dodaj razred', class: 'Razred', - advancedSetting: 'Napredne nastavitve', model: 'model', + topicPlaceholder: 'Napišite ime svoje teme', + topicName: 'Ime teme', + instructionTip: 'Vnesite dodatna navodila, ki bodo pomagala klasifikatorju vprašanj bolje razumeti, kako kategorizirati vprašanja.', inputVars: 'Vhodne spremenljivke', - instructionTip: 'Vnesite dodatna navodila, ki bodo klasifikatorju vprašanj pomagala bolje razumeti, kako kategorizirati vprašanja.', + classNamePlaceholder: 'Napiši ime svoje razredi', + advancedSetting: 'Napredno nastavitev', + instructionPlaceholder: 'Napišite svoje navodilo', }, parameterExtractor: { addExtractParameterContent: { + required: 'Zahtevano', description: 'Opis', - typePlaceholder: 'Vrsta parametra izvlečka', - requiredContent: 'Zahtevano se uporablja samo kot referenca za sklepanje modela in ne za obvezno validacijo izhodnega parametra.', - required: 'Zahteva', - type: 'Vrsta', - namePlaceholder: 'Izvleček imena parametra', - descriptionPlaceholder: 'Opis parametra izvlečka', name: 'Ime', + descriptionPlaceholder: 'Izvleči opis parametra', + namePlaceholder: 'Izvleči ime parametra', + type: 'Tip', + typePlaceholder: 'Izvleči vrsto parametra', + requiredContent: 'Zahtevano se uporablja le kot referenca za sklepanje modela in ne kot obvezno validacijo izhodnih parametrov.', }, - isSuccess: 'Je uspeh.Pri uspehu je vrednost 1, pri neuspehu je vrednost 0.', - addExtractParameter: 'Dodajanje parametra izvlečka', - importFromTool: 'Uvoz iz orodij', - reasoningModeTip: 'Izberete lahko ustrezen način sklepanja glede na sposobnost modela, da se odzove na navodila za klicanje funkcij ali pozive.', - inputVar: 'Vhodna spremenljivka', - advancedSetting: 'Napredne nastavitve', + extractParameters: 'Izvleči parametre', errorReason: 'Razlog za napako', - reasoningMode: 'Način sklepanja', instruction: 'Navodilo', - instructionTip: 'Vnesite dodatna navodila, ki bodo ekstraktorju parametrov pomagala razumeti, kako izvleči parametre.', - extractParametersNotSet: 'Izvleček parametrov ni nastavljen', - extractParameters: 'Izvleček parametrov', + instructionTip: 'Vnesite dodatna navodila, da pomagate izvleku parametrov razumeti, kako izvleči parametre.', + reasoningMode: 'Način razmišljanja', + isSuccess: 'Ali je uspeh. Na uspehu je vrednost 1, na neuspehu je vrednost 0.', + importFromTool: 'Uvoz iz orodij', + advancedSetting: 'Napredno nastavitev', + addExtractParameter: 'Dodaj parameter za ekstrakcijo', + extractParametersNotSet: 'Parameterji za ekstrakcijo niso nastavljeni', + inputVar: 'Vhodna spremenljivka', + reasoningModeTip: 'Lahko izberete ustrezen način razmišljanja glede na sposobnost modela, da se odzove na navodila za klic funkcij ali pozive.', }, iteration: { ErrorMethod: { - continueOnError: 'Nadaljuj ob napaki', - removeAbnormalOutput: 'Odstranite nenormalen izhod', - operationTerminated: 'Prekinjena', + operationTerminated: 'Prekinjeno', + removeAbnormalOutput: 'Odstrani nenavadne izhode', + continueOnError: 'Nadaljuj naprej kljub napaki', }, + errorResponseMethod: 'Metoda odziva napake', + parallelModeEnableTitle: 'Paralelni način vključen', output: 'Izhodne spremenljivke', - parallelMode: 'Vzporedni način', - MaxParallelismTitle: 'Največji vzporednost', - errorResponseMethod: 'Način odziva na napako', - parallelModeEnableDesc: 'V vzporednem načinu opravila v iteracijah podpirajo vzporedno izvajanje. To lahko konfigurirate na plošči z lastnostmi na desni.', - error_one: '{{štetje}} Napaka', + MaxParallelismTitle: 'Maksimalno paralelizem', + currentIteration: 'Trenutna iteracija', + error_other: '{{count}} Napak', comma: ',', - parallelModeUpper: 'VZPOREDNI NAČIN', - parallelModeEnableTitle: 'Vzporedni način omogočen', - currentIteration: 'Trenutna ponovitev', - error_other: '{{štetje}} Napake', - input: 'Vhodni', - deleteTitle: 'Izbrisati iteracijsko vozlišče?', - parallelPanelDesc: 'V vzporednem načinu opravila v iteraciji podpirajo vzporedno izvajanje.', - deleteDesc: 'Če izbrišete iteracijsko vozlišče, boste izbrisali vsa podrejena vozlišča', - iteration_other: '{{štetje}} Ponovitev', - answerNodeWarningDesc: 'Opozorilo vzporednega načina: Vozlišča za odgovore, dodelitve spremenljivk pogovora in trajne operacije branja / pisanja v iteracijah lahko povzročijo izjeme.', - MaxParallelismDesc: 'Največja vzporednost se uporablja za nadzor števila nalog, ki se izvajajo hkrati v eni ponovitvi.', - iteration_one: '{{štetje}} Ponovitev', + iteration_one: '{{count}} Iteracija', + parallelMode: 'Paralelni način', + error_one: '{{count}} Napaka', + deleteTitle: 'Izbriši vozlišče ponovitve?', + iteration_other: '{{count}} ponovitev', + input: 'Vnos', + answerNodeWarningDesc: 'Opozorilo o paralelnem načinu: Odgovorni vozli, dodelitve spremenljivk v pogovorih in trajne operacije branja/pisanja znotraj iteracij lahko povzročijo izjeme.', + parallelModeUpper: 'PARALELNO MODE', + MaxParallelismDesc: 'Največje paralelizem se uporablja za nadzorovanje števila nalog, ki se izvajajo hkrati v eni iteraciji.', + deleteDesc: 'Izbris iteracijskega vozlišča bo izbrisal vsa otroška vozlišča.', + parallelModeEnableDesc: 'V vzporednem načinu naloge znotraj iteracij podpirajo vzporedno izvajanje. To lahko nastavite v razdelku lastnosti na desni strani.', + parallelPanelDesc: 'V paralelnem načinu naloge v iteraciji podpirajo parallelno izvajanje.', + }, + loop: { + ErrorMethod: { + removeAbnormalOutput: 'Odstrani nenavadne izhode', + operationTerminated: 'Prekinjeno', + continueOnError: 'Nadaljuj naprej kljub napaki', + }, + loop_one: '{{count}} Zanka', + loop_other: '{{count}} Zavoji', + input: 'Vnos', + errorResponseMethod: 'Metoda odziva napake', + output: 'Izhodna spremenljivka', + loopMaxCount: 'Maksimalno število zank', + loopVariables: 'Zanke Spremenljivke', + comma: ',', + currentLoop: 'Trenutni obrat', + currentLoopCount: 'Trenutno število zank: {{count}}', + deleteTitle: 'Izbriši vozlišče zanke?', + loopNode: 'Ciklični vozlišče', + inputMode: 'Vnosni način', + variableName: 'Spremenljivka Ime', + exitConditionTip: 'Vozić potrebuje vsaj eno izhodno pogoj.', + finalLoopVariables: 'Končne zanke spremenljivke', + deleteDesc: 'Izbris vozlišča zanke bo odstranil vse otroške vozlišča.', + breakCondition: 'Pogoji za prekinitev zanke', + error_one: '{{count}} Napaka', + error_other: '{{count}} Napak', + setLoopVariables: 'Nastavite spremenljivke znotraj obsega zanke', + totalLoopCount: 'Skupno število zank: {{count}}', + initialLoopVariables: 'Začetne spremenljivke zanke', + breakConditionTip: 'Lahko se sklicujete le na spremenljivke znotraj zank z zaključnimi pogoji in pogovorne spremenljivke.', + loopMaxCountError: 'Prosimo, vnesite veljavno največje število ponovitev, ki mora biti med 1 in {{maxCount}}', }, note: { editor: { - medium: 'Srednja', - openLink: 'Odprt', - showAuthor: 'Pokaži avtorja', - bold: 'Smel', - strikethrough: 'Prečrtano', + bold: 'Poudarjeno', + medium: 'Srednje', large: 'Velik', link: 'Povezava', - enterUrl: 'Vnesite URL ...', + enterUrl: 'Vnesite URL...', small: 'Majhen', - italic: 'Ležeče', - invalidUrl: 'Neveljaven URL', - unlink: 'Prekini povezavo', - placeholder: 'Napišite svojo opombo ...', - bulletList: 'Seznam oznak', + bulletList: 'Seznam s puščicami', + unlink: 'Odstrani povezavo', + italic: 'Italika', + placeholder: 'Napiši svojo opombo...', + openLink: 'Odprto', + showAuthor: 'Prikaži avtorja', + strikethrough: 'Prečrtano', + invalidUrl: 'Nesprejemljiv URL', }, addNote: 'Dodaj opombo', }, docExtractor: { outputVars: { - text: 'Izvlečeno besedilo', + text: 'Izvlečene besedilo', }, + supportFileTypes: 'Podpora za vrste datotek: {{types}}.', + learnMore: 'Nauči se več', inputVar: 'Vhodna spremenljivka', - learnMore: 'Izvedi več', - supportFileTypes: 'Podporne vrste datotek: {{types}}.', }, listFilter: { outputVars: { - result: 'Rezultat filtriranja', first_record: 'Prvi zapis', last_record: 'Zadnji zapis', + result: 'Filtriraj rezultat', }, - extractsCondition: 'Ekstrahiranje elementa N', - selectVariableKeyPlaceholder: 'Izberite ključ podspremenljivke', - asc: 'ASC', - orderBy: 'Naročite po', - filterCondition: 'Pogoj filtra', filterConditionKey: 'Ključ pogoja filtra', + asc: 'ASC', + filterConditionComparisonOperator: 'Operator za primerjavo filtrovanja pogojev', + selectVariableKeyPlaceholder: 'Izberi podključ spremenljivke', + limit: 'Najboljši N', + filterConditionComparisonValue: 'Vrednost pogoja filtra', desc: 'DESC', - limit: 'Vrh N', - filterConditionComparisonOperator: 'Operator za primerjavo pogojev filtra', inputVar: 'Vhodna spremenljivka', - filterConditionComparisonValue: 'Vrednost pogoja filtra', + orderBy: 'Naroči po', + extractsCondition: 'Izvleči N predmet', + filterCondition: 'Filtrirni pogoj', }, agent: { strategy: { - configureTipDesc: 'Po konfiguraciji agentske strategije bo to vozlišče samodejno naložilo preostale konfiguracije. Strategija bo vplivala na mehanizem sklepanja z orodji v več korakih.', - tooltip: 'Različne agentske strategije določajo, kako sistem načrtuje in izvaja klice orodij v več korakih', + configureTip: 'Prosimo, konfigurirajte agentno strategijo.', + selectTip: 'Izberite agencijsko strategijo', + searchPlaceholder: 'Išči agentno strategijo', + label: 'Agentična strategija', shortLabel: 'Strategija', - configureTip: 'Prosimo, konfigurirajte agentsko strategijo.', - searchPlaceholder: 'Strategija iskalnega agenta', - label: 'Agentska strategija', - selectTip: 'Izberite agentsko strategijo', + configureTipDesc: 'Po nastavitvi agentne strategije bo ta vozlišče samodejno naložilo preostale nastavitve. Strategija bo vplivala na mehanizem večstopenjskega razmišljanja o orodju.', + tooltip: 'Različne agentne strategije določajo, kako sistem načrtuje in izvaja večstopenjske klice orodij.', }, pluginInstaller: { installing: 'Namestitev', - install: 'Namestiti', + install: 'Namestite', }, modelNotInMarketplace: { - desc: 'Ta model je nameščen iz lokalnega skladišča ali skladišča GitHub. Uporabite po namestitvi.', title: 'Model ni nameščen', manageInPlugins: 'Upravljanje v vtičnikih', + desc: 'Ta model je nameščen iz lokalnega ali GitHub repozitorija. Prosimo, uporabite ga po namestitvi.', }, modelNotSupport: { - descForVersionSwitch: 'Nameščena različica vtičnika ne zagotavlja tega modela. Kliknite, če želite preklopiti med različico.', - title: 'Nepodprt model', - desc: 'Nameščena različica vtičnika ne zagotavlja tega modela.', + title: 'Nepodprti model', + desc: 'V različici vtičnika, ki je nameščena, ta model ni na voljo.', + descForVersionSwitch: 'Nameščena različica vtičnika ne podpira tega modela. Kliknite za preklop na drugo različico.', }, modelSelectorTooltips: { - deprecated: 'Ta model je zastarel', + deprecated: 'Ta model je zastarelo', }, outputVars: { files: { + type: 'Vrsta podpore. Zdaj podpiramo samo slike.', + upload_file_id: 'Naložite ID datoteke', + title: 'datoteke, ki jih je ustvaril agent', url: 'URL slike', - title: 'Datoteke, ki jih ustvari agent', - type: 'Vrsta podpore. Zdaj podpiramo samo sliko', - upload_file_id: 'Naloži ID datoteke', - transfer_method: 'Način prenosa. Vrednost je remote_url ali local_file', }, - json: 'JSON, ustvarjen z agentom', - text: 'Vsebina, ki jo ustvari agent', + json: 'agent generiran json', + text: 'vsebina, ki jo je ustvaril agent', }, checkList: { strategyNotSelected: 'Strategija ni izbrana', }, installPlugin: { - cancel: 'Odpovedati', - changelog: 'Dnevnik sprememb', - install: 'Namestiti', - title: 'Namesti vtičnik', - desc: 'O namestitvi naslednjega vtičnika', - }, - strategyNotSet: 'Agentska strategija ni določena', - modelNotSelected: 'Model ni izbran', - pluginNotInstalled: 'Ta vtičnik ni nameščen', - toolNotAuthorizedTooltip: '{{orodje}} Ni pooblaščeno', - toolbox: 'Orodjarni', - tools: 'Orodja', + desc: 'Namestitev naslednjega vtičnika', + title: 'Namestite vtičnik', + changelog: 'Zapis sprememb', + cancel: 'Prekliči', + install: 'Namestite', + }, + toolbox: 'delovna orodja', + configureModel: 'Konfiguriraj Model', toolNotInstallTooltip: '{{tool}} ni nameščen', + pluginNotInstalled: 'Ta vtičnik ni nameščen', strategyNotInstallTooltip: '{{strategy}} ni nameščen', - modelNotInstallTooltip: 'Ta model ni nameščen', - pluginNotFoundDesc: 'Ta vtičnik je nameščen iz GitHuba. Prosimo, pojdite na Vtičniki za ponovno namestitev', - maxIterations: 'Največje število ponovitev', - notAuthorized: 'Ni pooblaščeno', + modelNotInstallTooltip: 'Ta model ni nameščen.', model: 'model', - learnMore: 'Izvedi več', + maxIterations: 'Maksimalne iteracije', + notAuthorized: 'Nimam dovoljenja', + modelNotSelected: 'Model ni izbran', + learnMore: 'Nauči se več', unsupportedStrategy: 'Nepodprta strategija', - strategyNotFoundDescAndSwitchVersion: 'Nameščena različica vtičnika ne zagotavlja te strategije. Kliknite, če želite preklopiti med različico.', - strategyNotFoundDesc: 'Nameščena različica vtičnika ne zagotavlja te strategije.', - configureModel: 'Konfiguracija modela', - pluginNotInstalledDesc: 'Ta vtičnik je nameščen iz GitHuba. Prosimo, pojdite na Vtičniki za ponovno namestitev', + pluginNotFoundDesc: 'Ta vtičnik je nameščen iz GitHuba. Prosimo, da greste v vtičnike in ga ponovo namestite.', + tools: 'Orodja', + strategyNotFoundDesc: 'V različici vtičnika, ki je nameščena, ta strategija ni zagotovljena.', linkToPlugin: 'Povezava do vtičnikov', + strategyNotSet: 'Agentična strategija ni nastavljena', + toolNotAuthorizedTooltip: '{{tool}} Ni pooblaščen', + strategyNotFoundDescAndSwitchVersion: 'Nameščena različica vtičnika ne podpira te strategije. Kliknite za preklop na drugo različico.', + pluginNotInstalledDesc: 'Ta vtičnik je nameščen iz GitHuba. Prosimo, da greste v vtičnike in ga ponovo namestite.', }, }, - variableReference: { - noVarsForOperation: 'Spremenljivk ni na voljo za dodelitev z izbrano operacijo.', - conversationVars: 'Spremenljivke pogovora', - noAssignedVars: 'Ni razpoložljivih dodeljenih spremenljivk', - noAvailableVars: 'Ni spremenljivk, ki so na voljo', - assignedVarsDescription: 'Dodeljene spremenljivke morajo biti zapisljive, kot so:', + tracing: { + stopBy: 'Ohranjaj se pri {{user}}', + }, + versionHistory: { + filter: { + all: 'Vse', + reset: 'Ponastavi filter', + onlyShowNamedVersions: 'Prikaži samo poimenovane različice', + onlyYours: 'Samo tvoje', + empty: 'Ni najdene zgodovine različic, ki bi se ujemala.', + }, + editField: { + title: 'Naslov', + titleLengthLimit: 'Naslov ne sme presegati {{limit}} znakov', + releaseNotesLengthLimit: 'Opombe o različici ne smejo presegati {{limit}} znakov.', + releaseNotes: 'Opombe o izdaji', + }, + action: { + deleteSuccess: 'Različica izbrisana', + deleteFailure: 'Brisanje različice ni uspelo', + updateFailure: 'Posodobitev različice ni uspela', + restoreSuccess: 'Obnovljena različica', + restoreFailure: 'Obnavljanje različice ni uspelo', + updateSuccess: 'Različica posodobljena', + }, + defaultName: 'Nepodpisana različica', + deletionTip: 'Izbris je nepovraten, prosim potrdite.', + currentDraft: 'Trenutni osnutek', + title: 'Različice', + editVersionInfo: 'Uredi informacije o različici', + latest: 'Najnovejši', + nameThisVersion: 'Poimenujte to različico', + releaseNotesPlaceholder: 'Opisujte, kaj se je spremenilo', + restorationTip: 'Po obnovitvi različice bo trenutni osnutek prepisan.', }, } diff --git a/web/i18n/th-TH/app-debug.ts b/web/i18n/th-TH/app-debug.ts index e69de29bb2..928649474b 100644 --- a/web/i18n/th-TH/app-debug.ts +++ b/web/i18n/th-TH/app-debug.ts @@ -0,0 +1,4 @@ +const translation = { +} + +export default translation diff --git a/web/i18n/th-TH/app-overview.ts b/web/i18n/th-TH/app-overview.ts index 92b002e4a5..87eddf1f7a 100644 --- a/web/i18n/th-TH/app-overview.ts +++ b/web/i18n/th-TH/app-overview.ts @@ -30,26 +30,26 @@ const translation = { overview: { title: 'ภาพรวม', appInfo: { - explanation: 'AI WebApp พร้อมใช้งาน', + explanation: 'AI web app พร้อมใช้งาน', accessibleAddress: 'URL สาธารณะ', preview: 'ดูตัวอย่าง', regenerate: 'สร้างใหม่', regenerateNotice: 'คุณต้องการสร้าง URL สาธารณะใหม่หรือไม่', - preUseReminder: 'โปรดเปิดใช้งาน WebApp ก่อนดําเนินการต่อ', + preUseReminder: 'โปรดเปิดใช้งาน web app ก่อนดําเนินการต่อ', settings: { entry: 'การตั้งค่า', title: 'การตั้งค่าเว็บแอป', webName: 'ชื่อเว็บแอป', - webDesc: 'คําอธิบาย WebApp', + webDesc: 'คําอธิบาย web app', webDescTip: 'ข้อความนี้จะแสดงที่ฝั่งไคลเอ็นต์ โดยให้คําแนะนําพื้นฐานเกี่ยวกับวิธีการใช้แอปพลิเคชัน', - webDescPlaceholder: 'ป้อนคําอธิบายของ WebApp', + webDescPlaceholder: 'ป้อนคําอธิบายของ web app', language: 'ภาษา', workflow: { title: 'เวิร์กโฟลว์', subTitle: 'รายละเอียดเวิร์กโฟลว์', show: 'แสดง', hide: 'ซ่อน', - showDesc: 'แสดงหรือซ่อนรายละเอียดเวิร์กโฟลว์ใน WebApp', + showDesc: 'แสดงหรือซ่อนรายละเอียดเวิร์กโฟลว์ใน web app', }, chatColorTheme: 'ธีมสีแชท', chatColorThemeDesc: 'กําหนดธีมสีของแชทบอท', @@ -59,8 +59,8 @@ const translation = { sso: { label: 'การรับรองความถูกต้องของ SSO', title: 'เว็บแอป SSO', - description: 'ผู้ใช้ทุกคนต้องเข้าสู่ระบบด้วย SSO ก่อนใช้ WebApp', - tooltip: 'ติดต่อผู้ดูแลระบบเพื่อเปิดใช้ WebApp SSO', + description: 'ผู้ใช้ทุกคนต้องเข้าสู่ระบบด้วย SSO ก่อนใช้ web app', + tooltip: 'ติดต่อผู้ดูแลระบบเพื่อเปิดใช้ web app SSO', }, more: { entry: 'แสดงการตั้งค่าเพิ่มเติม', @@ -95,7 +95,7 @@ const translation = { customize: { way: 'วิธี', entry: 'ปรับแต่ง', - title: 'ปรับแต่ง AI WebApp', + title: 'ปรับแต่ง AI web app', explanation: 'คุณสามารถปรับแต่งส่วนหน้าของ Web App ให้เหมาะกับสถานการณ์และความต้องการสไตล์ของคุณได้', way1: { name: 'แยกรหัสไคลเอ็นต์ แก้ไข และปรับใช้กับ Vercel (แนะนํา)', diff --git a/web/i18n/th-TH/app.ts b/web/i18n/th-TH/app.ts index 061e3a8076..0979d07f51 100644 --- a/web/i18n/th-TH/app.ts +++ b/web/i18n/th-TH/app.ts @@ -77,22 +77,22 @@ const translation = { noTemplateFoundTip: 'ลองค้นหาโดยใช้คีย์เวิร์ดอื่น', chatbotShortDescription: 'แชทบอทที่ใช้ LLM พร้อมการตั้งค่าที่ง่ายดาย', optional: 'เสริม', - workflowUserDescription: 'การประสานเวิร์กโฟลว์สําหรับงานรอบเดียว เช่น ระบบอัตโนมัติและการประมวลผลแบบแบตช์', + workflowUserDescription: 'สร้างโฟลว์ AI อัตโนมัติด้วยระบบลากและวางอย่างง่าย', agentShortDescription: 'ตัวแทนอัจฉริยะพร้อมการใช้เหตุผลและเครื่องมืออัตโนมัติ', - forBeginners: 'สําหรับผู้เริ่มต้น', + forBeginners: 'ประเภทแอปพื้นฐาน', completionShortDescription: 'ผู้ช่วย AI สําหรับงานสร้างข้อความ', agentUserDescription: 'ตัวแทนอัจฉริยะที่สามารถให้เหตุผลซ้ําๆ และใช้เครื่องมืออัตโนมัติเพื่อให้บรรลุเป้าหมายของงาน', noIdeaTip: 'ไม่มีความคิด? ดูเทมเพลตของเรา', foundResult: '{{นับ}} ผล', noAppsFound: 'ไม่พบแอป', - workflowShortDescription: 'การประสานงานสําหรับงานอัตโนมัติแบบเทิร์นเดียว', + workflowShortDescription: 'โฟลว์อัตโนมัติสำหรับระบบอัจฉริยะ', forAdvanced: 'สําหรับผู้ใช้ขั้นสูง', chatbotUserDescription: 'สร้างแชทบอทที่ใช้ LLM ได้อย่างรวดเร็วด้วยการกําหนดค่าที่ง่าย คุณสามารถเปลี่ยนไปใช้ Chatflow ได้ในภายหลัง', noTemplateFound: 'ไม่พบเทมเพลต', completionUserDescription: 'สร้างผู้ช่วย AI สําหรับงานสร้างข้อความอย่างรวดเร็วด้วยการกําหนดค่าที่ง่าย', - advancedUserDescription: 'การประสานเวิร์กโฟลว์สําหรับงานบทสนทนาที่ซับซ้อนหลายรอบพร้อมความสามารถของหน่วยความจํา', + advancedUserDescription: 'โฟลว์พร้อมคุณสมบัติหน่วยความจำเพิ่มเติมและอินเตอร์เฟซแชทบอท', chooseAppType: 'เลือกประเภทแอป', - advancedShortDescription: 'เวิร์กโฟลว์สําหรับบทสนทนาหลายรอบที่ซับซ้อนพร้อมหน่วยความจํา', + advancedShortDescription: 'โฟลว์ที่เสริมประสิทธิภาพสำหรับการสนทนาหลายรอบ', }, editApp: 'แก้ไขข้อมูล', editAppTitle: 'แก้ไขข้อมูลโปรเจกต์', @@ -105,9 +105,9 @@ const translation = { image: 'ภาพ', }, answerIcon: { - title: 'ใช้ไอคอน WebApp เพื่อแทนที่ 🤖', - description: 'จะใช้ไอคอน WebApp เพื่อแทนที่🤖ในโปรเจกต์ที่ใช้ร่วมกันหรือไม่', - descriptionInExplore: 'จะใช้ไอคอน WebApp เพื่อแทนที่🤖ใน Explore หรือไม่', + title: 'ใช้ไอคอน web app เพื่อแทนที่ 🤖', + description: 'จะใช้ไอคอน web app เพื่อแทนที่🤖ในโปรเจกต์ที่ใช้ร่วมกันหรือไม่', + descriptionInExplore: 'จะใช้ไอคอน web app เพื่อแทนที่🤖ใน Explore หรือไม่', }, switch: 'เปลี่ยนไปใช้ Workflow Orchestrate', switchTipStart: 'สําเนาโปรเจกต์ใหม่จะถูกสร้างขึ้นสําหรับคุณ และสําเนาใหม่จะเปลี่ยนเป็น Workflow Orchestration', @@ -164,6 +164,10 @@ const translation = { title: 'โอปิก', description: 'Opik เป็นแพลตฟอร์มโอเพ่นซอร์สสําหรับการประเมิน ทดสอบ และตรวจสอบแอปพลิเคชัน LLM', }, + weave: { + title: 'ทอ', + description: 'Weave เป็นแพลตฟอร์มโอเพนซอร์สสำหรับการประเมินผล ทดสอบ และตรวจสอบแอปพลิเคชัน LLM', + }, }, mermaid: { handDrawn: 'วาดด้วยมือ', @@ -190,6 +194,54 @@ const translation = { noParams: 'ไม่จําเป็นต้องใช้พารามิเตอร์', label: 'แอพ', }, + structOutput: { + notConfiguredTip: 'ยังไม่ได้กำหนดผลลัพธ์ที่มีโครงสร้าง', + moreFillTip: 'แสดงระดับการซ้อนสูงสุด 10 ระดับ', + structuredTip: 'Structured Outputs เป็นฟีเจอร์ที่ทำให้มั่นใจว่าโมเดลจะสร้างคำตอบที่สอดคล้องกับ JSON Schema ที่คุณกำหนดไว้เสมอ', + configure: 'กำหนดค่า', + required: 'ที่จำเป็น', + LLMResponse: 'LLM ตอบสนอง', + structured: 'มีระเบียบ', + modelNotSupported: 'โมเดลไม่ได้รับการสนับสนุน', + modelNotSupportedTip: 'โมเดลปัจจุบันไม่รองรับฟีเจอร์นี้และจะถูกลดระดับเป็นการฉีดคำสั่งโดยอัตโนมัติ.', + }, + accessItemsDescription: { + anyone: 'ใครก็สามารถเข้าถึงเว็บแอปได้', + specific: 'สมาชิกหรือกลุ่มเฉพาะเท่านั้นที่สามารถเข้าถึงแอปเว็บได้', + organization: 'ใครก็ได้ในองค์กรสามารถเข้าถึงแอปเว็บได้', + external: 'ผู้ใช้งานภายนอกที่ได้รับการยืนยันตัวตนเท่านั้นที่สามารถเข้าถึงแอปพลิเคชันเว็บได้', + }, + accessControlDialog: { + accessItems: { + specific: 'กลุ่มหรือสมาชิกเฉพาะ', + organization: 'เฉพาะสมาชิกภายในองค์กร', + anyone: 'ใครก็ตามที่มีลิงก์', + external: 'ผู้ใช้ภายนอกที่ได้รับการตรวจสอบแล้ว', + }, + operateGroupAndMember: { + searchPlaceholder: 'ค้นหากลุ่มและสมาชิก', + allMembers: 'สมาชิกทั้งหมด', + noResult: 'ไม่มีผลลัพธ์', + expand: 'ขยาย', + }, + title: 'การควบคุมการเข้าถึงเว็บแอปพลิเคชัน', + description: 'ตั้งค่าสิทธิ์การเข้าถึงเว็บแอป', + accessLabel: 'ใครมีสิทธิ์เข้าถึง', + groups_one: '{{count}} กลุ่ม', + groups_other: '{{count}} กลุ่ม', + members_one: '{{count}} สมาชิก', + noGroupsOrMembers: 'ไม่มีกลุ่มหรือสมาชิกที่เลือก', + webAppSSONotEnabledTip: 'กรุณาติดต่อผู้ดูแลระบบองค์กรเพื่อกำหนดวิธีการตรวจสอบสิทธิ์แอปเว็บ.', + updateSuccess: 'อัปเดตสำเร็จแล้ว', + members_other: '{{count}} สมาชิก', + }, + publishApp: { + title: 'ใครสามารถเข้าถึงแอปเว็บได้', + notSet: 'ยังไม่ได้ตั้งค่า', + notSetDesc: 'ขณะนี้ไม่มีใครสามารถเข้าถึงแอปเว็บได้ กรุณาเพิ่มสิทธิ์การเข้าถึง.', + }, + accessControl: 'การควบคุมการเข้าถึงเว็บแอปพลิเคชัน', + noAccessPermission: 'ไม่มีสิทธิ์เข้าถึงเว็บแอป', } export default translation diff --git a/web/i18n/th-TH/billing.ts b/web/i18n/th-TH/billing.ts index abe82754b4..c58d61c112 100644 --- a/web/i18n/th-TH/billing.ts +++ b/web/i18n/th-TH/billing.ts @@ -70,6 +70,7 @@ const translation = { messageRequest: { title: 'เครดิตข้อความ', tooltip: 'โควต้าการเรียกใช้ข้อความสําหรับแผนต่างๆ โดยใช้โมเดล OpenAI (ยกเว้น gpt4) ข้อความที่เกินขีดจํากัดจะใช้คีย์ OpenAI API ของคุณ', + titlePerMonth: '{{count,number}} ข้อความ/เดือน', }, annotatedResponse: { title: 'ขีดจํากัดโควต้าคําอธิบายประกอบ', @@ -77,27 +78,94 @@ const translation = { }, ragAPIRequestTooltip: 'หมายถึงจํานวนการเรียก API ที่เรียกใช้เฉพาะความสามารถในการประมวลผลฐานความรู้ของ Dify', receiptInfo: 'เฉพาะเจ้าของทีมและผู้ดูแลทีมเท่านั้นที่สามารถสมัครสมาชิกและดูข้อมูลการเรียกเก็บเงินได้', + cloud: 'บริการคลาวด์', + comparePlanAndFeatures: 'เปรียบเทียบแผนและฟีเจอร์', + apiRateLimit: 'ข้อจำกัดอัตราการใช้ API', + getStarted: 'เริ่มต้น', + documents: '{{count,number}} เอกสารความรู้', + freeTrialTipPrefix: 'ลงทะเบียนและรับ', + teamMember_one: '{{count,number}} สมาชิกทีม', + unlimitedApiRate: 'ไม่มีข้อจำกัดอัตราการเรียก API', + self: 'โฮสต์ด้วยตัวเอง', + apiRateLimitUnit: '{{count,number}}/วัน', + teamMember_other: '{{count,number}} สมาชิกทีม', + teamWorkspace: '{{count,number}} ทีมทำงาน', + priceTip: 'ต่อพื้นที่ทำงาน/', + documentsTooltip: 'โควต้าสำหรับจำนวนเอกสารที่นำเข้าจากแหล่งข้อมูลความรู้.', + documentsRequestQuota: '{{count,number}}/นาที จำกัด อัตราการร้องขอข้อมูล', + apiRateLimitTooltip: 'ข้อจำกัดการใช้งาน API จะใช้กับคำขอทั้งหมดที่ทำผ่าน Dify API รวมถึงการสร้างข้อความ, การสนทนาแชท, การดำเนินการเวิร์กโฟลว์ และการประมวลผลเอกสาร.', + freeTrialTipSuffix: 'ไม่จำเป็นต้องใช้บัตรเครดิต', + freeTrialTip: 'ทดลองใช้งานฟรี 200 ครั้งสำหรับ OpenAI.', + annualBilling: 'การเรียกเก็บเงินประจำปี', + documentsRequestQuotaTooltip: 'ระบุจำนวนรวมของการกระทำที่เวิร์กสเปซสามารถดำเนินการต่อหนึ่งนาทีภายในฐานความรู้ รวมถึงการสร้างชุดข้อมูล การลบ การอัปเดต การอัปโหลดเอกสาร การปรับเปลี่ยน การเก็บถาวร และการสอบถามฐานความรู้ เมตริกนี้ถูกใช้ในการประเมินประสิทธิภาพของคำขอฐานความรู้ ตัวอย่างเช่น หากผู้ใช้ Sandbox ทำการทดสอบการตี 10 ครั้งต่อเนื่องภายในหนึ่งนาที เวิร์กสเปซของพวกเขาจะถูกจำกัดชั่วคราวในการดำเนินการต่อไปนี้ในนาทีถัดไป: การสร้างชุดข้อมูล การลบ การอัปเดต หรือการอัปโหลดหรือปรับเปลี่ยนเอกสาร.', }, plans: { sandbox: { name: 'กระบะทราย', description: 'ทดลองใช้ GPT ฟรี 200 ครั้ง', includesTitle: 'มี:', + for: 'ทดลองใช้ฟรีของความสามารถหลัก', }, professional: { name: 'มืออาชีพ', description: 'สําหรับบุคคลและทีมขนาดเล็กเพื่อปลดล็อกพลังงานมากขึ้นในราคาย่อมเยา', includesTitle: 'ทุกอย่างในแผนฟรี รวมถึง:', + for: 'สำหรับนักพัฒนาที่เป็นอิสระ/ทีมขนาดเล็ก', }, team: { name: 'ทีม', description: 'ทํางานร่วมกันอย่างไร้ขีดจํากัดและเพลิดเพลินไปกับประสิทธิภาพระดับสูงสุด', includesTitle: 'ทุกอย่างในแผน Professional รวมถึง:', + for: 'สำหรับทีมขนาดกลาง', }, enterprise: { name: 'กิจการ', description: 'รับความสามารถและการสนับสนุนเต็มรูปแบบสําหรับระบบที่สําคัญต่อภารกิจขนาดใหญ่', includesTitle: 'ทุกอย่างในแผนทีม รวมถึง:', + features: { + 8: 'การสนับสนุนทางเทคนิคระดับมืออาชีพ', + 2: 'คุณสมบัติพิเศษขององค์กร', + 3: 'หลายพื้นที่ทำงานและการบริหารจัดการองค์กร', + 4: 'SSO', + 6: 'ความปลอดภัยและการควบคุมขั้นสูง', + 5: 'เจรจาข้อตกลงบริการ (SLA) โดย Dify Partners', + 7: 'การอัปเดตและการบำรุงรักษาโดย Dify อย่างเป็นทางการ', + 1: 'ใบอนุญาตการใช้เชิงพาณิชย์', + 0: 'โซลูชันการปรับใช้ที่มีขนาดใหญ่และมีคุณภาพระดับองค์กร', + }, + btnText: 'ติดต่อฝ่ายขาย', + price: 'ที่กำหนดเอง', + for: 'สำหรับทีมขนาดใหญ่', + priceTip: 'การเรียกเก็บเงินประจำปีเท่านั้น', + }, + community: { + features: { + 2: 'ปฏิบัติตามใบอนุญาตโอเพ่นซอร์สของ Dify', + 0: 'ฟีเจอร์หลักทั้งหมดถูกปล่อยออกภายใต้ที่เก็บสาธารณะ', + 1: 'พื้นที่ทำงานเดียว', + }, + name: 'ชุมชน', + price: 'ฟรี', + includesTitle: 'คุณสมบัติเสรี:', + description: 'สำหรับผู้ใช้ส่วนบุคคล ทีมขนาดเล็ก หรือโครงการที่ไม่ใช่เชิงพาณิชย์', + btnText: 'เริ่มต้นกับชุมชน', + for: 'สำหรับผู้ใช้ส่วนบุคคล ทีมขนาดเล็ก หรือโครงการที่ไม่ใช่เชิงพาณิชย์', + }, + premium: { + features: { + 3: 'การสนับสนุนทางอีเมลและแชทที่มีความสำคัญ', + 1: 'พื้นที่ทำงานเดียว', + 2: 'การปรับแต่งโลโก้และแบรนดิ้งของเว็บแอป', + 0: 'การจัดการความน่าเชื่อถือด้วยตนเองโดยผู้ให้บริการคลาวด์ต่าง ๆ', + }, + priceTip: 'อิงตามตลาดคลาวด์', + for: 'สำหรับองค์กรและทีมขนาดกลาง', + btnText: 'รับพรีเมียมใน', + includesTitle: 'ทุกอย่างจากชุมชน รวมถึง:', + description: 'สำหรับองค์กรและทีมขนาดกลาง', + name: 'พรีเมียม', + comingSoon: 'การสนับสนุน Microsoft Azure และ Google Cloud กำลังมาเร็วๆ นี้', + price: 'ขยายได้', }, }, vectorSpace: { @@ -107,12 +175,26 @@ const translation = { apps: { fullTipLine1: 'อัปเกรดแผนของคุณเป็น', fullTipLine2: 'สร้างแอปเพิ่มเติม', + contactUs: 'ติดต่อเรา', + fullTip2: 'ถึงขีดจำกัดของแผนแล้ว', + fullTip1: 'อัปเกรดเพื่อสร้างแอปเพิ่มเติม', + fullTip1des: 'คุณได้ถึงขีด จำกัด ของการสร้างแอปในแผนนี้แล้ว', + fullTip2des: 'แนะนำให้ทำความสะอาดแอปพลิเคชันที่ไม่ใช้งานเพื่อเพิ่มการใช้งาน หรือติดต่อเรา', }, annotatedResponse: { fullTipLine1: 'อัปเกรดแผนของคุณเป็น', fullTipLine2: 'ใส่คําอธิบายประกอบการสนทนาเพิ่มเติม', quotaTitle: 'โควต้าตอบกลับคําอธิบายประกอบ', }, + usagePage: { + buildApps: 'สร้างแอป', + annotationQuota: 'โควตาการประกาศ', + documentsUploadQuota: 'โควต้าการอัปโหลดเอกสาร', + teamMembers: 'สมาชิกในทีม', + vectorSpace: 'การจัดเก็บข้อมูลความรู้', + vectorSpaceTooltip: 'เอกสารที่ใช้โหมดการจัดทำดัชนีคุณภาพสูงจะใช้ทรัพยากรเก็บข้อมูลความรู้ เมื่อการเก็บข้อมูลความรู้ถึงขีดจำกัด เอกสารใหม่จะไม่สามารถอัปโหลดได้.', + }, + teamMembers: 'สมาชิกในทีม', } export default translation diff --git a/web/i18n/th-TH/common.ts b/web/i18n/th-TH/common.ts index be1f62cdd7..7425a178d3 100644 --- a/web/i18n/th-TH/common.ts +++ b/web/i18n/th-TH/common.ts @@ -54,6 +54,10 @@ const translation = { copied: 'คัด ลอก', viewDetails: 'ดูรายละเอียด', in: 'ใน', + format: 'รูปแบบ', + downloadFailed: 'ดาวน์โหลดล้มเหลว กรุณาลองอีกครั้งในภายหลัง.', + more: 'มากขึ้น', + downloadSuccess: 'ดาวน์โหลดเสร็จสิ้นแล้ว.', }, errorMsg: { fieldRequired: '{{field}} เป็นสิ่งจําเป็น', @@ -140,6 +144,8 @@ const translation = { newDataset: 'สร้างความรู้', tools: 'เครื่อง มือ', exploreMarketplace: 'สํารวจ Marketplace', + appDetail: 'รายละเอียดแอป', + account: 'บัญชี', }, userProfile: { settings: 'การตั้งค่า', @@ -152,6 +158,9 @@ const translation = { community: 'ชุมชน', about: 'ประมาณ', logout: 'ออกจากระบบ', + github: 'GitHub', + compliance: 'การปฏิบัติตามข้อกำหนด', + support: 'การสนับสนุน', }, settings: { accountGroup: 'ทั่วไป', @@ -201,6 +210,9 @@ const translation = { feedbackLabel: 'บอกเราว่าทําไมคุณถึงลบบัญชีของคุณ', feedbackPlaceholder: 'เสริม', deleteSuccessTip: 'บัญชีของคุณต้องใช้เวลาในการลบให้เสร็จสิ้น เราจะส่งอีเมลถึงคุณเมื่อทุกอย่างเสร็จสิ้น', + workspaceIcon: 'ไอคอนพื้นที่ทำงาน', + editWorkspaceInfo: 'แก้ไขข้อมูลเวิร์กสเปซ', + workspaceName: 'ชื่อพื้นที่ทำงาน', }, members: { team: 'ทีม', @@ -454,7 +466,7 @@ const translation = { apiBasedExtension: { title: 'ส่วนขยาย API ให้การจัดการ API แบบรวมศูนย์ ทําให้การกําหนดค่าง่ายขึ้นเพื่อให้ใช้งานได้ง่ายในแอปพลิเคชันของ Dify', link: 'เรียนรู้วิธีพัฒนาส่วนขยาย API ของคุณเอง', - linkUrl: 'https://docs.dify.ai/features/extension/api_based_extension', + linkUrl: 'https://docs.dify.ai/en/guides/knowledge-base/create-knowledge-and-upload-documents/import-content-data/sync-from-website', add: 'เพิ่มส่วนขยาย API', selector: { title: 'ส่วนขยาย API', @@ -542,6 +554,7 @@ const translation = { inputPlaceholder: 'พูดคุยกับบอท', thought: 'ความคิด', thinking: 'ความคิด ', + resend: 'ส่งอีกครั้ง', }, promptEditor: { placeholder: 'เขียนคําพร้อมท์ของคุณที่นี่ ป้อน \'{\' เพื่อแทรกตัวแปร ป้อน \'/\' เพื่อแทรกบล็อกเนื้อหาพร้อมท์', @@ -628,10 +641,31 @@ const translation = { license: { expiring: 'หมดอายุในหนึ่งวัน', expiring_plural: 'หมดอายุใน {{count}} วัน', + unlimited: 'ไม่มีขีดจำกัด', }, pagination: { perPage: 'รายการต่อหน้า', }, + theme: { + dark: 'มืด', + theme: 'ธีม', + auto: 'ระบบ', + light: 'แสง', + }, + compliance: { + professionalUpgradeTooltip: 'ใช้ได้เฉพาะแผนทีมหรือสูงกว่าเท่านั้น.', + gdpr: 'GDPR DPA', + sandboxUpgradeTooltip: 'มีให้บริการเฉพาะกับแผนการใช้งานแบบมืออาชีพหรือทีมเท่านั้น.', + iso27001: 'การรับรอง ISO 27001:2022', + soc2Type2: 'รายงาน SOC 2 Type II', + soc2Type1: 'รายงาน SOC 2 ประเภท I', + }, + imageInput: { + dropImageHere: 'วางภาพของคุณที่นี่ หรือ', + browse: 'ท่องเว็บ', + supportedFormats: 'รองรับ PNG, JPG, JPEG, WEBP และ GIF', + }, + you: 'คุณ', } export default translation diff --git a/web/i18n/th-TH/custom.ts b/web/i18n/th-TH/custom.ts index dfed65c9a0..41a0441988 100644 --- a/web/i18n/th-TH/custom.ts +++ b/web/i18n/th-TH/custom.ts @@ -3,9 +3,11 @@ const translation = { upgradeTip: { prefix: 'อัปเกรดแผนของคุณเป็น', suffix: 'ปรับแต่งแบรนด์ของคุณ', + des: 'อัปเกรดแผนของคุณเพื่อปรับแต่งแบรนด์ของคุณ', + title: 'อัปเกรดแผนของคุณ', }, webapp: { - title: 'ปรับแต่งแบรนด์ WebApp', + title: 'ปรับแต่งแบรนด์ web app', removeBrand: 'ลบ ขับเคลื่อนโดย Dify', changeLogo: 'การเปลี่ยนแปลงที่ขับเคลื่อนโดยภาพลักษณ์ของแบรนด์', changeLogoTip: 'รูปแบบ SVG หรือ PNG ที่มีขนาดขั้นต่ํา 40x40px', diff --git a/web/i18n/th-TH/dataset-creation.ts b/web/i18n/th-TH/dataset-creation.ts index d4ef1a9af9..e6081a9618 100644 --- a/web/i18n/th-TH/dataset-creation.ts +++ b/web/i18n/th-TH/dataset-creation.ts @@ -32,7 +32,7 @@ const translation = { }, uploader: { title: 'อัปโหลดไฟล์', - button: 'ลากและวางไฟล์ หรือ', + button: 'ลากและวางไฟล์หรือโฟลเดอร์หรือ', browse: 'เล็ม', tip: 'รองรับ {{supportTypes}} สูงสุด {{size}}MB แต่ละตัว', validation: { @@ -71,7 +71,7 @@ const translation = { run: 'วิ่ง', firecrawlTitle: 'แยกเนื้อหาเว็บด้วย 🔥Firecrawl', firecrawlDoc: 'เอกสาร Firecrawl', - firecrawlDocLink: 'https://docs.dify.ai/guides/knowledge-base/sync-from-website', + firecrawlDocLink: 'https://docs.dify.ai/en/guides/knowledge-base/create-knowledge-and-upload-documents/import-content-data/sync-from-website', jinaReaderTitle: 'แปลงทั้งไซต์เป็น Markdown', jinaReaderDoc: 'เรียนรู้เพิ่มเติมเกี่ยวกับ Jina Reader', jinaReaderDocLink: 'https://jina.ai/reader', @@ -92,6 +92,14 @@ const translation = { scrapTimeInfo: 'ขูด {{total}} หน้าทั้งหมดภายใน {{time}}s', preview: 'ดูตัวอย่าง', maxDepthTooltip: 'ความลึกสูงสุดในการรวบรวมข้อมูลเมื่อเทียบกับ URL ที่ป้อน ความลึก 0 เพียงแค่ขูดหน้าของ URL ที่ป้อนความลึก 1 ขูด url และทุกอย่างหลังจาก enteredURL + หนึ่ง / เป็นต้น', + watercrawlTitle: 'ดึงเนื้อหาจากเว็บด้วย Watercrawl', + configureJinaReader: 'ตั้งค่า Jina Reader', + watercrawlDocLink: 'https://docs.dify.ai/th/guides/knowledge-base/create-knowledge-and-upload-documents/import-content-data/sync-from-website', + configureFirecrawl: 'กำหนดค่า Firecrawl', + configureWatercrawl: 'กำหนดค่าการเข้าถึงน้ำ', + waterCrawlNotConfiguredDescription: 'กำหนดค่า Watercrawl ด้วย API key เพื่อใช้งาน.', + watercrawlDoc: 'เอกสาร Watercrawl', + waterCrawlNotConfigured: 'Watercrawl ยังไม่ได้ตั้งค่า', }, cancel: 'ยกเลิก', }, @@ -200,6 +208,11 @@ const translation = { title: 'เชื่อมต่อกับแหล่งข้อมูลอื่นใช่ไหม', description: 'ปัจจุบัน ฐานความรู้ของ Dify มีแหล่งข้อมูลที่จํากัดเท่านั้น การมีส่วนร่วมในแหล่งข้อมูลในฐานความรู้ Dify เป็นวิธีที่ยอดเยี่ยมในการช่วยเพิ่มความยืดหยุ่นและพลังของแพลตฟอร์มสําหรับผู้ใช้ทุกคน คู่มือการมีส่วนร่วมของเราทําให้ง่ายต่อการเริ่มต้นใช้งาน โปรดคลิกที่ลิงค์ด้านล่างเพื่อเรียนรู้เพิ่มเติม', }, + watercrawl: { + configWatercrawl: 'กำหนด Watercrawl', + getApiKeyLinkText: 'รับคีย์ API ของคุณจาก watercrawl.dev', + apiKeyPlaceholder: 'คีย์ API จาก watercrawl.dev', + }, } export default translation diff --git a/web/i18n/th-TH/dataset-documents.ts b/web/i18n/th-TH/dataset-documents.ts index 2f4c6d5c9c..91d04d6bc1 100644 --- a/web/i18n/th-TH/dataset-documents.ts +++ b/web/i18n/th-TH/dataset-documents.ts @@ -50,7 +50,7 @@ const translation = { empty: { title: 'ยังไม่มีเอกสาร', upload: { - tip: 'คุณสามารถอัปโหลดไฟล์ ซิงค์จากเว็บไซต์ หรือจากแอป webb เช่น Notion, GitHub เป็นต้น', + tip: 'คุณสามารถอัปโหลดไฟล์ ซิงค์จากเว็บไซต์ หรือจากแอป web เช่น Notion, GitHub เป็นต้น', }, sync: { tip: 'Dify จะดาวน์โหลดไฟล์จาก Notion ของคุณเป็นระยะและดําเนินการให้เสร็จสมบูรณ์', diff --git a/web/i18n/th-TH/dataset-settings.ts b/web/i18n/th-TH/dataset-settings.ts index e91834ced2..7ddbbc3787 100644 --- a/web/i18n/th-TH/dataset-settings.ts +++ b/web/i18n/th-TH/dataset-settings.ts @@ -27,6 +27,7 @@ const translation = { learnMore: 'ศึกษาเพิ่มเติม', description: 'เกี่ยวกับวิธีการดึงข้อมูล', longDescription: 'เกี่ยวกับวิธีการดึงข้อมูล คุณสามารถเปลี่ยนแปลงได้ตลอดเวลาในการตั้งค่าความรู้', + method: 'วิธีการค้นคืน', }, externalKnowledgeAPI: 'API ความรู้ภายนอก', externalKnowledgeID: 'ID ความรู้ภายนอก', diff --git a/web/i18n/th-TH/dataset.ts b/web/i18n/th-TH/dataset.ts index 1877226dc4..15ef381605 100644 --- a/web/i18n/th-TH/dataset.ts +++ b/web/i18n/th-TH/dataset.ts @@ -167,6 +167,54 @@ const translation = { enable: 'เปิด', allKnowledge: 'ความรู้ทั้งหมด', allKnowledgeDescription: 'เลือกเพื่อแสดงความรู้ทั้งหมดในพื้นที่ทํางานนี้ เฉพาะเจ้าของพื้นที่ทํางานเท่านั้นที่สามารถจัดการความรู้ทั้งหมดได้', + metadata: { + createMetadata: { + back: 'กลับ', + title: 'ข้อมูลเมตาใหม่', + namePlaceholder: 'เพิ่มชื่อข้อมูลเมตา', + name: 'ชื่อ', + type: 'ประเภท', + }, + checkName: { + invalid: 'ชื่อเมตาดาต้าต้องประกอบด้วยตัวอักษรตัวเล็กเท่านั้น เลข และขีดล่าง และต้องเริ่มต้นด้วยตัวอักษรตัวเล็ก', + empty: 'ชื่อข้อมูลเมตาไม่สามารถเป็นค่าแEmpty', + }, + batchEditMetadata: { + multipleValue: 'หลายค่า', + applyToAllSelectDocument: 'ใช้กับเอกสารที่เลือกทั้งหมด', + editMetadata: 'แก้ไขข้อมูลเมตา', + editDocumentsNum: 'การแก้ไขเอกสาร {{num}} ฉบับ', + applyToAllSelectDocumentTip: 'สร้างข้อมูลเมตาใหม่และแก้ไขทั้งหมดข้างต้นโดยอัตโนมัติสำหรับเอกสารที่เลือกทั้งหมด มิฉะนั้นการแก้ไขข้อมูลเมตาจะใช้ได้เฉพาะกับเอกสารที่มีข้อมูลเมตานั้นเท่านั้น.', + }, + selectMetadata: { + manageAction: 'จัดการ', + search: 'ค้นหาข้อมูลเมตา', + newAction: 'ข้อมูลเมตาใหม่', + }, + datasetMetadata: { + deleteTitle: 'ยืนยันเพื่อทำการลบ', + values: '{{num}} ค่า', + disabled: 'คนพิการ', + builtInDescription: 'ข้อมูลเมตาที่สร้างขึ้นในตัวจะถูกดึงออกและสร้างโดยอัตโนมัติ ต้องเปิดใช้งานก่อนใช้งานและไม่สามารถแก้ไขได้', + rename: 'เปลี่ยนชื่อ', + description: 'คุณสามารถจัดการข้อมูลเมตาทั้งหมดในความรู้นี้ได้ที่นี่ การปรับเปลี่ยนจะถูกซิงโครไนซ์ไปยังเอกสารทุกฉบับ', + deleteContent: 'คุณแน่ใจหรือว่าต้องการลบข้อมูลเมตา "{{name}}"', + name: 'ชื่อ', + addMetaData: 'เพิ่มข้อมูลเมตา', + builtIn: 'แบบในตัว', + namePlaceholder: 'ชื่อเมทาดาทา', + }, + documentMetadata: { + technicalParameters: 'พารามิเตอร์ทางเทคนิค', + startLabeling: 'เริ่มการติดป้าย', + metadataToolTip: 'ข้อมูลเมตาเป็นตัวกรองที่สำคัญซึ่งช่วยเพิ่มความถูกต้องและความเกี่ยวข้องของการดึงข้อมูล คุณสามารถปรับแก้และเพิ่มข้อมูลเมตาสำหรับเอกสารนี้ได้ที่นี่', + documentInformation: 'ข้อมูลเอกสาร', + }, + metadata: 'ข้อมูลเมตา', + addMetadata: 'เพิ่มข้อมูลเมตา', + chooseTime: 'เลือกเวลา...', + }, + embeddingModelNotAvailable: 'โมเดลฝังตัวไม่สามารถใช้งานได้.', } export default translation diff --git a/web/i18n/th-TH/education.ts b/web/i18n/th-TH/education.ts new file mode 100644 index 0000000000..5d19cbebae --- /dev/null +++ b/web/i18n/th-TH/education.ts @@ -0,0 +1,47 @@ +const translation = { + toVerifiedTip: { + coupon: 'คูปองพิเศษ 100%', + end: 'สำหรับแผนมืออาชีพของ Dify.', + front: 'คุณมีสิทธิ์ได้รับสถานะการตรวจสอบการศึกษาแล้ว กรุณากรอกข้อมูลการศึกษาของคุณด้านล่างเพื่อดำเนินการให้เสร็จสิ้นและรับสิทธิ์', + }, + form: { + schoolName: { + title: 'ชื่อโรงเรียนของคุณ', + placeholder: 'กรุณาใส่ชื่อของโรงเรียนอย่างเป็นทางการที่ไม่มีการย่อ', + }, + schoolRole: { + option: { + student: 'นักเรียน', + teacher: 'ครู', + administrator: 'ผู้ดูแลโรงเรียน', + }, + title: 'บทบาทของคุณในโรงเรียน', + }, + terms: { + desc: { + front: 'ข้อมูลของคุณและการใช้สถานะการตรวจสอบการศึกษาอยู่ภายใต้เงื่อนไขของเรา', + end: '. โดยการส่ง:', + privacyPolicy: 'นโยบายความเป็นส่วนตัว', + and: 'และ', + termsOfService: 'ข้อกำหนดในการให้บริการ', + }, + option: { + age: 'ฉันยืนยันว่าฉันมีอายุอย่างน้อย 18 ปี', + inSchool: 'ฉันยืนยันว่าฉันได้ลงทะเบียนหรือทำงานที่สถาบันที่ระบุไว้ Dify อาจขอหลักฐานการลงทะเบียน/การจ้างงาน หากฉันแสดงความไม่ถูกต้องเกี่ยวกับคุณสมบัติของฉัน ฉันตกลงที่จะชำระค่าธรรมเนียมใด ๆ ที่ถูกยกเว้นไปในเบื้องต้นตามสถานะการศึกษาของฉัน.', + }, + title: 'ข้อกำหนดและเงื่อนไข', + }, + }, + toVerified: 'ตรวจสอบการศึกษา', + rejectTitle: 'การตรวจสอบการศึกษา Dify ของคุณถูกปฏิเสธ', + emailLabel: 'อีเมลปัจจุบันของคุณ', + currentSigned: 'ลงชื่อเข้าใช้ในฐานะ', + successTitle: 'คุณได้รับการรับรองการศึกษา Dify', + learn: 'เรียนรู้วิธีการตรวจสอบการศึกษา', + submitError: 'การส่งแบบฟอร์มล้มเหลว โปรดลองอีกครั้งในภายหลัง.', + submit: 'ส่ง', + successContent: 'เราได้ออกคูปองส่วนลด 100% สำหรับแผน Dify Professional ให้กับบัญชีของคุณ คูปองนี้สามารถใช้ได้เป็นระยะเวลา 1 ปี กรุณาใช้ภายในช่วงระยะเวลาที่กำหนด.', + rejectContent: 'น่าเสียดายที่คุณไม่มีสิทธิ์ได้รับสถานะการตรวจสอบการศึกษาและดังนั้นคุณจึงไม่สามารถรับคูปองพิเศษ 100% สำหรับแผนมืออาชีพ Dify หากคุณใช้ที่อยู่อีเมลนี้.', +} + +export default translation diff --git a/web/i18n/th-TH/explore.ts b/web/i18n/th-TH/explore.ts index aaed249194..d8eb53d194 100644 --- a/web/i18n/th-TH/explore.ts +++ b/web/i18n/th-TH/explore.ts @@ -37,6 +37,7 @@ const translation = { HR: 'ชั่วโมง', Workflow: 'เวิร์กโฟลว์', Agent: 'ตัวแทน', + Entertainment: 'ความบันเทิง', }, } diff --git a/web/i18n/th-TH/login.ts b/web/i18n/th-TH/login.ts index 75f569d3a2..da24be7ea5 100644 --- a/web/i18n/th-TH/login.ts +++ b/web/i18n/th-TH/login.ts @@ -104,6 +104,11 @@ const translation = { licenseLostTip: 'เชื่อมต่อเซิร์ฟเวอร์ใบอนุญาต Dify ไม่สําเร็จ โปรดติดต่อผู้ดูแลระบบของคุณเพื่อใช้ Dify ต่อไป', licenseInactive: 'ใบอนุญาตไม่ใช้งาน', licenseInactiveTip: 'สิทธิ์การใช้งาน Dify Enterprise สําหรับพื้นที่ทํางานของคุณไม่ได้ใช้งาน โปรดติดต่อผู้ดูแลระบบของคุณเพื่อใช้ Dify ต่อไป', + webapp: { + noLoginMethodTip: 'กรุณาติดต่อผู้ดูแลระบบเพื่อเพิ่มวิธีการตรวจสอบสิทธิ์.', + noLoginMethod: 'ไม่ได้กำหนดวิธีการตรวจสอบสิทธิ์สำหรับเว็บแอป', + disabled: 'การรับรองความถูกต้องของเว็บแอปถูกปิดใช้งาน โปรดติดต่อผู้ดูแลระบบเพื่อเปิดใช้งาน คุณสามารถลองใช้แอปโดยตรงได้', + }, } export default translation diff --git a/web/i18n/th-TH/plugin.ts b/web/i18n/th-TH/plugin.ts index 962df3b912..a573fb95fb 100644 --- a/web/i18n/th-TH/plugin.ts +++ b/web/i18n/th-TH/plugin.ts @@ -62,6 +62,7 @@ const translation = { uninstalledTitle: 'ไม่ได้ติดตั้งเครื่องมือ', descriptionPlaceholder: 'คําอธิบายสั้น ๆ เกี่ยวกับวัตถุประสงค์ของเครื่องมือ เช่น รับอุณหภูมิสําหรับตําแหน่งเฉพาะ', uninstalledContent: 'ปลั๊กอินนี้ติดตั้งจากที่เก็บในเครื่อง/GitHub กรุณาใช้หลังการติดตั้ง', + toolSetting: 'การตั้งค่าเครื่องมือ', }, endpointDisableContent: 'คุณต้องการปิดการใช้งาน {{name}} หรือไม่?', configureApp: 'กําหนดค่าแอป', @@ -180,6 +181,8 @@ const translation = { noPluginFound: 'ไม่พบปลั๊กอิน', empower: 'เพิ่มศักยภาพในการพัฒนา AI ของคุณ', difyMarketplace: 'ตลาด Dify', + partnerTip: 'ได้รับการตรวจสอบโดยพันธมิตรของ Dify', + verifiedTip: 'ได้รับการตรวจสอบโดย Dify', }, task: { installing: 'การติดตั้งปลั๊กอิน {{installingLength}} 0 เสร็จแล้ว', @@ -202,8 +205,13 @@ const translation = { searchTools: 'เครื่องมือค้นหา...', installFrom: 'ติดตั้งจาก', fromMarketplace: 'จาก Marketplace', - submitPlugin: 'ส่งปลั๊กอิน', allCategories: 'หมวดหมู่ทั้งหมด', + metadata: { + title: 'ปลั๊กอิน', + }, + difyVersionNotCompatible: 'เวอร์ชั่นปัจจุบันของ Dify ไม่สามารถใช้งานร่วมกับปลั๊กอินนี้ได้ กรุณาอัปเกรดไปยังเวอร์ชั่นขั้นต่ำที่ต้องการ: {{minimalDifyVersion}}', + requestAPlugin: 'ขอปลั๊กอิน', + publishPlugins: 'เผยแพร่ปลั๊กอิน', } export default translation diff --git a/web/i18n/th-TH/share-app.ts b/web/i18n/th-TH/share-app.ts index 290a8cc3f7..eca049b9a2 100644 --- a/web/i18n/th-TH/share-app.ts +++ b/web/i18n/th-TH/share-app.ts @@ -26,6 +26,12 @@ const translation = { }, tryToSolve: 'พยายามแก้', temporarySystemIssue: 'ขออภัย ปัญหาระบบชั่วคราว', + expand: 'ขยาย', + collapse: 'ย่อ', + newChatTip: 'อยู่ในแชทใหม่แล้ว', + chatSettingsTitle: 'การตั้งค่าการสนทนาใหม่', + viewChatSettings: 'ดูการตั้งค่าการแชท', + chatFormTip: 'การตั้งค่าแชทไม่สามารถเปลี่ยนแปลงได้หลังจากที่แชทเริ่มต้นขึ้นแล้ว.', }, generation: { tabs: { @@ -64,6 +70,11 @@ const translation = { moreThanMaxLengthLine: 'แถว {{rowIndex}}: ค่า {{varName}} ต้องไม่เกิน {{maxLength}} อักขระ', atLeastOne: 'โปรดป้อนอย่างน้อยหนึ่งแถวในไฟล์ที่อัปโหลด', }, + execution: 'การดำเนินการ', + executions: '{{num}} การประหารชีวิต', + }, + login: { + backToHome: 'กลับไปที่หน้าแรก', }, } diff --git a/web/i18n/th-TH/time.ts b/web/i18n/th-TH/time.ts index e2410dd34b..03897dd863 100644 --- a/web/i18n/th-TH/time.ts +++ b/web/i18n/th-TH/time.ts @@ -1,3 +1,37 @@ -const translation = {} +const translation = { + daysInWeek: { + Fri: 'เสรี', + Sat: 'เสาร์', + Wed: 'วันพุธ', + Sun: 'ดวงอาทิตย์', + Tue: 'อังคาร', + Thu: 'วันพฤหัสบดี', + Mon: 'มอน', + }, + months: { + February: 'กุมภาพันธ์', + July: 'กรกฎาคม', + June: 'มิถุนายน', + January: 'มกราคม', + September: 'กันยายน', + August: 'สิงหาคม', + October: 'ตุลาคม', + May: 'พฤษภาคม', + November: 'พฤศจิกายน', + March: 'มีนาคม', + December: 'ธันวาคม', + April: 'เมษายน', + }, + operation: { + ok: 'ตกลง', + cancel: 'ยกเลิก', + pickDate: 'เลือกวันที่', + now: 'ตอนนี้', + }, + title: { + pickTime: 'เลือกเวลา', + }, + defaultPlaceholder: 'เลือกเวลา...', +} export default translation diff --git a/web/i18n/th-TH/tools.ts b/web/i18n/th-TH/tools.ts index 7770b3d92e..14c9457c4e 100644 --- a/web/i18n/th-TH/tools.ts +++ b/web/i18n/th-TH/tools.ts @@ -15,7 +15,6 @@ const translation = { }, author: 'โดย', auth: { - unauthorized: 'การอนุญาต', authorized: 'อนุญาต', setup: 'ตั้งค่าการให้สิทธิ์เพื่อใช้', setupModalTitle: 'ตั้งค่าการให้สิทธิ์', diff --git a/web/i18n/th-TH/workflow.ts b/web/i18n/th-TH/workflow.ts index 5cf4ad9e16..43dd83eda4 100644 --- a/web/i18n/th-TH/workflow.ts +++ b/web/i18n/th-TH/workflow.ts @@ -42,8 +42,6 @@ const translation = { setVarValuePlaceholder: 'ตั้งค่าตัวแปร', needConnectTip: 'ขั้นตอนนี้ไม่ได้เชื่อมต่อกับสิ่งใด', maxTreeDepth: 'ขีดจํากัดสูงสุดของ {{depth}} โหนดต่อสาขา', - needEndNode: 'ต้องเพิ่มบล็อก End', - needAnswerNode: 'ต้องเพิ่มบล็อกคําตอบ', workflowProcess: 'กระบวนการเวิร์กโฟลว์', notRunning: 'ยังไม่ได้ทํางาน', previewPlaceholder: 'ป้อนเนื้อหาในช่องด้านล่างเพื่อเริ่มแก้ไขข้อบกพร่องของแชทบอท', @@ -62,7 +60,6 @@ const translation = { learnMore: 'ศึกษาเพิ่มเติม', copy: 'ลอก', duplicate: 'สำเนา', - addBlock: 'เพิ่มบล็อก', pasteHere: 'วางที่นี่', pointerMode: 'โหมดตัวชี้', handMode: 'โหมดมือ', @@ -106,6 +103,18 @@ const translation = { addFailureBranch: 'เพิ่มสาขา Fail', loadMore: 'โหลดเวิร์กโฟลว์เพิ่มเติม', noHistory: 'ไม่มีประวัติ', + versionHistory: 'ประวัติรุ่น', + exportPNG: 'ส่งออกเป็น PNG', + noExist: 'ไม่มีตัวแปรดังกล่าว', + exportJPEG: 'ส่งออกเป็น JPEG', + referenceVar: 'ตัวแปรอ้างอิง', + publishUpdate: 'เผยแพร่การอัปเดต', + exitVersions: 'ออกเวอร์ชัน', + exportImage: 'ส่งออกภาพ', + exportSVG: 'ส่งออกเป็น SVG', + needAnswerNode: 'ต้องเพิ่มโหนดคำตอบ', + addBlock: 'เพิ่มโนด', + needEndNode: 'ต้องเพิ่มโหนดจบ', }, env: { envPanelTitle: 'ตัวแปรสภาพแวดล้อม', @@ -167,19 +176,19 @@ const translation = { stepForward_other: '{{count}} ก้าวไปข้างหน้า', sessionStart: 'เริ่มเซสชัน', currentState: 'สถานะปัจจุบัน', - nodeTitleChange: 'เปลี่ยนชื่อบล็อก', - nodeDescriptionChange: 'คําอธิบายบล็อกเปลี่ยนไป', - nodeDragStop: 'บล็อกย้าย', - nodeChange: 'บล็อกเปลี่ยนไป', - nodeConnect: 'บล็อกเชื่อมต่อ', - nodePaste: 'บล็อกวาง', - nodeDelete: 'บล็อกลบ', - nodeAdd: 'เพิ่มบล็อก', - nodeResize: 'บล็อกปรับขนาด', noteAdd: 'เพิ่มหมายเหตุ', noteChange: 'เปลี่ยนหมายเหตุ', noteDelete: 'ลบโน้ต', - edgeDelete: 'บล็อกตัดการเชื่อมต่อ', + nodeDelete: 'โหนดถูกลบแล้ว', + nodeDescriptionChange: 'คำอธิบายของโหนดถูกเปลี่ยน', + nodeDragStop: 'โหนดถูกย้าย', + edgeDelete: 'เชื่อมต่อ Node ขาดหาย', + nodeTitleChange: 'ชื่อโหนดเปลี่ยน', + nodeAdd: 'เพิ่มโนด', + nodeChange: 'โหนดเปลี่ยนแปลง', + nodeResize: 'ขนาดของโหนดถูกปรับขนาด', + nodeConnect: 'เชื่อมต่อ Node', + nodePaste: 'โนดที่วางไว้', }, errorMsg: { fieldRequired: '{{field}} เป็นสิ่งจําเป็น', @@ -205,10 +214,9 @@ const translation = { testRunIteration: 'การทดสอบการทําซ้ํา', back: 'ย้อนกลับ', iteration: 'เกิด ซ้ำ', + loop: 'ลูป', }, tabs: { - 'searchBlock': 'บล็อกการค้นหา', - 'blocks': 'บล็อก', 'searchTool': 'เครื่องมือค้นหา', 'tools': 'เครื่อง มือ', 'allTool': 'ทั้งหมด', @@ -222,6 +230,8 @@ const translation = { 'noResult': 'ไม่พบการจับคู่', 'agent': 'กลยุทธ์ตัวแทน', 'plugin': 'ปลั๊กอิน', + 'searchBlock': 'ค้นหาโหนด', + 'blocks': 'โหนด', }, blocks: { 'start': 'เริ่ม', @@ -243,6 +253,9 @@ const translation = { 'document-extractor': 'ตัวแยกเอกสาร', 'list-operator': 'ตัวดําเนินการรายการ', 'agent': 'ตัวแทน', + 'loop': 'ลูป', + 'loop-start': 'เริ่มลูป', + 'loop-end': 'ออกจากลูป', }, blocksAbout: { 'start': 'กําหนดพารามิเตอร์เริ่มต้นสําหรับการเปิดใช้เวิร์กโฟลว์', @@ -263,6 +276,8 @@ const translation = { 'document-extractor': 'ใช้เพื่อแยกวิเคราะห์เอกสารที่อัปโหลดเป็นเนื้อหาข้อความที่ LLM เข้าใจได้ง่าย', 'list-operator': 'ใช้เพื่อกรองหรือจัดเรียงเนื้อหาอาร์เรย์', 'agent': 'การเรียกใช้โมเดลภาษาขนาดใหญ่เพื่อตอบคําถามหรือประมวลผลภาษาธรรมชาติ', + 'loop': 'ดำเนินการลูปของตรรกะจนกว่าจะถึงเงื่อนไขการสิ้นสุดหรือตรงตามจำนวนลูปสูงสุดที่กำหนด.', + 'loop-end': 'เทียบเท่ากับ "break" โหนดนี้ไม่มีรายการการกำหนดค่า เมื่อร่างกายของลูปถึงโหนดนี้ ลูปจะสิ้นสุดลง.', }, operator: { zoomIn: 'ซูมเข้า', @@ -273,20 +288,21 @@ const translation = { }, panel: { userInputField: 'ฟิลด์ป้อนข้อมูลของผู้ใช้', - changeBlock: 'เปลี่ยนบล็อก', helpLink: 'ลิงค์ช่วยเหลือ', about: 'ประมาณ', createdBy: 'สร้างโดย', nextStep: 'ขั้นตอนถัดไป', - addNextStep: 'เพิ่มบล็อกถัดไปในเวิร์กโฟลว์นี้', - selectNextStep: 'เลือกบล็อกถัดไป', runThisStep: 'เรียกใช้ขั้นตอนนี้', checklist: 'ตรวจ สอบ', checklistTip: 'ตรวจสอบให้แน่ใจว่าปัญหาทั้งหมดได้รับการแก้ไขแล้วก่อนที่จะเผยแพร่', checklistResolved: 'ปัญหาทั้งหมดได้รับการแก้ไขแล้ว', - organizeBlocks: 'จัดระเบียบบล็อก', change: 'เปลี่ยน', optional: '(ไม่บังคับ)', + moveToThisNode: 'ย้ายไปที่โหนดนี้', + organizeBlocks: 'จัดระเบียบโหนด', + addNextStep: 'เพิ่มขั้นตอนถัดไปในกระบวนการทำงานนี้', + changeBlock: 'เปลี่ยนโหนด', + selectNextStep: 'เลือกขั้นตอนถัดไป', }, nodes: { common: { @@ -404,6 +420,34 @@ const translation = { variable: 'ตัวแปร', }, sysQueryInUser: 'sys.query ในข้อความผู้ใช้เป็นสิ่งจําเป็น', + jsonSchema: { + warningTips: { + saveSchema: 'กรุณาแก้ไขฟิลด์ปัจจุบันให้เสร็จก่อนที่จะบันทึกสคีมา', + }, + apply: 'สมัคร', + resetDefaults: 'รีเซ็ต', + generate: 'สร้าง', + import: 'นำเข้าจาก JSON', + descriptionPlaceholder: 'เพิ่มคำอธิบาย', + instruction: 'คำแนะนำ', + generating: 'กำลังสร้าง JSON Schema...', + resultTip: 'นี่คือผลลัพธ์ที่สร้างขึ้น หากคุณไม่พอใจ คุณสามารถกลับไปและแก้ไขคำสั่งของคุณได้', + regenerate: 'สร้างใหม่', + title: 'รูปแบบข้อมูลที่จัดระเบียบ', + promptPlaceholder: 'โปรดอธิบาย JSON Schema ของคุณ...', + generatedResult: 'ผลลัพธ์ที่สร้างขึ้น', + generateJsonSchema: 'สร้าง JSON Schema', + promptTooltip: 'แปลงคำอธิบายข้อความเป็นโครงสร้าง JSON Schema มาตรฐาน.', + showAdvancedOptions: 'แสดงตัวเลือกขั้นสูง', + addField: 'เพิ่มฟิลด์', + back: 'กลับ', + fieldNamePlaceholder: 'ชื่อฟิลด์', + generationTip: 'คุณสามารถใช้ภาษาธรรมชาติในการสร้าง JSON Schema ได้อย่างรวดเร็ว.', + doc: 'เรียนรู้เพิ่มเติมเกี่ยวกับผลลัพธ์ที่มีโครงสร้าง', + addChildField: 'เพิ่มฟิลด์เด็ก', + stringValidations: 'การตรวจสอบสตริง', + required: 'จำเป็นต้องใช้', + }, }, knowledgeRetrieval: { queryVariable: 'ตัวแปรแบบสอบถาม', @@ -416,6 +460,33 @@ const translation = { url: 'URL ที่แบ่งกลุ่ม', metadata: 'ข้อมูลเมตาอื่นๆ', }, + metadata: { + options: { + disabled: { + title: 'คนพิการ', + subTitle: 'ไม่ได้เปิดใช้งานการกรองข้อมูลเมตา', + }, + automatic: { + desc: 'สร้างเงื่อนไขการกรองข้อมูลเมตาโดยอัตโนมัติตามตัวแปรค้นหา', + title: 'อัตโนมัติ', + subTitle: 'สร้างเงื่อนไขการกรองข้อมูลเมตาโดยอัตโนมัติตามการค้นหาของผู้ใช้', + }, + manual: { + subTitle: 'เพิ่มเงื่อนไขการกรองข้อมูลเมตาด้วยตนเอง', + title: 'คู่มือ', + }, + }, + panel: { + conditions: 'เงื่อนไข', + search: 'ค้นหาข้อมูลเมตา', + add: 'เพิ่มเงื่อนไข', + datePlaceholder: 'เลือกเวลา...', + title: 'เงื่อนไขการกรองข้อมูลเมตา', + select: 'เลือกตัวแปร...', + placeholder: 'ใส่ค่า', + }, + title: 'การกรองข้อมูลเมตา', + }, }, http: { inputVars: 'ตัวแปรอินพุต', @@ -504,6 +575,8 @@ const translation = { 'all of': 'ทั้งหมด', 'exists': 'อยู่', 'not exists': 'ไม่มีอยู่จริง', + 'before': 'ก่อน', + 'after': 'หลังจากนั้น', }, optionName: { image: 'ภาพ', @@ -519,6 +592,7 @@ const translation = { selectVariable: 'เลือกตัวแปร...', addSubVariable: 'ตัวแปรย่อย', select: 'เลือก', + condition: 'เงื่อนไข', }, variableAssigner: { title: 'กําหนดตัวแปร', @@ -561,6 +635,8 @@ const translation = { 'over-write': 'เขียน ทับ', '+=': '+=', '/=': '/=', + 'remove-last': 'ลบสุดท้าย', + 'remove-first': 'ลบอันดับแรก', }, 'noAssignedVars': 'ไม่มีตัวแปรที่กําหนด', 'selectAssignedVariable': 'เลือกตัวแปรที่กําหนด...', @@ -571,7 +647,6 @@ const translation = { 'setParameter': 'ตั้งค่าพารามิเตอร์...', }, tool: { - toAuthorize: 'เพื่ออนุญาต', inputVars: 'ตัวแปรอินพุต', outputVars: { text: 'เนื้อหาที่สร้างขึ้นด้วยเครื่องมือ', @@ -584,6 +659,7 @@ const translation = { }, json: 'เครื่องมือสร้าง JSON', }, + authorize: 'อนุญาต', }, questionClassifiers: { model: 'แบบ', @@ -765,6 +841,38 @@ const translation = { modelNotSelected: 'ไม่ได้เลือกรุ่น', linkToPlugin: 'ลิงก์ไปยังปลั๊กอิน', }, + loop: { + ErrorMethod: { + removeAbnormalOutput: 'ลบผลลัพธ์ที่ผิดปกติ', + operationTerminated: 'ถูกยกเลิก', + continueOnError: 'ดำเนินการต่อเมื่อมีข้อผิดพลาด', + }, + breakCondition: 'เงื่อนไขการหยุดลูป', + output: 'ตัวแปรเอาท์พุท', + error_one: '{{count}} ข้อผิดพลาด', + loop_one: '{{count}} ลูป', + loopMaxCount: 'จำนวนรอบสูงสุด', + errorResponseMethod: 'วิธีการตอบสนองข้อผิดพลาด', + loopVariables: 'ตัวแปรลูป', + deleteDesc: 'การลบโหนดลูปจะลบโหนดลูกทั้งหมด', + deleteTitle: 'ลบโหนดลูปหรือไม่?', + error_other: '{{count}} ข้อผิดพลาด', + loop_other: '{{count}} รอบ', + loopMaxCountError: 'กรุณาใส่จำนวนรอบสูงสุดที่ถูกต้อง ซึ่งอยู่ระหว่าง 1 ถึง {{maxCount}}', + comma: ',', + loopNode: 'น็อดลูป', + totalLoopCount: 'จำนวนรอบทั้งหมด: {{count}}', + setLoopVariables: 'กำหนดตัวแปรภายในขอบเขตของลูป', + input: 'การป้อนข้อมูล', + finalLoopVariables: 'ตัวแปรในลูปสุดท้าย', + inputMode: 'โหมดการนำเข้า', + currentLoop: 'วงจรปัจจุบัน', + initialLoopVariables: 'ตัวแปรในลูปเริ่มต้น', + currentLoopCount: 'จำนวนรอบปัจจุบัน: {{count}}', + variableName: 'ชื่อ ตัวแปร', + exitConditionTip: 'โหนดลูปต้องมีเงื่อนไขการออกอย่างน้อยหนึ่งเงื่อนไข', + breakConditionTip: 'แค่ตัวแปรภายในลูปที่มีเงื่อนไขการสิ้นสุดและตัวแปรสำหรับการสนทนาเท่านั้นที่สามารถอ้างอิงได้.', + }, }, tracing: { stopBy: 'แวะที่ {{user}}', @@ -776,6 +884,38 @@ const translation = { assignedVarsDescription: 'ตัวแปรที่กําหนดต้องเป็นตัวแปรที่เขียนได้ เช่น', noAssignedVars: 'ไม่มีตัวแปรที่กําหนด', }, + versionHistory: { + filter: { + onlyYours: 'เพียงของคุณเท่านั้น', + empty: 'ไม่พบประวัติการเวอร์ชันที่ตรงกัน', + onlyShowNamedVersions: 'แสดงเฉพาะรุ่นที่ตั้งชื่อ', + all: 'ทั้งหมด', + reset: 'รีเซ็ตตัวกรอง', + }, + editField: { + releaseNotes: 'บันทึกการเปิดตัว', + releaseNotesLengthLimit: 'หมายเหตุการปล่อยไม่สามารถเกิน {{limit}} ตัวอักษร', + titleLengthLimit: 'ชื่อเรื่องต้องไม่เกิน {{limit}} ตัวอักษร', + title: 'ชื่อเรื่อง', + }, + action: { + updateFailure: 'ไม่สามารถอัปเดตเวอร์ชันได้', + deleteFailure: 'ลบเวอร์ชันไม่สำเร็จ', + deleteSuccess: 'เวอร์ชันถูกลบ', + restoreSuccess: 'เวอร์ชันที่กู้คืน', + restoreFailure: 'ไม่สามารถกู้คืนเวอร์ชันได้', + updateSuccess: 'อัปเดตเวอร์ชัน', + }, + releaseNotesPlaceholder: 'อธิบายว่าสิ่งที่เปลี่ยนแปลงไปคืออะไร', + currentDraft: 'ร่างปัจจุบัน', + editVersionInfo: 'แก้ไขข้อมูลเวอร์ชัน', + restorationTip: 'หลังจากการกู้คืนเวอร์ชันแล้ว ร่างปัจจุบันจะถูกเขียนทับ.', + defaultName: 'เวอร์ชันที่ไม่มีชื่อ', + deletionTip: 'การลบไม่สามารถย้อนกลับได้ กรุณายืนยัน.', + nameThisVersion: 'ชื่อเวอร์ชันนี้', + title: 'เวอร์ชัน', + latest: 'ล่าสุด', + }, } export default translation diff --git a/web/i18n/tr-TR/app-overview.ts b/web/i18n/tr-TR/app-overview.ts index f7203e2f59..f6c16553f1 100644 --- a/web/i18n/tr-TR/app-overview.ts +++ b/web/i18n/tr-TR/app-overview.ts @@ -30,25 +30,25 @@ const translation = { overview: { title: 'Genel Bakış', appInfo: { - explanation: 'Kullanıma hazır AI WebApp', + explanation: 'Kullanıma hazır AI web app', accessibleAddress: 'Genel URL', preview: 'Önizleme', regenerate: 'Yeniden Oluştur', regenerateNotice: 'Genel URL\'yi yeniden oluşturmak istiyor musunuz?', - preUseReminder: 'Devam etmeden önce WebApp\'i etkinleştirin.', + preUseReminder: 'Devam etmeden önce web app\'i etkinleştirin.', settings: { entry: 'Ayarlar', - title: 'WebApp Ayarları', - webName: 'WebApp İsmi', - webDesc: 'WebApp Açıklaması', + title: 'web app Ayarları', + webName: 'web app İsmi', + webDesc: 'web app Açıklaması', webDescTip: 'Bu metin, uygulamanın nasıl kullanılacağına dair temel açıklamalar sağlar ve istemci tarafında görüntülenir', - webDescPlaceholder: 'WebApp\'in açıklamasını girin', + webDescPlaceholder: 'web app\'in açıklamasını girin', language: 'Dil', workflow: { title: 'Workflow Adımları', show: 'Göster', hide: 'Gizle', - showDesc: 'WebApp\'te iş akışı ayrıntılarını gösterme veya gizleme', + showDesc: 'web app\'te iş akışı ayrıntılarını gösterme veya gizleme', subTitle: 'İş Akışı Detayları', }, chatColorTheme: 'Sohbet renk teması', @@ -70,10 +70,10 @@ const translation = { copyrightTooltip: 'Lütfen Profesyonel plana veya daha yüksek bir plana yükseltin', }, sso: { - title: 'WebApp SSO\'su', - tooltip: 'WebApp SSO\'yu etkinleştirmek için yöneticiyle iletişime geçin', + title: 'web app SSO\'su', + tooltip: 'web app SSO\'yu etkinleştirmek için yöneticiyle iletişime geçin', label: 'SSO Kimlik Doğrulaması', - description: 'Tüm kullanıcıların WebApp\'i kullanmadan önce SSO ile oturum açmaları gerekir', + description: 'Tüm kullanıcıların web app\'i kullanmadan önce SSO ile oturum açmaları gerekir', }, modalTip: 'İstemci tarafı web uygulaması ayarları.', }, @@ -95,7 +95,7 @@ const translation = { customize: { way: 'yol', entry: 'Özelleştir', - title: 'AI WebApp\'i Özelleştirin', + title: 'AI web app\'i Özelleştirin', explanation: 'Web Uygulamasının ön yüzünü senaryo ve stil ihtiyaçlarınıza uygun şekilde özelleştirebilirsiniz.', way1: { name: 'İstemci kodunu forklayarak değiştirin ve Vercel\'e dağıtın (önerilen)', diff --git a/web/i18n/tr-TR/app.ts b/web/i18n/tr-TR/app.ts index f205bd8ae4..5e55ffa349 100644 --- a/web/i18n/tr-TR/app.ts +++ b/web/i18n/tr-TR/app.ts @@ -78,19 +78,19 @@ const translation = { optional: 'Opsiyonel', foundResult: '{{sayı}} Sonuç', noTemplateFound: 'Şablon bulunamadı', - workflowUserDescription: 'Otomasyon ve toplu işleme gibi tek turlu görevler için iş akışı düzenlemesi.', - advancedUserDescription: 'Bellek özelliklerine sahip çok yönlü karmaşık diyalog görevleri için iş akışı orkestrasyonu.', + workflowUserDescription: 'Sürükle-bırak kolaylığıyla görsel olarak otonom yapay zeka iş akışları oluşturun.', + advancedUserDescription: 'Ek bellek özellikleri ve sohbet robotu arayüzü ile iş akışı.', completionShortDescription: 'Metin oluşturma görevleri için yapay zeka asistanı', noTemplateFoundTip: 'Farklı anahtar kelimeler kullanarak arama yapmayı deneyin.', learnMore: 'Daha fazla bilgi edinin', agentShortDescription: 'Akıl yürütme ve otonom araç kullanımına sahip akıllı ajan', - forBeginners: 'YENI BAŞLAYANLAR IÇIN', - workflowShortDescription: 'Tek dönüşlü otomasyon görevleri için orkestrasyon', + forBeginners: 'Daha temel uygulama türleri', + workflowShortDescription: 'Akıllı otomasyonlar için ajantik akış', agentUserDescription: 'Görev hedeflerine ulaşmak için yinelemeli akıl yürütme ve otonom araç kullanımı yeteneğine sahip akıllı bir ajan.', chooseAppType: 'Uygulama Türünü Seçin', completionUserDescription: 'Basit yapılandırmayla metin oluşturma görevleri için hızlı bir şekilde bir yapay zeka asistanı oluşturun.', chatbotShortDescription: 'Basit kurulumlu LLM tabanlı sohbet robotu', - advancedShortDescription: 'Hafızalı karmaşık çok dönüşlü diyaloglar için iş akışı', + advancedShortDescription: 'Çok turlu sohbetler için geliştirilmiş iş akışı', noIdeaTip: 'Fikriniz yok mu? Şablonlarımıza göz atın', forAdvanced: 'İLERI DÜZEY KULLANICILAR IÇIN', }, @@ -159,11 +159,15 @@ const translation = { title: 'Opik Belediyesi', description: 'Opik, LLM uygulamalarını değerlendirmek, test etmek ve izlemek için açık kaynaklı bir platformdur.', }, + weave: { + title: 'Dokuma', + description: 'Weave, LLM uygulamalarını değerlendirmek, test etmek ve izlemek için açık kaynaklı bir platformdur.', + }, }, answerIcon: { - descriptionInExplore: 'Keşfet\'te değiştirilecek 🤖 WebApp simgesinin kullanılıp kullanılmayacağı', - title: 'Değiştirmek 🤖 için WebApp simgesini kullanın', - description: 'Paylaşılan uygulamada değiştirmek 🤖 için WebApp simgesinin kullanılıp kullanılmayacağı', + descriptionInExplore: 'Keşfet\'te değiştirilecek 🤖 web app simgesinin kullanılıp kullanılmayacağı', + title: 'Değiştirmek 🤖 için web app simgesini kullanın', + description: 'Paylaşılan uygulamada değiştirmek 🤖 için web app simgesinin kullanılıp kullanılmayacağı', }, mermaid: { handDrawn: 'Elle çizilmiş', @@ -190,6 +194,54 @@ const translation = { placeholder: 'Bir uygulama seçin...', params: 'UYGULAMA PARAMETRELERI', }, + structOutput: { + required: 'Gerekli', + structured: 'Yapılandırılmış', + LLMResponse: 'LLM Yanıtı', + notConfiguredTip: 'Yapılandırılmış çıktı henüz yapılandırılmamış.', + configure: 'Yapılandır', + modelNotSupported: 'Model desteklenmiyor', + moreFillTip: 'Maksimum 10 katmanlı iç içe geçişleri gösterme', + modelNotSupportedTip: 'Mevcut model bu özelliği desteklemiyor ve otomatik olarak prompt enjeksiyonuna düşürülüyor.', + structuredTip: 'Yapılandırılmış Çıktılar, modelin sağladığınız JSON Şemasına uyacak şekilde her zaman yanıtlar üretmesini sağlayan bir özelliktir.', + }, + accessItemsDescription: { + anyone: 'Herkes web uygulamasına erişebilir', + organization: 'Kuruluşta herkes web uygulamasına erişebilir.', + specific: 'Sadece belirli gruplar veya üyeler web uygulamasına erişebilir.', + external: 'Sadece kimliği doğrulanmış dış kullanıcılar Web uygulamasına erişebilir', + }, + accessControlDialog: { + accessItems: { + anyone: 'Bağlantıya sahip olan herkes', + organization: 'Sadece işletme içindeki üyeler', + specific: 'Belirli gruplar veya üyeler', + external: 'Kimliği onaylanmış harici kullanıcılar', + }, + operateGroupAndMember: { + searchPlaceholder: 'Grupları ve üyeleri ara', + expand: 'Genişlet', + allMembers: 'Tüm üyeler', + noResult: 'Sonuç yok', + }, + title: 'Web Uygulaması Erişim Kontrolü', + description: 'Web uygulaması erişim izinlerini ayarlayın', + accessLabel: 'Kimin erişimi var', + groups_other: '{{count}} GRUP', + members_one: '{{count}} ÜYE', + members_other: '{{count}} ÜYE', + noGroupsOrMembers: 'Seçilen grup veya üye yok', + webAppSSONotEnabledTip: 'Lütfen web uygulaması kimlik doğrulama yöntemini yapılandırmak için kurumsal yöneticinizle iletişime geçin.', + updateSuccess: 'Başarıyla güncellendi', + groups_one: '{{count}} GRUP', + }, + publishApp: { + title: 'Web uygulamasına kim erişebilir', + notSet: 'Ayar yapılmamış', + notSetDesc: 'Şu anda kimse web uygulamasına erişemiyor. Lütfen izinleri ayarlayın.', + }, + accessControl: 'Web Uygulaması Erişim Kontrolü', + noAccessPermission: 'Web uygulamasına erişim izni yok', } export default translation diff --git a/web/i18n/tr-TR/billing.ts b/web/i18n/tr-TR/billing.ts index 1db14bb149..fd51bae648 100644 --- a/web/i18n/tr-TR/billing.ts +++ b/web/i18n/tr-TR/billing.ts @@ -70,6 +70,7 @@ const translation = { messageRequest: { title: 'Mesaj Kredileri', tooltip: 'OpenAI modellerini (gpt4 hariç) kullanarak çeşitli planlar için mesaj çağrı kotaları. Limitin üzerindeki mesajlar OpenAI API Anahtarınızı kullanır.', + titlePerMonth: '{{count,number}} mesaj/ay', }, annotatedResponse: { title: 'Ek Açıklama Kota Sınırları', @@ -77,27 +78,94 @@ const translation = { }, ragAPIRequestTooltip: 'Dify\'nin sadece bilgi tabanı işleme yeteneklerini çağıran API çağrıları sayısını ifade eder.', receiptInfo: 'Sadece takım sahibi ve takım yöneticisi abone olabilir ve faturalandırma bilgilerini görüntüleyebilir', + documentsTooltip: 'Bilgi Veri Kaynağından ithal edilen belge sayısına kota.', + freeTrialTipSuffix: 'Kredi kartı gerekmez', + freeTrialTipPrefix: 'Kaydolun ve bir', + priceTip: 'iş alanı başına/', + documentsRequestQuota: '{{count,number}}/dakika Bilgi İsteği Oran Limiti', + apiRateLimitUnit: '{{count,number}}/gün', + documents: '{{count,number}} Bilgi Belgesi', + comparePlanAndFeatures: 'Planları ve özellikleri karşılaştır', + self: 'Kendi Barındırılan', + getStarted: 'Başlayın', + annualBilling: 'Yıllık Faturalama', + teamMember_one: '{{count,number}} Takım Üyesi', + apiRateLimit: 'API Hız Limiti', + cloud: 'Bulut Hizmeti', + teamMember_other: '{{count,number}} Takım Üyesi', + apiRateLimitTooltip: 'Dify API\'si aracılığıyla yapılan tüm isteklerde, metin oluşturma, sohbet konuşmaları, iş akışı yürütmeleri ve belge işleme dahil olmak üzere, API Oran Sınırı uygulanır.', + unlimitedApiRate: 'API Hız Sınırı Yok', + freeTrialTip: '200 OpenAI çağrısının ücretsiz denemesi.', + teamWorkspace: '{{count,number}} Takım Çalışma Alanı', + documentsRequestQuotaTooltip: 'Bir çalışma alanının bilgi tabanında, veri seti oluşturma, silme, güncellemeler, belge yüklemeleri, değişiklikler, arşivleme ve bilgi tabanı sorguları dahil olmak üzere, dakikada gerçekleştirebileceği toplam işlem sayısını belirtir. Bu ölçüt, bilgi tabanı taleplerinin performansını değerlendirmek için kullanılır. Örneğin, bir Sandbox kullanıcısı bir dakika içinde ardışık 10 vurma testi gerçekleştirirse, çalışma alanı bir sonraki dakika için aşağıdaki işlemleri gerçekleştirmesi geçici olarak kısıtlanacaktır: veri seti oluşturma, silme, güncellemeler ve belge yüklemeleri veya değişiklikler.', }, plans: { sandbox: { name: 'Sandbox', description: '200 kez GPT ücretsiz deneme', includesTitle: 'İçerdikleri:', + for: 'Temel Yeteneklerin Ücretsiz Denemesi', }, professional: { name: 'Profesyonel', description: 'Bireyler ve küçük takımlar için daha fazla güç açın.', includesTitle: 'Ücretsiz plandaki her şey, artı:', + for: 'Bağımsız Geliştiriciler/Küçük Takımlar için', }, team: { name: 'Takım', description: 'Sınırsız işbirliği ve en üst düzey performans.', includesTitle: 'Profesyonel plandaki her şey, artı:', + for: 'Orta Boyutlu Takımlar İçin', }, enterprise: { name: 'Kurumsal', description: 'Büyük ölçekli kritik sistemler için tam yetenekler ve destek.', includesTitle: 'Takım plandaki her şey, artı:', + features: { + 3: 'Birden Fazla Çalışma Alanı ve Kurumsal Yönetim', + 8: 'Profesyonel Teknik Destek', + 4: 'SSO', + 2: 'Özel Şirket Özellikleri', + 1: 'Ticari Lisans Yetkilendirmesi', + 7: 'Dify Tarafından Resmi Güncellemeler ve Bakım', + 5: 'Dify Ortakları tarafından müzakere edilen SLA\'lar', + 6: 'Gelişmiş Güvenlik ve Kontroller', + 0: 'Kurumsal Düzeyde Ölçeklenebilir Dağıtım Çözümleri', + }, + priceTip: 'Yıllık Faturalama Sadece', + for: 'Büyük boyutlu Takımlar için', + btnText: 'Satış ile İletişime Geç', + price: 'Özel', + }, + community: { + features: { + 1: 'Tek İş Alanı', + 0: 'Tüm Temel Özellikler Kamu Deposu Altında Yayınlandı', + 2: 'Dify Açık Kaynak Lisansına uyar', + }, + price: 'Ücretsiz', + includesTitle: 'Ücretsiz Özellikler:', + name: 'Topluluk', + btnText: 'Topluluğa Başlayın', + for: 'Bireysel Kullanıcılar, Küçük Ekipler veya Ticari Olmayan Projeler İçin', + description: 'Bireysel Kullanıcılar, Küçük Ekipler veya Ticari Olmayan Projeler İçin', + }, + premium: { + features: { + 1: 'Tek İş Alanı', + 0: 'Çeşitli Bulut Sağlayıcıları Tarafından Kendiliğinden Yönetilen Güvenilirlik', + 3: 'Öncelikli Email ve Sohbet Desteği', + 2: 'Web Uygulaması Logo ve Markalaşma Özelleştirmesi', + }, + name: 'Premium', + includesTitle: 'Topluluktan her şey, artı:', + for: 'Orta Büyüklükteki Organizasyonlar ve Ekipler için', + price: 'Ölçeklenebilir', + btnText: 'Premium alın', + priceTip: 'Bulut Pazarına Dayalı', + description: 'Orta Büyüklükteki Organizasyonlar ve Ekipler için', + comingSoon: 'Microsoft Azure ve Google Cloud Desteği Yakında Geliyor', }, }, vectorSpace: { @@ -107,12 +175,26 @@ const translation = { apps: { fullTipLine1: 'Daha fazla uygulama oluşturmak için', fullTipLine2: 'planınızı yükseltin.', + contactUs: 'Bizimle iletişime geçin', + fullTip2des: 'Kullanımı serbest bırakmak için etkisiz uygulamaların temizlenmesi önerilir veya bizimle iletişime geçin.', + fullTip1des: 'Bu planda uygulama oluşturma limitine ulaştınız.', + fullTip2: 'Plan limiti aşıldı', + fullTip1: 'Daha fazla uygulama oluşturmak için yükseltin', }, annotatedResponse: { fullTipLine1: 'Daha fazla konuşmayı açıklamak için', fullTipLine2: 'planınızı yükseltin.', quotaTitle: 'Ek Açıklama Yanıtı Kotası', }, + usagePage: { + teamMembers: 'Ekip Üyeleri', + vectorSpaceTooltip: 'Yüksek Kalite indeksleme moduna sahip belgeler, Bilgi Veri Depolama kaynaklarını tüketir. Bilgi Veri Depolama sınırına ulaştığında, yeni belgeler yüklenmeyecek.', + vectorSpace: 'Bilgi Veri Depolama', + buildApps: 'Uygulama Geliştir', + annotationQuota: 'Notlandırma Kotası', + documentsUploadQuota: 'Belgeler Yükleme Kotası', + }, + teamMembers: 'Ekip Üyeleri', } export default translation diff --git a/web/i18n/tr-TR/common.ts b/web/i18n/tr-TR/common.ts index 9dd2f2dd7e..62ce150986 100644 --- a/web/i18n/tr-TR/common.ts +++ b/web/i18n/tr-TR/common.ts @@ -54,6 +54,10 @@ const translation = { copied: 'Kopya -lanan', in: 'içinde', viewDetails: 'Detayları Görüntüle', + downloadSuccess: 'İndirme Tamamlandı.', + format: 'Format', + more: 'Daha fazla', + downloadFailed: 'İndirme başarısız oldu. Lütfen daha sonra tekrar deneyin.', }, errorMsg: { fieldRequired: '{{field}} gereklidir', @@ -145,6 +149,8 @@ const translation = { newDataset: 'Bilgi Oluştur', tools: 'Araçlar', exploreMarketplace: 'Marketplace\'i Keşfedin', + appDetail: 'Uygulama Detayı', + account: 'Hesap', }, userProfile: { settings: 'Ayarlar', @@ -157,6 +163,9 @@ const translation = { community: 'Topluluk', about: 'Hakkında', logout: 'Çıkış Yap', + support: 'Destek', + compliance: 'Uygunluk', + github: 'GitHub', }, settings: { accountGroup: 'HESAP', @@ -206,6 +215,9 @@ const translation = { permanentlyDeleteButton: 'Hesabı Kalıcı Olarak Sil', deletePrivacyLinkTip: 'Verilerinizi nasıl işlediğimiz hakkında daha fazla bilgi için lütfen', sendVerificationButton: 'Doğrulama Kodu Gönder', + workspaceName: 'Çalışma Alanı Adı', + workspaceIcon: 'Çalışma Alanı İkonu', + editWorkspaceInfo: 'Çalışma Alanı Bilgilerini Düzenle', }, members: { team: 'Takım', @@ -459,7 +471,7 @@ const translation = { apiBasedExtension: { title: 'API uzantıları merkezi API yönetimi sağlar, Dify\'nin uygulamaları arasında kolay kullanım için yapılandırmayı basitleştirir.', link: 'Kendi API Uzantınızı nasıl geliştireceğinizi öğrenin.', - linkUrl: 'https://docs.dify.ai/features/extension/api_based_extension', + linkUrl: 'https://docs.dify.ai/en/guides/extension/api-based-extension/README', add: 'API Uzantısı Ekle', selector: { title: 'API Uzantısı', @@ -544,9 +556,10 @@ const translation = { vectorHash: 'Vektör Hash:', hitScore: 'Geri Alım Skoru:', }, - inputPlaceholder: 'Bot ile konuş', + inputPlaceholder: '{{botName}} ile konuş', thought: 'Düşünce', thinking: 'Düşünü...', + resend: 'Yeniden gönder', }, promptEditor: { placeholder: 'Prompt kelimenizi buraya yazın, değişken eklemek için \'{\' tuşuna, prompt içerik bloğu eklemek için \'/\' tuşuna basın', @@ -633,10 +646,31 @@ const translation = { license: { expiring_plural: '{{count}} gün içinde sona eriyor', expiring: 'Bir günde sona eriyor', + unlimited: 'Sınırsız', }, pagination: { perPage: 'Sayfa başına öğe sayısı', }, + theme: { + light: 'ışık', + dark: 'koyu', + auto: 'sistem', + theme: 'Tema', + }, + compliance: { + soc2Type1: 'SOC 2 Tip I Raporu', + sandboxUpgradeTooltip: 'Yalnızca Profesyonel veya Takım planı ile kullanılabilir.', + iso27001: 'ISO 27001:2022 Sertifikası', + professionalUpgradeTooltip: 'Yalnızca Takım planı veya üstü ile mevcuttur.', + gdpr: 'GDPR DPA', + soc2Type2: 'SOC 2 Tip II Raporu', + }, + imageInput: { + supportedFormats: 'PNG, JPG, JPEG, WEBP ve GIF\'i destekler', + dropImageHere: 'Görüntünüzü buraya bırakın veya', + browse: 'tarayıcı', + }, + you: 'Sen', } export default translation diff --git a/web/i18n/tr-TR/custom.ts b/web/i18n/tr-TR/custom.ts index d4526074d5..83135a1787 100644 --- a/web/i18n/tr-TR/custom.ts +++ b/web/i18n/tr-TR/custom.ts @@ -3,9 +3,11 @@ const translation = { upgradeTip: { prefix: 'Markanızı özelleştirmek için planınızı yükseltin', suffix: '.', + des: 'Markanızı özelleştirmek için planınızı yükseltin', + title: 'Planınızı yükseltin', }, webapp: { - title: 'WebApp markasını özelleştir', + title: 'web app markasını özelleştir', removeBrand: 'Powered by Dify\'i kaldır', changeLogo: 'Powered by Brand Resmini Değiştir', changeLogoTip: 'SVG veya PNG formatında, en az 40x40px boyutunda', diff --git a/web/i18n/tr-TR/dataset-creation.ts b/web/i18n/tr-TR/dataset-creation.ts index 1da6f97c4c..cb3cfcfac4 100644 --- a/web/i18n/tr-TR/dataset-creation.ts +++ b/web/i18n/tr-TR/dataset-creation.ts @@ -27,7 +27,7 @@ const translation = { }, uploader: { title: 'Dosya yükle', - button: 'Dosyayı sürükleyip bırakın veya', + button: 'Dosyaları veya klasörleri sürükleyip bırakın veya', browse: 'Göz atın', tip: 'Destekler {{supportTypes}}. Her biri en fazla {{size}}MB.', validation: { @@ -63,7 +63,7 @@ const translation = { run: 'Çalıştır', firecrawlTitle: '🔥Firecrawl ile web içeriğini çıkarın', firecrawlDoc: 'Firecrawl dokümanları', - firecrawlDocLink: 'https://docs.dify.ai/guides/knowledge-base/sync-from-website', + firecrawlDocLink: 'https://docs.dify.ai/en/guides/knowledge-base/create-knowledge-and-upload-documents/import-content-data/sync-from-website', options: 'Seçenekler', crawlSubPage: 'Alt sayfaları tarayın', limit: 'Sınır', @@ -87,6 +87,14 @@ const translation = { jinaReaderDoc: 'Jina Reader hakkında daha fazla bilgi edinin', jinaReaderNotConfigured: 'Jina Reader yapılandırılmadı', jinaReaderDocLink: 'https://jina.ai/reader', + waterCrawlNotConfiguredDescription: 'Watercrawl\'ı kullanmak için API anahtarı ile yapılandırın.', + configureFirecrawl: 'Firecrawl\'ı yapılandır', + watercrawlDoc: 'Watercrawl belgeleri', + waterCrawlNotConfigured: 'Watercrawl yapılandırılmamış', + watercrawlTitle: 'Watercrawl ile web içeriğini çıkar', + configureJinaReader: 'Jina Okuyucusunu Yapılandır', + watercrawlDocLink: 'https://docs.dify.ai/en/guides/knowledge-base/create-knowledge-and-upload-documents/import-content-data/sync-from-website', + configureWatercrawl: 'Watercrawl\'ı yapılandır', }, cancel: 'İptal', }, @@ -200,6 +208,11 @@ const translation = { description: 'Şu anda, Dify\'ın bilgi tabanı yalnızca sınırlı veri kaynaklarına sahiptir. Dify bilgi tabanına bir veri kaynağına katkıda bulunmak, tüm kullanıcılar için platformun esnekliğini ve gücünü artırmaya yardımcı olmanın harika bir yoludur. Katkı kılavuzumuz, başlamanızı kolaylaştırır. Daha fazla bilgi edinmek için lütfen aşağıdaki bağlantıya tıklayın.', title: 'Diğer veri kaynaklarına bağlanılıyor mu?', }, + watercrawl: { + configWatercrawl: 'Su Tarayıcısını Yapılandır', + apiKeyPlaceholder: 'watercrawl.dev\'den API anahtarı', + getApiKeyLinkText: 'API anahtarınızı watercrawl.dev\'den alın', + }, } export default translation diff --git a/web/i18n/tr-TR/dataset-settings.ts b/web/i18n/tr-TR/dataset-settings.ts index 554f3c7a5c..18fb004d1b 100644 --- a/web/i18n/tr-TR/dataset-settings.ts +++ b/web/i18n/tr-TR/dataset-settings.ts @@ -27,6 +27,7 @@ const translation = { learnMore: 'Daha fazla bilgi edinin', description: ' geri alım yöntemi hakkında.', longDescription: ' geri alım yöntemi hakkında, bunu Bilgi ayarlarında istediğiniz zaman değiştirebilirsiniz.', + method: 'Retrieval Yöntemi', }, save: 'Kaydet', retrievalSettings: 'Alma Ayarları', diff --git a/web/i18n/tr-TR/dataset.ts b/web/i18n/tr-TR/dataset.ts index 6183849ebc..96f120c13d 100644 --- a/web/i18n/tr-TR/dataset.ts +++ b/web/i18n/tr-TR/dataset.ts @@ -168,6 +168,54 @@ const translation = { enable: 'Etkinleştirmek', allKnowledge: 'Tüm Bilgiler', allKnowledgeDescription: 'Bu çalışma alanındaki tüm bilgileri görüntülemek için seçin. Yalnızca Çalışma Alanı Sahibi tüm bilgileri yönetebilir.', + metadata: { + createMetadata: { + namePlaceholder: 'Meta veri adı ekleyin', + back: 'Geri', + name: 'İsim', + type: 'Yaz', + title: 'Yeni Veriler', + }, + checkName: { + empty: 'Meta veri adı boş olamaz', + invalid: 'Meta verisi adı yalnızca küçük harfler, sayılar ve alt çizgiler içerebilir ve küçük bir harfle başlamalıdır.', + }, + batchEditMetadata: { + multipleValue: 'Birden Fazla Değer', + applyToAllSelectDocumentTip: 'Seçilen tüm belgeler için yukarıda düzenlenmiş ve yeni olan tüm meta verileri otomatik olarak oluşturun, aksi takdirde meta verileri düzenlemek yalnızca bununla ilgili belgelere uygulanacaktır.', + editDocumentsNum: '{{num}} belge düzenleniyor', + editMetadata: 'Meta Verileri Düzenle', + applyToAllSelectDocument: 'Seçilen tüm belgelere uygula', + }, + selectMetadata: { + newAction: 'Yeni Veriler', + manageAction: 'Yönet', + search: 'Arama meta verileri', + }, + datasetMetadata: { + disabled: 'Devre dışı bırakıldı.', + builtIn: 'Yerleşik', + values: '{{num}} Değerler', + builtInDescription: 'Yerleşik meta veriler otomatik olarak çıkarılır ve oluşturulur. Kullanımdan önce etkinleştirilmesi gerekir ve düzenlenemez.', + rename: 'Yeniden Adlandır', + addMetaData: 'Meta Verileri Ekle', + name: 'İsim', + deleteContent: 'Bu {{name}} meta verisini silmek istediğinizden emin misiniz?', + namePlaceholder: 'Meta veri adı', + deleteTitle: 'Silmek için onayla', + description: 'Bu bilgideki tüm meta verileri yönetebilirsiniz. Değişiklikler her belgeye senkronize edilecektir.', + }, + documentMetadata: { + documentInformation: 'Belge Bilgisi', + metadataToolTip: 'Meta veriler, bilgi alma doğruluğunu ve geçerliliğini artıran önemli bir filtre görevi görür. Bu belgede meta verileri burada değiştirebilir ve ekleyebilirsiniz.', + startLabeling: 'Etiketlemeye Başla', + technicalParameters: 'Teknik Parametreler', + }, + metadata: 'Veri Seti', + addMetadata: 'Meta Verileri Ekle', + chooseTime: 'Bir zaman seçin...', + }, + embeddingModelNotAvailable: 'Gömme modeli mevcut değil.', } export default translation diff --git a/web/i18n/tr-TR/education.ts b/web/i18n/tr-TR/education.ts new file mode 100644 index 0000000000..f6afb98e96 --- /dev/null +++ b/web/i18n/tr-TR/education.ts @@ -0,0 +1,47 @@ +const translation = { + toVerifiedTip: { + end: 'Dify Profesyonel Planı için.', + front: 'Artık Eğitim Doğrulandı statüsüne uygun oldunuz. Lütfen süreci tamamlamak ve bir almak için eğitim bilgilerinizi aşağıya girin.', + coupon: 'özel %100 kupon', + }, + form: { + schoolName: { + placeholder: 'Okulunuzun resmi, kısaltılmamış adını girin', + title: 'Okulunuzun Adı', + }, + schoolRole: { + option: { + student: 'Öğrenci', + teacher: 'Öğretmen', + administrator: 'Okul Yöneticisi', + }, + title: 'Okul Rolünüz', + }, + terms: { + desc: { + front: 'Eğitim Doğrulandı durumunuza ilişkin bilgileriniz ve kullanımınız, bizim', + termsOfService: 'Hizmet Şartları', + end: 'Göndererek:', + privacyPolicy: 'Gizlilik Politikası', + and: 've', + }, + option: { + age: 'En az 18 yaşında olduğumu onaylıyorum.', + inSchool: 'Verilen kurumda kayıtlı veya istihdamda olduğumu onaylıyorum. Dify, kayıt veya istihdam kanıtı talep edebilir. Uygunluğumu yanlış beyan edersem, eğitim durumuma dayalı olarak başlangıçta feragat edilen her türlü ücreti ödemeyi kabul ediyorum.', + }, + title: 'Şartlar ve Koşullar', + }, + }, + toVerified: 'Eğitim Bilgilerinizi Doğrulayın', + currentSigned: 'ŞU ANDA GİRİŞ YAPILDIĞI KİŞİ', + submitError: 'Form gönderimi başarısız oldu. Lütfen daha sonra tekrar deneyin.', + rejectTitle: 'Dify Eğitim Doğrulamanız Rededildi', + emailLabel: 'Şu anki e-posta adresin', + submit: 'Gönder', + rejectContent: 'Maalesef, Eğitim Doğrulama statüsüne uygun değilsiniz ve bu nedenle bu e-posta adresini kullanıyorsanız Dify Profesyonel Planı için özel %100 kuponu alamazsınız.', + learn: 'Eğitim doğrulamasının nasıl yapılacağını öğrenin', + successContent: 'Hesabınıza Dify Profesyonel planı için %100 indirim kuponu verdik. Kuponun geçerlilik süresi bir yıldır, lütfen bu süre içinde kullanın.', + successTitle: 'Dify Eğitim Onayınız Var', +} + +export default translation diff --git a/web/i18n/tr-TR/explore.ts b/web/i18n/tr-TR/explore.ts index 52b45c6b32..78b305ee47 100644 --- a/web/i18n/tr-TR/explore.ts +++ b/web/i18n/tr-TR/explore.ts @@ -37,6 +37,7 @@ const translation = { HR: 'İK', Agent: 'Aracı', Workflow: 'İş Akışı', + Entertainment: 'Eğlence', }, } diff --git a/web/i18n/tr-TR/login.ts b/web/i18n/tr-TR/login.ts index e742548dc5..e6471d935f 100644 --- a/web/i18n/tr-TR/login.ts +++ b/web/i18n/tr-TR/login.ts @@ -105,6 +105,11 @@ const translation = { licenseExpired: 'Lisansın Süresi Doldu', licenseLost: 'Lisans Kaybedildi', licenseInactive: 'Lisans Etkin Değil', + webapp: { + disabled: 'Web uygulaması kimlik doğrulaması devre dışı. Lütfen bu özelliği etkinleştirmesi için sistem yöneticisi ile iletişime geçin. Uygulamayı doğrudan kullanmayı deneyebilirsiniz.', + noLoginMethod: 'Web uygulaması için kimlik doğrulama yöntemi yapılandırılmamış', + noLoginMethodTip: 'Lütfen bir kimlik doğrulama yöntemi eklemek için sistem yöneticisi ile iletişime geçin.', + }, } export default translation diff --git a/web/i18n/tr-TR/plugin.ts b/web/i18n/tr-TR/plugin.ts index 4bab35950b..0b761b53b0 100644 --- a/web/i18n/tr-TR/plugin.ts +++ b/web/i18n/tr-TR/plugin.ts @@ -62,6 +62,7 @@ const translation = { params: 'AKIL YÜRÜTME YAPILANDIRMASI', paramsTip2: '\'Otomatik\' kapalıyken, varsayılan değer kullanılır.', unsupportedTitle: 'Desteklenmeyen Eylem', + toolSetting: 'Araç Ayarları', }, strategyNum: '{{sayı}} {{strateji}} DAHİL', switchVersion: 'Sürümü Değiştir', @@ -180,6 +181,8 @@ const translation = { noPluginFound: 'Eklenti bulunamadı', viewMore: 'Daha fazla göster', discover: 'Keşfetmek', + verifiedTip: 'Dify tarafından doğrulanmıştır.', + partnerTip: 'Dify partner\'ı tarafından doğrulandı', }, task: { installedError: '{{errorLength}} eklentileri yüklenemedi', @@ -194,7 +197,6 @@ const translation = { search: 'Aramak', install: '{{num}} yükleme', searchPlugins: 'Eklentileri ara', - submitPlugin: 'Eklenti gönder', searchTools: 'Arama araçları...', fromMarketplace: 'Pazar Yerinden', installPlugin: 'Eklentiyi yükle', @@ -204,6 +206,12 @@ const translation = { findMoreInMarketplace: 'Marketplace\'te daha fazla bilgi edinin', searchCategories: 'Arama Kategorileri', searchInMarketplace: 'Marketplace\'te arama yapma', + metadata: { + title: 'Eklentiler', + }, + difyVersionNotCompatible: 'Mevcut Dify sürümü bu eklentiyle uyumlu değil, lütfen gerekli minimum sürüme güncelleyin: {{minimalDifyVersion}}', + requestAPlugin: 'Bir eklenti iste', + publishPlugins: 'Eklentileri yayınlayın', } export default translation diff --git a/web/i18n/tr-TR/share-app.ts b/web/i18n/tr-TR/share-app.ts index 26c6f56fb4..e7ad4fcd68 100644 --- a/web/i18n/tr-TR/share-app.ts +++ b/web/i18n/tr-TR/share-app.ts @@ -26,6 +26,12 @@ const translation = { }, tryToSolve: 'Çözmeyi Dene', temporarySystemIssue: 'Üzgünüz, geçici sistem sorunu.', + expand: 'Genişlet', + collapse: 'Kısıtla', + chatSettingsTitle: 'Yeni sohbet kurulumu', + chatFormTip: 'Sohbet başladıktan sonra sohbet ayarları değiştirilemez.', + viewChatSettings: 'Sohbet ayarlarını görüntüle', + newChatTip: 'Zaten yeni bir sohbette', }, generation: { tabs: { @@ -64,6 +70,11 @@ const translation = { moreThanMaxLengthLine: 'Satır {{rowIndex}}: {{varName}} değeri {{maxLength}} karakterden fazla olamaz', atLeastOne: 'Lütfen yüklenen dosyada en az bir satır girin.', }, + execution: 'İFRAZAT', + executions: '{{num}} İDAM', + }, + login: { + backToHome: 'Ana Sayfaya Dön', }, } diff --git a/web/i18n/tr-TR/time.ts b/web/i18n/tr-TR/time.ts index e2410dd34b..f4cded0998 100644 --- a/web/i18n/tr-TR/time.ts +++ b/web/i18n/tr-TR/time.ts @@ -1,3 +1,37 @@ -const translation = {} +const translation = { + daysInWeek: { + Sat: 'Sat', + Thu: 'Perşembe', + Tue: 'Salı', + Mon: 'Mon', + Sun: 'Güneş', + Fri: 'Cuma', + Wed: 'Çarşamba', + }, + months: { + March: 'Mart', + December: 'Aralık', + October: 'Ekim', + September: 'Eylül', + July: 'Temmuz', + August: 'Ağustos', + June: 'Haziran', + November: 'Kasım', + February: 'Şubat', + April: 'Nisan', + May: 'Mayıs', + January: 'Ocak', + }, + operation: { + cancel: 'İptal', + now: 'Şimdi', + pickDate: 'Tarih Seç', + ok: 'Tamam', + }, + title: { + pickTime: 'Zamanı Seç', + }, + defaultPlaceholder: 'Bir zaman seç...', +} export default translation diff --git a/web/i18n/tr-TR/tools.ts b/web/i18n/tr-TR/tools.ts index d4b6725418..af9ddf182f 100644 --- a/web/i18n/tr-TR/tools.ts +++ b/web/i18n/tr-TR/tools.ts @@ -15,7 +15,6 @@ const translation = { }, author: 'Tarafından', auth: { - unauthorized: 'Yetki Ver', authorized: 'Yetkilendirildi', setup: 'Kullanmak için yetkilendirmeyi ayarla', setupModalTitle: 'Yetkilendirmeyi Ayarla', diff --git a/web/i18n/tr-TR/workflow.ts b/web/i18n/tr-TR/workflow.ts index 0ba1206b86..64449ab754 100644 --- a/web/i18n/tr-TR/workflow.ts +++ b/web/i18n/tr-TR/workflow.ts @@ -38,8 +38,6 @@ const translation = { setVarValuePlaceholder: 'Değişkeni ayarla', needConnectTip: 'Bu adım hiçbir şeye bağlı değil', maxTreeDepth: 'Her dal için maksimum {{depth}} düğüm limiti', - needEndNode: 'Son blok eklenmelidir', - needAnswerNode: 'Yanıt bloğu eklenmelidir', workflowProcess: 'Workflow Süreci', notRunning: 'Henüz çalıştırılmadı', previewPlaceholder: 'Sohbet Robotunu hata ayıklamak için aşağıdaki kutuya içerik girin', @@ -58,7 +56,6 @@ const translation = { learnMore: 'Daha Fazla Bilgi', copy: 'Kopyala', duplicate: 'Çoğalt', - addBlock: 'Blok Ekle', pasteHere: 'Buraya Yapıştır', pointerMode: 'İşaretçi Modu', handMode: 'El Modu', @@ -106,6 +103,18 @@ const translation = { addFailureBranch: 'Başarısız dal ekle', loadMore: 'Daha Fazla İş Akışı Yükleyin', noHistory: 'Tarih Yok', + exportPNG: 'PNG olarak dışa aktar', + exportImage: 'Resmi Dışa Aktar', + publishUpdate: 'Güncellemeyi Yayınla', + exitVersions: 'Çıkış Sürümleri', + versionHistory: 'Sürüm Geçmişi', + exportJPEG: 'JPEG olarak dışa aktar', + noExist: 'Böyle bir değişken yok', + exportSVG: 'SVG olarak dışa aktar', + referenceVar: 'Referans Değişken', + addBlock: 'Düğüm Ekle', + needAnswerNode: 'Cevap düğümü eklenmelidir.', + needEndNode: 'Son düğüm eklenmelidir', }, env: { envPanelTitle: 'Çevre Değişkenleri', @@ -167,19 +176,19 @@ const translation = { stepForward_other: '{{count}} adım ileri', sessionStart: 'Oturum Başladı', currentState: 'Geçerli Durum', - nodeTitleChange: 'Blok başlığı değiştirildi', - nodeDescriptionChange: 'Blok açıklaması değiştirildi', - nodeDragStop: 'Blok taşındı', - nodeChange: 'Blok değiştirildi', - nodeConnect: 'Blok bağlandı', - nodePaste: 'Blok yapıştırıldı', - nodeDelete: 'Blok silindi', - nodeAdd: 'Blok eklendi', - nodeResize: 'Blok yeniden boyutlandırıldı', noteAdd: 'Not eklendi', noteChange: 'Not değiştirildi', noteDelete: 'Not silindi', - edgeDelete: 'Blok bağlantısı kesildi', + nodeDragStop: 'Düğüm taşındı', + nodeConnect: 'Node bağlandı', + nodeDescriptionChange: 'Düğüm açıklaması değiştirildi', + edgeDelete: 'Düğüm bağlantısı kesildi', + nodeChange: 'Düğüm değişti', + nodeDelete: 'Düğüm silindi', + nodeResize: 'Düğüm boyutu değiştirildi', + nodeTitleChange: 'Düğüm başlığı değiştirildi', + nodeAdd: 'Düğüm eklendi', + nodePaste: 'Düğüm yapıştırıldı', }, errorMsg: { fieldRequired: '{{field}} gereklidir', @@ -205,10 +214,9 @@ const translation = { testRunIteration: 'Test Çalıştırma Yineleme', back: 'Geri', iteration: 'Yineleme', + loop: 'Döngü', }, tabs: { - 'searchBlock': 'Blok ara', - 'blocks': 'Bloklar', 'tools': 'Araçlar', 'allTool': 'Hepsi', 'builtInTool': 'Yerleşik', @@ -222,6 +230,8 @@ const translation = { 'searchTool': 'Arama aracı', 'agent': 'Temsilci Stratejisi', 'plugin': 'Eklenti', + 'blocks': 'Düğümler', + 'searchBlock': 'Arama düğümü', }, blocks: { 'start': 'Başlat', @@ -243,6 +253,9 @@ const translation = { 'list-operator': 'Liste İşleci', 'document-extractor': 'Doküman Çıkarıcı', 'agent': 'Aracı', + 'loop-start': 'Döngü Başlangıcı', + 'loop-end': 'Döngüden Çık', + 'loop': 'Döngü', }, blocksAbout: { 'start': 'Bir iş akışını başlatmak için başlangıç parametrelerini tanımlayın', @@ -263,6 +276,8 @@ const translation = { 'document-extractor': 'Yüklenen belgeleri LLM tarafından kolayca anlaşılabilen metin içeriğine ayrıştırmak için kullanılır.', 'list-operator': 'Dizi içeriğini filtrelemek veya sıralamak için kullanılır.', 'agent': 'Soruları yanıtlamak veya doğal dili işlemek için büyük dil modellerini çağırma', + 'loop': 'Sonlandırma koşulu karşılanana kadar veya maksimum döngü sayısına ulaşılana kadar bir mantık döngüsü çalıştırın.', + 'loop-end': '"break" ile eşdeğerdir. Bu düğümün yapılandırma öğesi yoktur. Döngü gövdesi bu düğüme ulaştığında, döngü sona erer.', }, operator: { zoomIn: 'Yakınlaştır', @@ -273,20 +288,21 @@ const translation = { }, panel: { userInputField: 'Kullanıcı Giriş Alanı', - changeBlock: 'Blok Değiştir', helpLink: 'Yardım Linki', about: 'Hakkında', createdBy: 'Oluşturan: ', nextStep: 'Sonraki Adım', - addNextStep: 'Bu iş akışında sonraki bloğu ekleyin', - selectNextStep: 'Sonraki Bloğu Seç', runThisStep: 'Bu adımı çalıştır', checklist: 'Kontrol Listesi', checklistTip: 'Yayınlamadan önce tüm sorunların çözüldüğünden emin olun', checklistResolved: 'Tüm sorunlar çözüldü', - organizeBlocks: 'Blokları Düzenle', change: 'Değiştir', optional: '(isteğe bağlı)', + moveToThisNode: 'Bu düğüme geç', + changeBlock: 'Düğümü Değiştir', + addNextStep: 'Bu iş akışına bir sonraki adımı ekleyin', + organizeBlocks: 'Düğümleri düzenle', + selectNextStep: 'Sonraki Adımı Seç', }, nodes: { common: { @@ -404,6 +420,34 @@ const translation = { variable: 'Değişken', }, sysQueryInUser: 'sys.query kullanıcı mesajında gereklidir', + jsonSchema: { + warningTips: { + saveSchema: 'Lütfen şemayı kaydetmeden önce mevcut alanı düzenlemeyi tamamlayın.', + }, + apply: 'Uygula', + addField: 'Alan Ekle', + descriptionPlaceholder: 'Açıklama ekleyin', + title: 'Yapılandırılmış Çıktı Şeması', + generate: 'Oluştur', + fieldNamePlaceholder: 'Alan Adı', + doc: 'Yapılandırılmış çıktı hakkında daha fazla bilgi edinin', + instruction: 'Talimat', + promptTooltip: 'Metin tanımını standart bir JSON Şeması yapısına dönüştür.', + import: 'JSON\'den içe aktar', + back: 'Geri', + promptPlaceholder: 'JSON Şemanızı tanımlayın...', + required: 'gerekli', + generatedResult: 'Üretilen Sonuç', + regenerate: 'Yeniden üret', + generateJsonSchema: 'JSON Şeması Oluştur', + stringValidations: 'Dize Doğrulamaları', + showAdvancedOptions: 'Gelişmiş seçenekleri göster', + resetDefaults: 'Sıfırlama', + generating: 'JSON Şeması Oluşturuluyor...', + generationTip: 'Doğal dil kullanarak hızlıca bir JSON Şeması oluşturabilirsiniz.', + addChildField: 'Çocuk Alanı Ekle', + resultTip: 'İşte oluşturulan sonuç. Eğer memnun değilseniz, geri dönüp isteminizi değiştirebilirsiniz.', + }, }, knowledgeRetrieval: { queryVariable: 'Sorgu Değişkeni', @@ -416,6 +460,33 @@ const translation = { url: 'Parça URL\'si', metadata: 'Diğer meta veriler', }, + metadata: { + options: { + disabled: { + title: 'Devre dışı bırakıldı.', + subTitle: 'Meta veri filtreleme özelliğini devre dışı bırakma', + }, + automatic: { + title: 'Otomatik', + desc: 'Sorgu Değişkenine dayalı olarak otomatik olarak meta veri filtreleme koşulları oluşturun', + subTitle: 'Kullanıcı sorgusuna dayalı olarak otomatik olarak meta veri filtreleme koşulları oluşturun.', + }, + manual: { + subTitle: 'Manuel olarak meta veri filtreleme koşulları ekleyin', + title: 'Kılavuz', + }, + }, + panel: { + add: 'Koşul Ekle', + select: 'Değişkeni seç...', + title: 'Meta Verisi Filtre Koşulları', + search: 'Arama meta verileri', + conditions: 'Koşullar', + placeholder: 'Değer girin', + datePlaceholder: 'Bir zaman seçin...', + }, + title: 'Meta Verileri Filtreleme', + }, }, http: { inputVars: 'Giriş Değişkenleri', @@ -506,6 +577,8 @@ const translation = { 'all of': 'Tümü', 'not in': 'İçinde değil', 'exists': 'Var', + 'before': 'önce', + 'after': 'sonra', }, enterValue: 'Değer girin', addCondition: 'Koşul Ekle', @@ -521,6 +594,7 @@ const translation = { }, addSubVariable: 'Alt Değişken', select: 'Seçmek', + condition: 'Koşul', }, variableAssigner: { title: 'Değişken ata', @@ -563,6 +637,8 @@ const translation = { '-=': '-=', '*=': '*=', 'set': 'Ayarlamak', + 'remove-last': 'Sonuncuyu Kaldır', + 'remove-first': 'İlkini kaldır', }, 'variables': 'Değişken', 'selectAssignedVariable': 'Atanan değişkeni seçin...', @@ -573,7 +649,6 @@ const translation = { 'noAssignedVars': 'Kullanılabilir atanmış değişken yok', }, tool: { - toAuthorize: 'Yetkilendirmek için', inputVars: 'Giriş Değişkenleri', outputVars: { text: 'araç tarafından oluşturulan içerik', @@ -586,6 +661,7 @@ const translation = { }, json: 'araç tarafından oluşturulan json', }, + authorize: 'Yetkilendirmek', }, questionClassifiers: { model: 'model', @@ -767,6 +843,38 @@ const translation = { toolNotAuthorizedTooltip: '{{araç}} Yetkili Değil', model: 'model', }, + loop: { + ErrorMethod: { + operationTerminated: 'Sonlandırıldı', + removeAbnormalOutput: 'Anormal Çıktıyı Kaldır', + continueOnError: 'Hata ile Devam Et', + }, + loopNode: 'Döngü Düğümü', + output: 'Çıktı Değişkeni', + exitConditionTip: 'Bir döngü düğümünün en az bir çıkış koşuluna ihtiyacı vardır.', + inputMode: 'Giriş Modu', + setLoopVariables: 'Döngü kapsamı içinde değişkenleri ayarla', + loopMaxCount: 'Maksimum Döngü Sayısı', + input: 'Girdi', + breakCondition: 'Döngü Sonlandırma Koşulu', + comma: ',', + finalLoopVariables: 'Son Döngü Değişkenleri', + initialLoopVariables: 'İlk Döngü Değişkenleri', + errorResponseMethod: 'Hata Yanıt Yöntemi', + deleteTitle: 'Döngü Düğümünü Sil?', + totalLoopCount: 'Toplam döngü sayısı: {{count}}', + currentLoop: 'Mevcut Döngü', + loopMaxCountError: 'Lütfen 1 ile {{maxCount}} arasında geçerli bir maksimum döngü sayısı girin.', + variableName: 'Değişken Adı', + deleteDesc: 'Döngü düğümünü silmek, tüm alt düğümleri kaldıracaktır.', + breakConditionTip: 'Sadece sonlandırma koşulları olan döngüler içindeki değişkenler ve konuşma değişkenleri referans alınabilir.', + loop_other: '{{count}} Döngü', + currentLoopCount: 'Mevcut döngü sayısı: {{count}}', + error_one: '{{count}} Hata', + error_other: '{{count}} Hata', + loop_one: '{{count}} Döngü', + loopVariables: 'Döngü Değişkenleri', + }, }, tracing: { stopBy: '{{user}} tarafından durduruldu', @@ -778,6 +886,38 @@ const translation = { noVarsForOperation: 'Seçilen işlemle atanabilecek değişken yok.', noAssignedVars: 'Kullanılabilir atanmış değişken yok', }, + versionHistory: { + filter: { + reset: 'Filtreyi Sıfırla', + onlyYours: 'Sadece senin', + empty: 'Eşleşen bir sürüm geçmişi bulunamadı.', + all: 'Her şey', + onlyShowNamedVersions: 'Sadece adlandırılmış sürümleri göster', + }, + editField: { + releaseNotesLengthLimit: 'Sürüm notları {{limit}} karakteri geçemez', + titleLengthLimit: 'Başlık {{limit}} karakteri geçemez.', + title: 'Başlık', + releaseNotes: 'Sürüm Notları', + }, + action: { + restoreSuccess: 'Sürüm geri yüklendi', + deleteFailure: 'Versiyonu silme işlemi başarısız oldu', + restoreFailure: 'Sürümü geri yüklemekte başarısız olundu', + updateFailure: 'Sürüm güncellenemedi', + updateSuccess: 'Sürüm güncellendi', + deleteSuccess: 'Sürüm silindi', + }, + latest: 'Sonuncu', + currentDraft: 'Mevcut Taslak', + restorationTip: 'Sürüm geri yüklemeden sonra, mevcut taslak üzerine yazılacak.', + title: 'Sürümler', + defaultName: 'Başlıksız Versiyon', + editVersionInfo: 'Sürüm bilgilerini düzenle', + releaseNotesPlaceholder: 'Değişen şeyleri tanımlayın', + nameThisVersion: 'Bu versiyona isim ver', + deletionTip: 'Silme işlemi geri alınamaz, lütfen onaylayın.', + }, } export default translation diff --git a/web/i18n/uk-UA/app-debug.ts b/web/i18n/uk-UA/app-debug.ts index 1fc6981122..7e410ffef9 100644 --- a/web/i18n/uk-UA/app-debug.ts +++ b/web/i18n/uk-UA/app-debug.ts @@ -279,6 +279,7 @@ const translation = { 'labelName': 'Назва мітки', 'inputPlaceholder': 'Будь ласка, введіть', 'required': 'Обов\'язково', + 'hide': 'Приховати', 'errorMsg': { varNameRequired: 'Потрібно вказати назву змінної', labelNameRequired: 'Потрібно вказати назву мітки', diff --git a/web/i18n/uk-UA/app-overview.ts b/web/i18n/uk-UA/app-overview.ts index 002ab5da96..1a95b47abd 100644 --- a/web/i18n/uk-UA/app-overview.ts +++ b/web/i18n/uk-UA/app-overview.ts @@ -70,9 +70,9 @@ const translation = { copyrightTooltip: 'Будь ласка, перейдіть на тарифний план «Professional» або вище', }, sso: { - title: 'Єдиний вхід для WebApp', - description: 'Усі користувачі повинні увійти в систему за допомогою єдиного входу перед використанням WebApp', - tooltip: 'Зверніться до адміністратора, щоб увімкнути єдиний вхід WebApp', + title: 'Єдиний вхід для web app', + description: 'Усі користувачі повинні увійти в систему за допомогою єдиного входу перед використанням web app', + tooltip: 'Зверніться до адміністратора, щоб увімкнути єдиний вхід web app', label: 'Автентифікація за допомогою єдиного входу', }, modalTip: 'Налаштування веб-додатку на стороні клієнта.', diff --git a/web/i18n/uk-UA/app.ts b/web/i18n/uk-UA/app.ts index 2a9c03eace..6e5ff5dc74 100644 --- a/web/i18n/uk-UA/app.ts +++ b/web/i18n/uk-UA/app.ts @@ -72,7 +72,7 @@ const translation = { appCreateDSLErrorTitle: 'Несумісність версій', appCreateDSLErrorPart1: 'Виявлено суттєву різницю у версіях DSL. Примусовий імпорт може призвести до неправильної роботи програми.', appCreateDSLWarning: 'Увага: різниця у версіях DSL може вплинути на певні функції', - chooseAppType: 'Виберіть тип програми', + chooseAppType: 'Оберіть тип додатку', noIdeaTip: 'Немає ідей? Перегляньте наші шаблони', noTemplateFoundTip: 'Спробуйте шукати за різними ключовими словами.', foundResult: '{{count}} Результат', @@ -82,17 +82,17 @@ const translation = { forAdvanced: 'ДЛЯ ДОСВІДЧЕНИХ КОРИСТУВАЧІВ', noTemplateFound: 'Не знайдено шаблонів', agentUserDescription: 'Інтелектуальний агент, здатний до ітеративного міркування і автономного використання інструменту для досягнення поставлених цілей.', - advancedUserDescription: 'Оркестрація робочих процесів для багатораундових складних діалогових завдань з можливостями пам\'яті.', + advancedUserDescription: 'Робочий процес з функціями пам\'яті та інтерфейсом чат-бота.', agentShortDescription: 'Інтелектуальний агент з міркуваннями та автономним використанням інструментів', noAppsFound: 'Не знайдено додатків', - forBeginners: 'ДЛЯ ПОЧАТКІВЦІВ', - workflowShortDescription: 'Оркестрування для однотактних завдань автоматизації', + forBeginners: 'Простіші типи додатків', + workflowShortDescription: 'Агентський потік для інтелектуальних автоматизацій', learnMore: 'Дізнатися більше', chatbotUserDescription: 'Швидко створюйте чат-бота на базі LLM за допомогою простої конфігурації. Ви можете переключитися на Chatflow пізніше.', chatbotShortDescription: 'Чат-бот на базі LLM з простим налаштуванням', - advancedShortDescription: 'Робочий процес для складних багатоходових діалогів з пам\'яттю', + advancedShortDescription: 'Робочий процес, вдосконалений для багатоетапних чатів', completionUserDescription: 'Швидко створюйте помічника зі штучним інтелектом для завдань із генерації тексту за допомогою простої конфігурації.', - workflowUserDescription: 'Оркестрація робочих процесів для однокомпонентних завдань, таких як автоматизація та пакетна обробка.', + workflowUserDescription: 'ізуально створюйте автономні ШІ-процеси з простотою перетягування.', }, editApp: 'Редагувати інформацію', editAppTitle: 'Редагувати інформацію про додаток', @@ -159,10 +159,14 @@ const translation = { title: 'Опік', description: 'Opik — це платформа з відкритим вихідним кодом для оцінки, тестування та моніторингу додатків LLM.', }, + weave: { + title: 'Ткати', + description: 'Weave є платформою з відкритим кодом для оцінки, тестування та моніторингу LLM додатків.', + }, }, answerIcon: { - title: 'Використовуйте піктограму WebApp для заміни 🤖', - description: 'Чи слід використовувати піктограму WebApp для заміни 🤖 у спільній програмі', + title: 'Використовуйте піктограму web app для заміни 🤖', + description: 'Чи слід використовувати піктограму web app для заміни 🤖 у спільній програмі', descriptionInExplore: 'Чи використовувати піктограму веб-програми для заміни 🤖 в Огляді', }, importFromDSLUrl: 'З URL', @@ -194,6 +198,54 @@ const translation = { params: 'ПАРАМЕТРИ ПРОГРАМИ', placeholder: 'Виберіть програму...', }, + structOutput: { + LLMResponse: 'Відповідь ЛЛМ', + configure: 'Налаштувати', + required: 'Необхідно', + moreFillTip: 'Показуючи максимум 10 рівнів вкладеності', + structured: 'Структурований', + modelNotSupported: 'Модель не підтримується', + notConfiguredTip: 'Структурований вихід ще не було налаштовано', + modelNotSupportedTip: 'Поточна модель не підтримує цю функцію та автоматично знижується до ін\'єкції запитів.', + structuredTip: 'Структуровані виходи - це функція, яка забезпечує, що модель завжди генеруватиме відповіді, що відповідають наданій вами схемі JSON.', + }, + accessItemsDescription: { + anyone: 'Будь-хто може отримати доступ до веб-додатку', + specific: 'Тільки окремі групи або члени можуть отримати доступ до веб-додатку.', + organization: 'Будь-хто в організації може отримати доступ до веб-додатку.', + external: 'Тільки перевірені зовнішні користувачі можуть отримати доступ до веб-застосунку.', + }, + accessControlDialog: { + accessItems: { + anyone: 'Кожен, у кого є посилання', + specific: 'Конкретні групи або члени', + organization: 'Тільки члени підприємства', + external: 'Аутентифіковані зовнішні користувачі', + }, + operateGroupAndMember: { + searchPlaceholder: 'Шукати групи та учасників', + allMembers: 'Всі члени', + expand: 'розвивати', + noResult: 'Немає результату', + }, + title: 'Контроль доступу до веб-додатка', + description: 'Встановіть дозволи доступу до веб-додатку', + accessLabel: 'Хто має доступ', + groups_one: '{{count}} ГРУПА', + groups_other: '{{count}} ГРУП', + members_one: '{{count}} ЧЛЕН', + members_other: '{{count}} ЧЛЕНІ', + noGroupsOrMembers: 'Не вибрано групи чи учасників', + updateSuccess: 'Оновлення успішно', + webAppSSONotEnabledTip: 'Будь ласка, зв\'яжіться з адміністратором підприємства для налаштування методу аутентифікації веб-додатку.', + }, + publishApp: { + title: 'Хто може отримати доступ до веб-додатку', + notSet: 'Не встановлено', + notSetDesc: 'На даний момент ніхто не може отримати доступ до веб-додатку. Будь ласка, налаштуйте дозволи.', + }, + accessControl: 'Контроль доступу до веб-додатків', + noAccessPermission: 'Немає дозволу на доступ до веб-додатку', } export default translation diff --git a/web/i18n/uk-UA/billing.ts b/web/i18n/uk-UA/billing.ts index cebdb11521..56888531b0 100644 --- a/web/i18n/uk-UA/billing.ts +++ b/web/i18n/uk-UA/billing.ts @@ -68,6 +68,7 @@ const translation = { messageRequest: { title: 'Кредити повідомлень', tooltip: 'Квоти на виклик повідомлень для різних планів з використанням моделей OpenAI (крім gpt4). Повідомлення понад ліміт використовуватимуть ваш ключ API OpenAI.', + titlePerMonth: '{{count,number}} повідомлень/місяць', }, annotatedResponse: { title: 'Ліміти квоти відповідей з анотаціями', @@ -77,27 +78,94 @@ const translation = { receiptInfo: 'Лише власник команди та адміністратор команди можуть підписуватися та переглядати інформацію про виставлення рахунків', annotationQuota: 'Квота анотацій', documentsUploadQuota: 'Квота завантаження документів', + teamMember_one: '{{count,number}} член команди', + teamWorkspace: '{{count,number}} Командний Простір', + apiRateLimit: 'Обмеження швидкості API', + documentsTooltip: 'Квота на кількість документів, імпортованих з джерела знань.', + self: 'Власний хостинг', + cloud: 'Хмарний сервіс', + documentsRequestQuota: '{{count,number}}/хвилина Ліміт запиту знань', + annualBilling: 'Щорічна оплата', + priceTip: 'за робочим простором/', + unlimitedApiRate: 'Немає обмеження на швидкість API', + freeTrialTipSuffix: 'Кредитна картка не потрібна', + apiRateLimitUnit: '{{count,number}}/день', + getStarted: 'Почати', + freeTrialTip: 'безкоштовна пробна версія з 200 запитів до OpenAI.', + documents: '{{count,number}} Документів знань', + freeTrialTipPrefix: 'Зареєструйтесь і отримайте', + teamMember_other: '{{count,number}} членів команди', + comparePlanAndFeatures: 'Порівняйте плани та функції', + apiRateLimitTooltip: 'Обмеження частоти запитів застосовується до всіх запитів, зроблених через API Dify, включаючи генерацію тексту, чат-розмови, виконання робочих процесів та обробку документів.', + documentsRequestQuotaTooltip: 'Вказує загальну кількість дій, які робоча область може виконувати за хвилину в межах бази знань, включаючи створення, видалення, оновлення наборів даних, завантаження документів, модифікації, архівування та запити до бази знань. Цей показник використовується для оцінки ефективності запитів до бази знань. Наприклад, якщо користувач Sandbox виконує 10 послідовних тестів за один хвилину, його робочій області буде тимчасово заборонено виконувати наступні дії протягом наступної хвилини: створення наборів даних, видалення, оновлення, а також завантаження чи модифікацію документів.', }, plans: { sandbox: { name: 'Пісочниця', description: '200 безкоштовних пробних версій GPT', includesTitle: 'Включає в себе:', + for: 'Безкоштовна пробна версія основних функцій', }, professional: { name: 'Професійний', description: 'Щоб окремі особи та невеликі команди могли отримати більше можливостей за доступною ціною.', includesTitle: 'Все у безкоштовному плані, плюс:', + for: 'Для незалежних розробників/малих команд', }, team: { name: 'Команда', description: 'Співпрацюйте без обмежень і користуйтеся продуктивністю найвищого рівня.', includesTitle: 'Все, що входить до плану Professional, плюс:', + for: 'Для середніх команд', }, enterprise: { name: 'Ентерпрайз', description: 'Отримайте повні можливості та підтримку для масштабних критично важливих систем.', includesTitle: 'Все, що входить до плану Team, плюс:', + features: { + 5: 'Угоди про рівень обслуговування, узгоджені партнерами Dify', + 2: 'Ексклюзивні підприємницькі функції', + 6: 'Розвинена безпека та контроль', + 8: 'Професійна технічна підтримка', + 1: 'Комерційна ліцензія на авторизацію', + 3: 'Кілька робочих просторів та управління підприємством', + 4: 'ССО', + 0: 'Рішення для масштабованого розгортання підприємств', + 7: 'Оновлення та обслуговування від Dify Офіційно', + }, + btnText: 'Зв\'язатися з відділом продажу', + priceTip: 'Тільки річна оплата', + for: 'Для великих команд', + price: 'Користувацький', + }, + community: { + features: { + 2: 'Відповідає ліцензії Dify Open Source', + 1: 'Єдине робоче місце', + 0: 'Усі основні функції випущені під публічним репозиторієм', + }, + btnText: 'Розпочніть з громади', + includesTitle: 'Безкоштовні можливості:', + for: 'Для індивідуальних користувачів, малих команд або некомерційних проектів', + price: 'Безкоштовно', + description: 'Для індивідуальних користувачів, малих команд або некомерційних проектів', + name: 'Спільнота', + }, + premium: { + features: { + 2: 'Логотип веб-додатку та налаштування брендингу', + 1: 'Єдине робоче місце', + 3: 'Пріоритетна email та чат підтримка', + 0: 'Самостійно керовані надійність різних хмарних постачальників', + }, + description: 'Для середніх підприємств та команд', + btnText: 'Отримайте Преміум у', + price: 'Масштабований', + comingSoon: 'Підтримка Microsoft Azure та Google Cloud незабаром', + priceTip: 'На основі Хмарного ринку', + for: 'Для середніх підприємств та команд', + name: 'Преміум', + includesTitle: 'Все з громади, плюс:', }, }, vectorSpace: { @@ -107,12 +175,26 @@ const translation = { apps: { fullTipLine1: 'Оновіть свій план, щоб', fullTipLine2: 'створити більше програм.', + fullTip1des: 'Ви досягли межі створення додатків за цим планом.', + fullTip2: 'Досягнуто ліміту плану', + fullTip1: 'Оновіть, щоб створити більше додатків', + contactUs: 'Зв\'яжіться з нами', + fullTip2des: 'Рекомендується очистити неактивні програми, щоб звільнити місце, або зв\'язатися з нами.', }, annotatedResponse: { fullTipLine1: 'Оновіть свій план, щоб', fullTipLine2: 'анотувати більше розмов.', quotaTitle: 'Квота на анотовані відповіді', }, + usagePage: { + teamMembers: 'Члени команди', + buildApps: 'Створюйте додатки', + annotationQuota: 'Квота анотацій', + vectorSpaceTooltip: 'Документи з режимом індексування високої якості споживатимуть ресурси Сховища Знань. Коли Сховище Знань досягне межі, нові документи не будуть завантажені.', + documentsUploadQuota: 'Квота на завантаження документів', + vectorSpace: 'Сховище даних знань', + }, + teamMembers: 'Члени команди', } export default translation diff --git a/web/i18n/uk-UA/common.ts b/web/i18n/uk-UA/common.ts index dd6dab61cc..a80e308b78 100644 --- a/web/i18n/uk-UA/common.ts +++ b/web/i18n/uk-UA/common.ts @@ -54,6 +54,10 @@ const translation = { viewDetails: 'Перегляд докладних відомостей', copied: 'Скопійовані', in: 'В', + format: 'Формат', + downloadFailed: 'Не вдалося завантажити. Будь ласка, спробуйте ще раз пізніше.', + more: 'Більше', + downloadSuccess: 'Завантаження завершено.', }, placeholder: { input: 'Будь ласка, введіть текст', @@ -141,6 +145,8 @@ const translation = { newDataset: 'Створити знання', tools: 'Інструменти', exploreMarketplace: 'Дізнайтеся більше про Marketplace', + appDetail: 'Деталі програми', + account: 'Обліковий запис', }, userProfile: { settings: 'Налаштування', @@ -153,6 +159,9 @@ const translation = { community: 'Спільнота', about: 'Про нас', logout: 'Вийти', + compliance: 'Відповідність', + support: 'Підтримка', + github: 'Гітхаб', }, settings: { accountGroup: 'ОБЛІКОВИЙ ЗАПИС', @@ -202,6 +211,9 @@ const translation = { deleteSuccessTip: 'Вашому обліковому запису потрібен час, щоб завершити видалення. Ми надішлемо вам електронного листа, коли все буде готово.', deleteLabel: 'Для підтвердження, будь ласка, введіть свою електронну пошту нижче', deletePlaceholder: 'Будь ласка, введіть свою електронну пошту', + workspaceName: 'Назва робочого простору', + workspaceIcon: 'Іконка робочого простору', + editWorkspaceInfo: 'Редагувати інформацію про робочий простір', }, members: { team: 'Команда', @@ -456,7 +468,7 @@ const translation = { apiBasedExtension: { title: 'API-розширення забезпечують централізоване керування API, спрощуючи конфігурацію для зручного використання в різних програмах Dify.', link: 'Дізнайтеся, як розробити власне розширення API.', - linkUrl: 'https://docs.dify.ai/features/extension/api_based_extension', + linkUrl: 'https://docs.dify.ai/en/guides/extension/api-based-extension/README', add: 'Додати розширення API', selector: { title: 'Розширення API', @@ -544,6 +556,7 @@ const translation = { inputPlaceholder: 'Поговоріть з ботом', thought: 'Думка', thinking: 'Мислення...', + resend: 'Відправити знову', }, promptEditor: { placeholder: 'Пишіть свої підказки тут, вводьте \'{\', щоб вставити змінну чи \'/\', щоб вставити блок-підказку', @@ -634,10 +647,31 @@ const translation = { license: { expiring: 'Термін дії закінчується за один день', expiring_plural: 'Термін дії закінчується за {{count}} днів', + unlimited: 'Безмежний', }, pagination: { perPage: 'Елементів на сторінці', }, + theme: { + auto: 'система', + dark: 'темний', + light: 'світло', + theme: 'Тема', + }, + compliance: { + professionalUpgradeTooltip: 'Доступно лише з командним планом або вище.', + soc2Type2: 'Звіт SOC 2 Тип II', + iso27001: 'Сертифікація ISO 27001:2022', + soc2Type1: 'Звіт SOC 2 Тип I', + sandboxUpgradeTooltip: 'Доступно лише з професійним або командним планом.', + gdpr: 'GDPR DPA', + }, + imageInput: { + browse: 'перегляд', + supportedFormats: 'Підтримує PNG, JPG, JPEG, WEBP і GIF', + dropImageHere: 'Перетягніть зображення сюди або', + }, + you: 'Ти', } export default translation diff --git a/web/i18n/uk-UA/custom.ts b/web/i18n/uk-UA/custom.ts index 378e094c62..1b7a4c0abd 100644 --- a/web/i18n/uk-UA/custom.ts +++ b/web/i18n/uk-UA/custom.ts @@ -3,9 +3,11 @@ const translation = { upgradeTip: { prefix: 'Оновіть свій план до ', suffix: ', щоб налаштувати свій бренд.', + title: 'Оновіть свій план', + des: 'Оновіть свій план, щоб налаштувати свій бренд', }, webapp: { - title: 'Налаштувати бренд для WebApp', + title: 'Налаштувати бренд для web app', removeBrand: 'Видалити Powered by Dify', changeLogo: 'Змінити зображення бренду "Powered by"', changeLogoTip: 'Формат SVG або PNG з мінімальним розміром 40x40 пікселів', diff --git a/web/i18n/uk-UA/dataset-creation.ts b/web/i18n/uk-UA/dataset-creation.ts index 120c24a9d0..18b5553be7 100644 --- a/web/i18n/uk-UA/dataset-creation.ts +++ b/web/i18n/uk-UA/dataset-creation.ts @@ -22,7 +22,7 @@ const translation = { }, uploader: { title: 'Завантажити текстовий файл', - button: 'Перетягніть файл або', + button: 'Перетягніть файли або папки або', browse: 'Оберіть', tip: 'Підтримуються {{supportTypes}}. Максимум {{size}} МБ кожен.', validation: { @@ -60,7 +60,7 @@ const translation = { unknownError: 'Невідома помилка', maxDepth: 'Максимальна глибина', crawlSubPage: 'Сканування підсторінок', - firecrawlDocLink: 'https://docs.dify.ai/guides/knowledge-base/sync-from-website', + firecrawlDocLink: 'https://docs.dify.ai/en/guides/knowledge-base/create-knowledge-and-upload-documents/import-content-data/sync-from-website', preview: 'Попередній перегляд', fireCrawlNotConfigured: 'Firecrawl не налаштовано', includeOnlyPaths: 'Включати лише контури', @@ -82,6 +82,14 @@ const translation = { jinaReaderNotConfigured: 'Jina Reader не налаштована', jinaReaderTitle: 'Перетворіть весь сайт на Markdown', useSitemap: 'Використовуйте карту сайту', + configureJinaReader: 'Налаштувати Jina Reader', + waterCrawlNotConfigured: 'Watercrawl не налаштовано', + waterCrawlNotConfiguredDescription: 'Налаштуйте Watercrawl з ключем API, щоб його використовувати.', + configureFirecrawl: 'Налаштування Firecrawl', + configureWatercrawl: 'Налаштування Watercrawl', + watercrawlTitle: 'Витягуйте веб-контент за допомогою Watercrawl', + watercrawlDocLink: 'https://docs.dify.ai/en/guides/knowledge-base/create-knowledge-and-upload-documents/import-content-data/sync-from-website', + watercrawlDoc: 'Документація Watercrawl', }, cancel: 'Скасувати', }, @@ -200,6 +208,11 @@ const translation = { title: 'Підключитися до інших джерел даних?', description: 'Наразі база знань Dify має лише обмежені джерела даних. Додавання джерела даних до бази знань Dify – це фантастичний спосіб підвищити гнучкість і потужність платформи для всіх користувачів. Наш посібник із внеску спрощує початок роботи. Будь ласка, натисніть на посилання нижче, щоб дізнатися більше.', }, + watercrawl: { + configWatercrawl: 'Налаштування Watercrawl', + apiKeyPlaceholder: 'API ключ з watercrawl.dev', + getApiKeyLinkText: 'Отримайте ваш API ключ з watercrawl.dev', + }, } export default translation diff --git a/web/i18n/uk-UA/dataset-settings.ts b/web/i18n/uk-UA/dataset-settings.ts index c56473896c..f5403b883e 100644 --- a/web/i18n/uk-UA/dataset-settings.ts +++ b/web/i18n/uk-UA/dataset-settings.ts @@ -25,6 +25,7 @@ const translation = { learnMore: 'Дізнатися більше', description: ' про метод вибірки.', longDescription: ' про метод вибірки, ви можете змінити це будь-коли в налаштуваннях бази знань.', + method: 'Метод отримання', }, save: 'Зберегти', me: '(Ви)', diff --git a/web/i18n/uk-UA/dataset.ts b/web/i18n/uk-UA/dataset.ts index 20948e7b99..84bc8d9ad1 100644 --- a/web/i18n/uk-UA/dataset.ts +++ b/web/i18n/uk-UA/dataset.ts @@ -169,6 +169,54 @@ const translation = { enable: 'Вмикати', allKnowledge: 'Всі знання', allKnowledgeDescription: 'Виберіть, щоб відобразити всі знання в цій робочій області. Тільки власник робочої області може керувати всіма знаннями.', + metadata: { + createMetadata: { + back: 'Назад', + title: 'Нові метадані', + type: 'Тип', + namePlaceholder: 'Додати назву метаданих', + name: 'Ім\'я', + }, + checkName: { + empty: 'Ім\'я метаданих не може бути порожнім', + invalid: 'Ім\'я метаданих може містити лише малі літери, цифри та підкреслення, і повинно починатися з малої літери', + }, + batchEditMetadata: { + editMetadata: 'Редагувати метадані', + editDocumentsNum: 'Редагування {{num}} документів', + applyToAllSelectDocument: 'Застосувати до всіх вибраних документів', + multipleValue: 'Кілька значень', + applyToAllSelectDocumentTip: 'Автоматично створювати всю редаговану та нову метадані для всіх вибраних документів, інакше редагування метаданих буде застосовуватися тільки до документів, які мають їх.', + }, + selectMetadata: { + manageAction: 'Керувати', + search: 'Пошукова метаінформація', + newAction: 'Нові метадані', + }, + datasetMetadata: { + namePlaceholder: 'Назва метаданих', + rename: 'Перейменувати', + disabled: 'Вимкнено', + name: 'Ім\'я', + values: '{{num}} Значення', + builtIn: 'Вбудований', + addMetaData: 'Додати метадані', + description: 'Ви можете керувати всіма метаданими в цьому знанні тут. Модифікації будуть синхронізовані з кожним документом.', + builtInDescription: 'Вбудовані метадані автоматично витягуються та генеруються. Вони повинні бути активовані перед використанням і не можуть бути відредаговані.', + deleteTitle: 'Підтвердьте, щоб видалити', + deleteContent: 'Ви впевнені, що хочете видалити метадані "{{name}}"?', + }, + documentMetadata: { + documentInformation: 'Інформація про документ', + technicalParameters: 'Технічні параметри', + startLabeling: 'Почати маркування', + metadataToolTip: 'Метадані слугують критичною фільтрацією, що підвищує точність і актуальність витягування інформації. Ви можете змінити та додати метадані для цього документа тут.', + }, + metadata: 'Метадані', + chooseTime: 'Виберіть час...', + addMetadata: 'Додати метадані', + }, + embeddingModelNotAvailable: 'Модель вбудовування недоступна.', } export default translation diff --git a/web/i18n/uk-UA/education.ts b/web/i18n/uk-UA/education.ts new file mode 100644 index 0000000000..3cb4750050 --- /dev/null +++ b/web/i18n/uk-UA/education.ts @@ -0,0 +1,47 @@ +const translation = { + toVerifiedTip: { + end: 'для професійного плану Dify.', + coupon: 'ексклюзивний купон 100%', + front: 'Ви тепер маєте право на статус перевіреної освіти. Будь ласка, введіть свою інформацію про освіту нижче, щоб завершити процес і отримати', + }, + form: { + schoolName: { + title: 'Ваша назва школи', + placeholder: 'Введіть офіційну, повну назву вашої школи', + }, + schoolRole: { + option: { + administrator: 'Шкільний адміністратор', + teacher: 'Вчитель', + student: 'Студент', + }, + title: 'Ваша роль у школі', + }, + terms: { + desc: { + and: 'і', + end: '. Надсилаючи:', + privacyPolicy: 'Політика конфіденційності', + termsOfService: 'Умови обслуговування', + front: 'Ваша інформація та використання статусу Перевірено в освіті підлягають нашому', + }, + option: { + inSchool: 'Я підтверджую, що я зареєстрований або працюю в зазначеній установі. Dify може вимагати підтвердження моєї реєстрації/трудової зайнятості. Якщо я неправильно представлю свою кваліфікацію, я погоджуюся сплатити будь-які збори, які спочатку були скасовані на основі мого навчального статусу.', + age: 'Я підтверджую, що мені щонайменше 18 років.', + }, + title: 'Умови та угоди', + }, + }, + submitError: 'Відправка форми не вдалася. Будь ласка, спробуйте ще раз пізніше.', + rejectTitle: 'Ваша перевірка освіти Dify була відхилена', + submit: 'Надіслати', + successTitle: 'Ви отримали перевірене освіту Dify', + emailLabel: 'Ваш поточний електронний лист', + rejectContent: 'На жаль, ви не відповідаєте вимогам для статусу Education Verified і тому не можете отримати ексклюзивний купон на 100% для професійного плану Dify, якщо використовуєте цю електронну адресу.', + toVerified: 'Отримайте підтвердження освіти', + learn: 'Дізнайтеся, як перевірити освіту', + currentSigned: 'В даний момент ви підписані як', + successContent: 'Ми видали купон на 100% знижку на професійний план Dify для вашого акаунту. Купон дійсний протягом одного року, будь ласка, скористайтеся ним протягом терміну дії.', +} + +export default translation diff --git a/web/i18n/uk-UA/explore.ts b/web/i18n/uk-UA/explore.ts index 8b2efc903e..0bb03af719 100644 --- a/web/i18n/uk-UA/explore.ts +++ b/web/i18n/uk-UA/explore.ts @@ -37,6 +37,7 @@ const translation = { HR: 'HR', Workflow: 'Робочий процес', Agent: 'Агент', + Entertainment: 'Розваги', }, } diff --git a/web/i18n/uk-UA/login.ts b/web/i18n/uk-UA/login.ts index e6d1d15dd5..13c71c32c0 100644 --- a/web/i18n/uk-UA/login.ts +++ b/web/i18n/uk-UA/login.ts @@ -105,6 +105,11 @@ const translation = { licenseLost: 'Ліцензію втрачено', licenseInactiveTip: 'Ліцензія Dify Enterprise для вашої робочої області неактивна. Будь ласка, зверніться до свого адміністратора, щоб продовжити користуватися Dify.', licenseExpiredTip: 'Термін дії ліцензії Dify Enterprise для вашого робочого простору закінчився. Будь ласка, зверніться до свого адміністратора, щоб продовжити користуватися Dify.', + webapp: { + noLoginMethod: 'Метод аутентифікації не налаштований для веб-додатку', + noLoginMethodTip: 'Будь ласка, зв\'яжіться з адміністратором системи, щоб додати метод автентифікації.', + disabled: 'Аутентифікацію вебдодатка вимкнено. Будь ласка, зв\'яжіться з адміністратором системи, щоб увімкнути її. Ви можете спробувати використовувати додаток безпосередньо.', + }, } export default translation diff --git a/web/i18n/uk-UA/plugin.ts b/web/i18n/uk-UA/plugin.ts index c86e225b1e..1e4f0b6d2d 100644 --- a/web/i18n/uk-UA/plugin.ts +++ b/web/i18n/uk-UA/plugin.ts @@ -62,6 +62,7 @@ const translation = { auto: 'Автоматичний', uninstalledContent: 'Цей плагін встановлюється з локального/GitHub репозиторію. Будь ласка, використовуйте після встановлення.', unsupportedContent: 'Встановлена версія плагіна не передбачає цієї дії.', + toolSetting: 'Налаштування інструментів', }, modelNum: '{{num}} МОДЕЛІ В КОМПЛЕКТІ', switchVersion: 'Версія перемикача', @@ -180,6 +181,8 @@ const translation = { difyMarketplace: 'Dify Marketplace', viewMore: 'Дивитись більше', noPluginFound: 'Плагін не знайдено', + verifiedTip: 'Перевірено Dify', + partnerTip: 'Перевірено партнером Dify', }, task: { installingWithError: 'Не вдалося встановити плагіни {{installingLength}}, успіх {{successLength}}, {{errorLength}}', @@ -189,7 +192,6 @@ const translation = { installing: 'Встановлення плагінів {{installingLength}}, 0 виконано.', installingWithSuccess: 'Встановлення плагінів {{installingLength}}, успіх {{successLength}}.', }, - submitPlugin: 'Надіслати плагін', from: 'Від', searchInMarketplace: 'Пошук у Marketplace', endpointsEnabled: '{{num}} наборів кінцевих точок увімкнено', @@ -204,6 +206,12 @@ const translation = { search: 'Шукати', searchPlugins: 'Плагіни пошуку', allCategories: 'Всі категорії', + metadata: { + title: 'Плагіни', + }, + difyVersionNotCompatible: 'Поточна версія Dify не сумісна з цим плагіном, будь ласка, оновіть до мінімальної версії: {{minimalDifyVersion}}', + requestAPlugin: 'Запросити плагін', + publishPlugins: 'Публікація плагінів', } export default translation diff --git a/web/i18n/uk-UA/share-app.ts b/web/i18n/uk-UA/share-app.ts index 3465a6e5b9..92f25545d9 100644 --- a/web/i18n/uk-UA/share-app.ts +++ b/web/i18n/uk-UA/share-app.ts @@ -26,6 +26,12 @@ const translation = { }, tryToSolve: 'Спробувати вирішити', temporarySystemIssue: 'Вибачте, тимчасова системна проблема.', + expand: 'Розгорнути', + collapse: 'Згорнути', + chatFormTip: 'Налаштування чату не можуть бути змінені після початку чату.', + viewChatSettings: 'Переглянути налаштування чату', + chatSettingsTitle: 'Нове налаштування чату', + newChatTip: 'Вже в новому чаті', }, generation: { tabs: { @@ -64,6 +70,11 @@ const translation = { moreThanMaxLengthLine: 'Рядок {{rowIndex}}: значення {{varName}} не може містити більше {{maxLength}} символів', atLeastOne: 'Будь ласка, введіть принаймні один рядок у завантажений файл.', }, + execution: 'ВИКОНАННЯ', + executions: '{{num}} ВИКОНАНЬ', + }, + login: { + backToHome: 'Повернутися на головну', }, } diff --git a/web/i18n/uk-UA/time.ts b/web/i18n/uk-UA/time.ts index e2410dd34b..1ea08cb4ed 100644 --- a/web/i18n/uk-UA/time.ts +++ b/web/i18n/uk-UA/time.ts @@ -1,3 +1,37 @@ -const translation = {} +const translation = { + daysInWeek: { + Thu: 'Четвер', + Fri: 'Вільний', + Sun: 'Сонце', + Tue: 'Вівторок', + Mon: 'Мон', + Sat: 'Субота', + Wed: 'Середа', + }, + months: { + August: 'Серпень', + January: 'Січень', + October: 'Жовтень', + June: 'Червень', + September: 'Вересень', + February: 'Лютий', + November: 'Листопад', + July: 'Липень', + May: 'Травень', + December: 'Грудень', + April: 'Квітень', + March: 'Березень', + }, + operation: { + cancel: 'Скасувати', + pickDate: 'Виберіть дату', + now: 'Тепер', + ok: 'Добре', + }, + title: { + pickTime: 'Виберіть час', + }, + defaultPlaceholder: 'Виберіть час...', +} export default translation diff --git a/web/i18n/uk-UA/tools.ts b/web/i18n/uk-UA/tools.ts index 528e683a56..d390b500d3 100644 --- a/web/i18n/uk-UA/tools.ts +++ b/web/i18n/uk-UA/tools.ts @@ -14,7 +14,6 @@ const translation = { }, author: 'Автор', auth: { - unauthorized: 'Авторизуватися', authorized: 'Авторизовано', setup: 'Налаштувати авторизацію, щоб використовувати', setupModalTitle: 'Налаштування авторизації', diff --git a/web/i18n/uk-UA/workflow.ts b/web/i18n/uk-UA/workflow.ts index a4b832660d..a8bf456464 100644 --- a/web/i18n/uk-UA/workflow.ts +++ b/web/i18n/uk-UA/workflow.ts @@ -38,8 +38,6 @@ const translation = { setVarValuePlaceholder: 'Встановити значення змінної', needConnectTip: 'Цей крок ні до чого не підключений', maxTreeDepth: 'Максимальний ліміт {{depth}} вузлів на гілку', - needEndNode: 'Потрібно додати кінцевий блок', - needAnswerNode: 'Потрібно додати блок відповіді', workflowProcess: 'Процес робочого потоку', notRunning: 'Ще не запущено', previewPlaceholder: 'Введіть вміст у поле нижче, щоб розпочати налагодження чат-бота', @@ -58,7 +56,6 @@ const translation = { learnMore: 'Дізнатися більше', copy: 'Копіювати', duplicate: 'Дублювати', - addBlock: 'Додати блок', pasteHere: 'Вставити сюди', pointerMode: 'Режим вказівника', handMode: 'Ручний режим', @@ -106,6 +103,18 @@ const translation = { addFailureBranch: 'Додано гілку помилки', noHistory: 'Без історії', loadMore: 'Завантажте більше робочих процесів', + referenceVar: 'Посилальна змінна', + exportPNG: 'Експортувати як PNG', + noExist: 'Такої змінної не існує', + exitVersions: 'Вихідні версії', + versionHistory: 'Історія версій', + publishUpdate: 'Опублікувати оновлення', + exportImage: 'Експорт зображення', + exportSVG: 'Експортувати як SVG', + exportJPEG: 'Експортувати як JPEG', + addBlock: 'Додати вузол', + needEndNode: 'Необхідно додати кінцевий вузол', + needAnswerNode: 'Вузол Відповіді повинен бути доданий', }, env: { envPanelTitle: 'Змінні середовища', @@ -167,19 +176,19 @@ const translation = { stepForward_other: '{{count}} кроки вперед', sessionStart: 'Початок сесії', currentState: 'Поточний стан', - nodeTitleChange: 'Назву блоку змінено', - nodeDescriptionChange: 'Опис блоку змінено', - nodeDragStop: 'Блок переміщено', - nodeChange: 'Блок змінено', - nodeConnect: 'Блок підключено', - nodePaste: 'Блок вставлено', - nodeDelete: 'Блок видалено', - nodeAdd: 'Блок додано', - nodeResize: 'Розмір блоку змінено', noteAdd: 'Додано нотатку', noteChange: 'Нотатку змінено', noteDelete: 'Нотатку видалено', - edgeDelete: 'Блок відключено', + nodeTitleChange: 'Заголовок вузла змінено', + nodeResize: 'Вузол змінив розмір', + nodePaste: 'Вузол вставлений', + nodeDelete: 'Вузол видалено', + nodeDragStop: 'Вузол переміщено', + edgeDelete: 'Вузол відключено', + nodeChange: 'Вузол змінився', + nodeAdd: 'Додано вузол', + nodeDescriptionChange: 'Опис вузла змінено', + nodeConnect: 'Вузол підключено', }, errorMsg: { fieldRequired: '{{field}} є обов\'язковим', @@ -205,10 +214,9 @@ const translation = { testRunIteration: 'Ітерація тестового запуску', back: 'Назад', iteration: 'Ітерація', + loop: 'Петля', }, tabs: { - 'searchBlock': 'Пошук блоку', - 'blocks': 'Блоки', 'tools': 'Інструменти', 'allTool': 'Усі', 'builtInTool': 'Вбудовані', @@ -222,6 +230,8 @@ const translation = { 'searchTool': 'Інструмент пошуку', 'plugin': 'Плагін', 'agent': 'Стратегія агента', + 'blocks': 'Вузли', + 'searchBlock': 'Пошуковий вузол', }, blocks: { 'start': 'Початок', @@ -243,6 +253,9 @@ const translation = { 'document-extractor': 'Екстрактор документів', 'list-operator': 'Оператор списку', 'agent': 'Агент', + 'loop-start': 'Початок циклу', + 'loop': 'Петля', + 'loop-end': 'Вихід з циклу', }, blocksAbout: { 'start': 'Визначте початкові параметри для запуску робочого потоку', @@ -263,6 +276,8 @@ const translation = { 'document-extractor': 'Використовується для аналізу завантажених документів у текстовий контент, який легко зрозумілий LLM.', 'list-operator': 'Використовується для фільтрації або сортування вмісту масиву.', 'agent': 'Виклик великих мовних моделей для відповідей на запитання або обробки природної мови', + 'loop': 'Виконуйте цикл логіки, поки не буде виконано умову завершення або досягнуто максимальну кількість ітерацій.', + 'loop-end': 'Еквівалентно "перерві". Цей вузол не має елементів конфігурації. Коли тіло циклу досягає цього вузла, цикл завершується.', }, operator: { zoomIn: 'Збільшити', @@ -273,20 +288,21 @@ const translation = { }, panel: { userInputField: 'Поле введення користувача', - changeBlock: 'Змінити блок', helpLink: 'Посилання на допомогу', about: 'Про', createdBy: 'Створено ', nextStep: 'Наступний крок', - addNextStep: 'Додати наступний блок у цей робочий потік', - selectNextStep: 'Вибрати наступний блок', runThisStep: 'Запустити цей крок', checklist: 'Контрольний список', checklistTip: 'Переконайтеся, що всі проблеми вирішені перед публікацією', checklistResolved: 'Всі проблеми вирішені', - organizeBlocks: 'Організувати блоки', change: 'Змінити', optional: '(необов\'язково)', + moveToThisNode: 'Перемістіть до цього вузла', + organizeBlocks: 'Організуйте вузли', + changeBlock: 'Змінити вузол', + selectNextStep: 'Виберіть наступний крок', + addNextStep: 'Додайте наступний крок у цей робочий процес', }, nodes: { common: { @@ -404,6 +420,34 @@ const translation = { variable: 'Змінна', }, sysQueryInUser: 'sys.query у повідомленні користувача є обов\'язковим', + jsonSchema: { + warningTips: { + saveSchema: 'Будь ласка, завершіть редагування поточного поля перед збереженням схемы.', + }, + import: 'Імпорт з JSON', + instruction: 'Інструкція', + descriptionPlaceholder: 'Додати опис', + addField: 'Додати поле', + promptTooltip: 'Перетворіть текстовий опис у стандартизовану структуру JSON Schema.', + resultTip: 'Ось згенерований результат. Якщо вас не влаштовує, ви можете повернутися назад і змінити свій запит.', + promptPlaceholder: 'Опишіть вашу схему JSON...', + generating: 'Генерація JSON-схеми...', + back: 'Назад', + generatedResult: 'Згенерований результат', + fieldNamePlaceholder: 'Назва поля', + addChildField: 'Додати поле дитини', + apply: 'Застосувати', + regenerate: 'Перегенерувати', + resetDefaults: 'Скинути', + generateJsonSchema: 'Згенерувати JSON Схему', + showAdvancedOptions: 'Показати розширені налаштування', + required: 'необхідно', + generationTip: 'Ви можете використовувати природну мову, щоб швидко створити JSON-схему.', + stringValidations: 'Валідність рядків', + generate: 'Генерувати', + title: 'Структурована схема виходу', + doc: 'Дізнайтеся більше про структурований вихід', + }, }, knowledgeRetrieval: { queryVariable: 'Змінна запиту', @@ -416,6 +460,33 @@ const translation = { url: 'Сегментована URL', metadata: 'Інші метадані', }, + metadata: { + options: { + disabled: { + title: 'Вимкнено', + subTitle: 'Не включаючи фільтрацію метаданих', + }, + automatic: { + title: 'Автоматичний', + subTitle: 'Автоматично генерувати умови фільтрації метаданих на основі запиту користувача.', + desc: 'Автоматично генерувати умови фільтрації метаданих на основі змінної запиту.', + }, + manual: { + subTitle: 'Вручну додайте умови фільтрації метаданих', + title: 'Посібник', + }, + }, + panel: { + search: 'Пошукова метаінформація', + datePlaceholder: 'Виберіть час...', + title: 'Умови фільтрації метаданих', + placeholder: 'Введіть значення', + conditions: 'Умови', + select: 'Виберіть змінну...', + add: 'Додати умову', + }, + title: 'Фільтрація метаданих', + }, }, http: { inputVars: 'Вхідні змінні', @@ -505,6 +576,8 @@ const translation = { 'exists': 'Існує', 'not exists': 'не існує', 'not in': 'Не в', + 'after': 'після', + 'before': 'раніше', }, enterValue: 'Введіть значення', addCondition: 'Додати умову', @@ -520,6 +593,7 @@ const translation = { }, select: 'Виберіть', addSubVariable: 'Підзмінна', + condition: 'Умова', }, variableAssigner: { title: 'Присвоєння змінних', @@ -562,6 +636,8 @@ const translation = { '+=': '+=', '*=': '*=', 'extend': 'Розширити', + 'remove-last': 'Видалити останнє', + 'remove-first': 'Видалити перший', }, 'selectAssignedVariable': 'Виберіть призначену змінну...', 'noAssignedVars': 'Немає доступних призначених змінних', @@ -572,7 +648,6 @@ const translation = { 'setParameter': 'Встановити параметр...', }, tool: { - toAuthorize: 'Авторизувати', inputVars: 'Вхідні змінні', outputVars: { text: 'генерований вміст інструменту', @@ -585,6 +660,7 @@ const translation = { }, json: 'JSON, згенерований інструментом', }, + authorize: 'Уповноважити', }, questionClassifiers: { model: 'модель', @@ -766,6 +842,38 @@ const translation = { modelNotSelected: 'Модель не обрана', strategyNotFoundDescAndSwitchVersion: 'Встановлена версія плагіна не забезпечує цю стратегію. Натисніть, щоб змінити версію.', }, + loop: { + ErrorMethod: { + operationTerminated: 'Припинено', + removeAbnormalOutput: 'Видалити ненормальний вихід', + continueOnError: 'Продовжити незважаючи на помилку', + }, + loop_one: '{{count}} Цикл', + exitConditionTip: 'Вузол циклу потребує принаймні однієї умови виходу', + error_other: '{{count}} Помилок', + setLoopVariables: 'Встановіть змінні в межах області видимості циклу', + loopVariables: 'Циклічні змінні', + currentLoopCount: 'Поточна кількість циклів: {{count}}', + totalLoopCount: 'Загальна кількість циклів: {{count}}', + loop_other: '{{count}} Циклів', + error_one: '{{count}} Помилка', + currentLoop: 'Поточна петля', + breakCondition: 'Умова завершення циклу', + deleteDesc: 'Видалення вузла циклу призведе до видалення всіх дочірніх вузлів.', + breakConditionTip: 'Тільки змінні в циклах з умовами завершення та змінні розмови можуть бути використані.', + initialLoopVariables: 'Початкові змінні циклу', + finalLoopVariables: 'Заключні змінні циклу', + input: 'Введення', + errorResponseMethod: 'Метод відповіді на помилку', + output: 'Вихідна змінна', + variableName: 'Змінна Ім\'я', + comma: ',', + inputMode: 'Режим введення', + loopNode: 'Циклічний вузол', + loopMaxCountError: 'Будь ласка, введіть дійсне максимальне значення циклу, яке коливається від 1 до {{maxCount}}', + deleteTitle: 'Видалити вузол циклу?', + loopMaxCount: 'Максимальна кількість циклів', + }, }, tracing: { stopBy: 'Зупинено користувачем {{user}}', @@ -777,6 +885,38 @@ const translation = { noAssignedVars: 'Немає доступних призначених змінних', noAvailableVars: 'Немає доступних змінних', }, + versionHistory: { + filter: { + onlyShowNamedVersions: 'Показуйте лише названі версії', + reset: 'Скинути фільтр', + all: 'Усе', + onlyYours: 'Тільки твоє', + empty: 'Не знайдено відповідну історію версій', + }, + editField: { + titleLengthLimit: 'Заголовок не може перевищувати {{limit}} символів', + releaseNotes: 'Примітки до випуску', + title: 'Назва', + releaseNotesLengthLimit: 'Примітки до випуску не можуть перевищувати {{limit}} символів', + }, + action: { + restoreFailure: 'Не вдалося відновити версію', + updateSuccess: 'Версія оновлена', + deleteFailure: 'Не вдалося видалити версію', + deleteSuccess: 'Версія видалена', + restoreSuccess: 'Версія відновлена', + updateFailure: 'Не вдалося оновити версію', + }, + defaultName: 'Без назви версія', + restorationTip: 'Після відновлення версії нинішній проект буде перезаписано.', + title: 'Версії', + currentDraft: 'Поточний проект', + deletionTip: 'Видалення є незворотнім, будь ласка, підтвердіть.', + releaseNotesPlaceholder: 'Опишіть, що змінилося', + editVersionInfo: 'Редагувати інформацію про версію', + nameThisVersion: 'Назвіть цю версію', + latest: 'Останні новини', + }, } export default translation diff --git a/web/i18n/vi-VN/app-debug.ts b/web/i18n/vi-VN/app-debug.ts index 4e8a1962fe..c091cb5abb 100644 --- a/web/i18n/vi-VN/app-debug.ts +++ b/web/i18n/vi-VN/app-debug.ts @@ -279,6 +279,7 @@ const translation = { 'labelName': 'Tên nhãn', 'inputPlaceholder': 'Vui lòng nhập', 'required': 'Bắt buộc', + 'hide': 'Ẩn', 'errorMsg': { varNameRequired: 'Tên biến là bắt buộc', labelNameRequired: 'Tên nhãn là bắt buộc', diff --git a/web/i18n/vi-VN/app-overview.ts b/web/i18n/vi-VN/app-overview.ts index a0b7bd006d..34f3735beb 100644 --- a/web/i18n/vi-VN/app-overview.ts +++ b/web/i18n/vi-VN/app-overview.ts @@ -48,7 +48,7 @@ const translation = { title: 'Các bước quy trình', show: 'Hiển thị', hide: 'Ẩn', - showDesc: 'Hiển thị hoặc ẩn chi tiết dòng công việc trong WebApp', + showDesc: 'Hiển thị hoặc ẩn chi tiết dòng công việc trong web app', subTitle: 'Chi tiết quy trình làm việc', }, chatColorTheme: 'Giao diện màu trò chuyện', @@ -71,8 +71,8 @@ const translation = { }, sso: { title: 'SSO ứng dụng web', - description: 'Tất cả người dùng được yêu cầu đăng nhập bằng SSO trước khi sử dụng WebApp', - tooltip: 'Liên hệ với quản trị viên để bật SSO WebApp', + description: 'Tất cả người dùng được yêu cầu đăng nhập bằng SSO trước khi sử dụng web app', + tooltip: 'Liên hệ với quản trị viên để bật SSO web app', label: 'Xác thực SSO', }, modalTip: 'Cài đặt ứng dụng web phía máy khách.', diff --git a/web/i18n/vi-VN/app.ts b/web/i18n/vi-VN/app.ts index fd0a66ad03..c5f1a7496d 100644 --- a/web/i18n/vi-VN/app.ts +++ b/web/i18n/vi-VN/app.ts @@ -72,22 +72,22 @@ const translation = { appCreateDSLErrorPart3: 'Phiên bản DSL ứng dụng hiện tại:', appCreateDSLWarning: 'Phạt cảnh cáo: Sự khác biệt về phiên bản DSL có thể ảnh hưởng đến một số tính năng nhất định', appCreateDSLErrorPart4: 'Phiên bản DSL được hệ thống hỗ trợ:', - forBeginners: 'DÀNH CHO NGƯỜI MỚI BẮT ĐẦU', + forBeginners: 'Các loại ứng dụng cơ bản hơn', chooseAppType: 'Chọn loại ứng dụng', chatbotShortDescription: 'Chatbot dựa trên LLM với thiết lập đơn giản', noTemplateFoundTip: 'Hãy thử tìm kiếm bằng các từ khóa khác nhau.', - workflowShortDescription: 'Điều phối cho các tác vụ tự động hóa một lượt', + workflowShortDescription: 'Luồng tác nhân cho tự động hóa thông minh', optional: 'Tùy chọn', advancedShortDescription: 'Quy trình làm việc cho các cuộc đối thoại nhiều lượt phức tạp với bộ nhớ', - workflowUserDescription: 'Điều phối quy trình làm việc cho các tác vụ một vòng như tự động hóa và xử lý hàng loạt.', + workflowUserDescription: 'Xây dựng trực quan quy trình AI tự động bằng kéo thả đơn giản.', foundResults: '{{đếm}} Kết quả', chatbotUserDescription: 'Nhanh chóng xây dựng chatbot dựa trên LLM với cấu hình đơn giản. Bạn có thể chuyển sang Chatflow sau.', agentUserDescription: 'Một tác nhân thông minh có khả năng suy luận lặp đi lặp lại và sử dụng công cụ tự động để đạt được mục tiêu nhiệm vụ.', noIdeaTip: 'Không có ý tưởng? Kiểm tra các mẫu của chúng tôi', - advancedUserDescription: 'Điều phối quy trình làm việc cho các tác vụ đối thoại phức tạp nhiều vòng với khả năng bộ nhớ.', + advancedUserDescription: 'Quy trình với tính năng bộ nhớ bổ sung và giao diện chatbot.', forAdvanced: 'DÀNH CHO NGƯỜI DÙNG NÂNG CAO', foundResult: '{{đếm}} Kết quả', - agentShortDescription: 'Tác nhân thông minh với lý luận và sử dụng công cụ tự động', + agentShortDescription: 'Quy trình nâng cao cho hội thoại nhiều lượt', noTemplateFound: 'Không tìm thấy mẫu', noAppsFound: 'Không tìm thấy ứng dụng nào', learnMore: 'Tìm hiểu thêm', @@ -159,11 +159,15 @@ const translation = { description: 'Opik là một nền tảng mã nguồn mở để đánh giá, thử nghiệm và giám sát các ứng dụng LLM.', title: 'Opik', }, + weave: { + title: 'Dệt', + description: 'Weave là một nền tảng mã nguồn mở để đánh giá, thử nghiệm và giám sát các ứng dụng LLM.', + }, }, answerIcon: { - description: 'Có nên sử dụng biểu tượng WebApp để thay thế 🤖 trong ứng dụng được chia sẻ hay không', - descriptionInExplore: 'Có nên sử dụng biểu tượng WebApp để thay thế 🤖 trong Khám phá hay không', - title: 'Sử dụng biểu tượng WebApp để thay thế 🤖', + description: 'Có nên sử dụng biểu tượng web app để thay thế 🤖 trong ứng dụng được chia sẻ hay không', + descriptionInExplore: 'Có nên sử dụng biểu tượng web app để thay thế 🤖 trong Khám phá hay không', + title: 'Sử dụng biểu tượng web app để thay thế 🤖', }, importFromDSLFile: 'Từ tệp DSL', importFromDSL: 'Nhập từ DSL', @@ -194,6 +198,54 @@ const translation = { noParams: 'Không cần thông số', label: 'Ứng dụng', }, + structOutput: { + configure: 'Cấu hình', + required: 'Yêu cầu', + notConfiguredTip: 'Đầu ra có cấu trúc chưa được cấu hình.', + modelNotSupported: 'Mô hình không được hỗ trợ', + LLMResponse: 'Phản hồi của LLM', + structured: 'Cấu trúc', + structuredTip: 'Đầu ra có cấu trúc là một tính năng đảm bảo rằng mô hình sẽ luôn tạo ra các phản hồi tuân theo sơ đồ JSON mà bạn cung cấp.', + modelNotSupportedTip: 'Mô hình hiện tại không hỗ trợ tính năng này và tự động bị hạ cấp xuống việc tiêm lệnh.', + moreFillTip: 'Hiển thị tối đa 10 cấp độ lồng ghép', + }, + accessItemsDescription: { + anyone: 'Mọi người đều có thể truy cập ứng dụng web.', + specific: 'Chỉ những nhóm hoặc thành viên cụ thể mới có thể truy cập ứng dụng web.', + organization: 'Bất kỳ ai trong tổ chức đều có thể truy cập ứng dụng web.', + external: 'Chỉ những người dùng bên ngoài đã xác thực mới có thể truy cập vào ứng dụng Web.', + }, + accessControlDialog: { + accessItems: { + anyone: 'Ai có liên kết', + specific: 'Các nhóm hoặc thành viên cụ thể', + organization: 'Chỉ các thành viên trong doanh nghiệp', + external: 'Người dùng bên ngoài được xác thực', + }, + operateGroupAndMember: { + searchPlaceholder: 'Tìm kiếm nhóm và thành viên', + allMembers: 'Tất cả các thành viên', + expand: 'Mở rộng', + noResult: 'Không có kết quả', + }, + title: 'Kiểm soát truy cập ứng dụng web', + description: 'Cài đặt quyền truy cập ứng dụng web', + accessLabel: 'Ai có quyền truy cập', + groups_one: '{{count}} NHÓM', + groups_other: '{{count}} NHÓM', + members_one: '{{count}} THÀNH VIÊN', + members_other: '{{count}} THÀNH VIÊN', + noGroupsOrMembers: 'Không có nhóm hoặc thành viên nào được chọn', + webAppSSONotEnabledTip: 'Vui lòng liên hệ với quản trị viên doanh nghiệp để cấu hình phương thức xác thực ứng dụng web.', + updateSuccess: 'Cập nhật thành công', + }, + publishApp: { + title: 'Ai có thể truy cập ứng dụng web', + notSet: 'Chưa đặt', + notSetDesc: 'Hiện tại không ai có thể truy cập ứng dụng web. Vui lòng thiết lập quyền truy cập.', + }, + noAccessPermission: 'Không được phép truy cập ứng dụng web', + accessControl: 'Kiểm soát truy cập ứng dụng web', } export default translation diff --git a/web/i18n/vi-VN/billing.ts b/web/i18n/vi-VN/billing.ts index 595481e3a4..3a8ac03ffb 100644 --- a/web/i18n/vi-VN/billing.ts +++ b/web/i18n/vi-VN/billing.ts @@ -69,6 +69,7 @@ const translation = { messageRequest: { title: 'Số Lượng Tin Nhắn', tooltip: 'Hạn mức triệu hồi tin nhắn cho các kế hoạch sử dụng mô hình OpenAI (ngoại trừ gpt4). Các tin nhắn vượt quá giới hạn sẽ sử dụng Khóa API OpenAI của bạn.', + titlePerMonth: '{{count,number}} tin nhắn/tháng', }, annotatedResponse: { title: 'Hạn Mức Quota Phản hồi Đã được Ghi chú', @@ -77,27 +78,94 @@ const translation = { ragAPIRequestTooltip: 'Đề cập đến số lượng cuộc gọi API triệu hồi chỉ khả năng xử lý cơ sở kiến thức của Dify.', receiptInfo: 'Chỉ chủ nhóm và quản trị viên nhóm có thể đăng ký và xem thông tin thanh toán', annotationQuota: 'Hạn ngạch chú thích', + priceTip: 'mỗi không gian làm việc/', + documentsTooltip: 'Hạn ngạch số lượng tài liệu được nhập từ Nguồn Dữ liệu Kiến thức.', + apiRateLimitTooltip: 'Giới hạn tần suất API áp dụng cho tất cả các yêu cầu được thực hiện thông qua API Dify, bao gồm tạo văn bản, cuộc trò chuyện, thực thi quy trình làm việc và xử lý tài liệu.', + teamMember_one: '{{count,number}} thành viên trong nhóm', + apiRateLimit: 'Giới hạn tần suất API', + annualBilling: 'Hóa đơn hàng năm', + cloud: 'Dịch vụ đám mây', + documentsRequestQuota: '{{count,number}}/phút Giới Hạn Tỷ Lệ Yêu Cầu Kiến Thức', + self: 'Tự lưu trữ', + comparePlanAndFeatures: 'So sánh các kế hoạch & tính năng', + freeTrialTip: 'dùng thử miễn phí 200 cuộc gọi OpenAI.', + freeTrialTipPrefix: 'Đăng ký và nhận một', + unlimitedApiRate: 'Không giới hạn tỷ lệ API', + teamWorkspace: '{{count,number}} Không gian làm việc của Đội', + teamMember_other: '{{count,number}} thành viên trong nhóm', + documents: '{{count,number}} Tài liệu Kiến thức', + getStarted: 'Bắt đầu', + apiRateLimitUnit: '{{count,number}}/ngày', + freeTrialTipSuffix: 'Không cần thẻ tín dụng', + documentsRequestQuotaTooltip: 'Chỉ định tổng số hành động mà một không gian làm việc có thể thực hiện mỗi phút trong cơ sở tri thức, bao gồm tạo mới tập dữ liệu, xóa, cập nhật, tải tài liệu lên, thay đổi, lưu trữ và truy vấn cơ sở tri thức. Chỉ số này được sử dụng để đánh giá hiệu suất của các yêu cầu cơ sở tri thức. Ví dụ, nếu một người dùng Sandbox thực hiện 10 lần kiểm tra liên tiếp trong một phút, không gian làm việc của họ sẽ bị hạn chế tạm thời không thực hiện các hành động sau trong phút tiếp theo: tạo mới tập dữ liệu, xóa, cập nhật và tải tài liệu lên hoặc thay đổi.', }, plans: { sandbox: { name: 'Hộp Cát', description: 'Thử nghiệm miễn phí 200 lần GPT', includesTitle: 'Bao gồm:', + for: 'Dùng thử miễn phí các tính năng cốt lõi', }, professional: { name: 'Chuyên nghiệp', description: 'Dành cho cá nhân và các nhóm nhỏ để mở khóa nhiều sức mạnh với giá cả phải chăng.', includesTitle: 'Tất cả trong kế hoạch miễn phí, cộng thêm:', + for: 'Dành cho các nhà phát triển độc lập/nhóm nhỏ', }, team: { name: 'Nhóm', description: 'Hợp tác mà không giới hạn và tận hưởng hiệu suất hạng nhất.', includesTitle: 'Tất cả trong kế hoạch Chuyên nghiệp, cộng thêm:', + for: 'Dành cho các đội nhóm vừa', }, enterprise: { name: 'Doanh nghiệp', description: 'Nhận toàn bộ khả năng và hỗ trợ cho các hệ thống quan trọng cho nhiệm vụ quy mô lớn.', includesTitle: 'Tất cả trong kế hoạch Nhóm, cộng thêm:', + features: { + 2: 'Tính năng Doanh nghiệp Độc quyền', + 1: 'Giấy phép kinh doanh', + 8: 'Hỗ trợ kỹ thuật chuyên nghiệp', + 7: 'Cập nhật và Bảo trì bởi Dify Chính thức', + 5: 'Thỏa thuận SLA bởi các đối tác Dify', + 6: 'An ninh nâng cao và kiểm soát', + 3: 'Nhiều không gian làm việc & Quản lý doanh nghiệp', + 0: 'Giải pháp triển khai mở rộng quy mô cấp doanh nghiệp', + 4: 'SSO', + }, + price: 'Tùy chỉnh', + for: 'Dành cho các đội lớn', + priceTip: 'Chỉ thanh toán hàng năm', + btnText: 'Liên hệ với Bộ phận Bán hàng', + }, + community: { + features: { + 2: 'Tuân thủ Giấy phép Mã nguồn Mở Dify', + 0: 'Tất cả các tính năng cốt lõi được phát hành dưới Kho lưu trữ công khai', + 1: 'Không gian làm việc đơn', + }, + description: 'Dành cho người dùng cá nhân, nhóm nhỏ hoặc các dự án phi thương mại', + name: 'Cộng đồng', + btnText: 'Bắt đầu với Cộng đồng', + price: 'Miễn phí', + for: 'Dành cho người dùng cá nhân, nhóm nhỏ hoặc các dự án phi thương mại', + includesTitle: 'Tính năng miễn phí:', + }, + premium: { + features: { + 3: 'Hỗ trợ qua Email & Chat Ưu tiên', + 2: 'Tùy chỉnh Logo & Thương hiệu Ứng dụng Web', + 1: 'Không gian làm việc đơn', + 0: 'Độ tin cậy tự quản lý bởi các nhà cung cấp đám mây khác nhau', + }, + comingSoon: 'Hỗ trợ Microsoft Azure & Google Cloud Sẽ Đến Sớm', + priceTip: 'Dựa trên Thị trường Đám mây', + btnText: 'Nhận Premium trong', + description: 'Dành cho các tổ chức và nhóm vừa', + price: 'Có thể mở rộng', + includesTitle: 'Mọi thứ từ Cộng đồng, cộng thêm:', + for: 'Dành cho các tổ chức và nhóm vừa', + name: 'Cao cấp', }, }, vectorSpace: { @@ -107,12 +175,26 @@ const translation = { apps: { fullTipLine1: 'Nâng cấp kế hoạch của bạn để', fullTipLine2: 'xây dựng thêm ứng dụng.', + contactUs: 'Liên hệ với chúng tôi', + fullTip2: 'Đã đạt giới hạn kế hoạch', + fullTip1des: 'Bạn đã đạt đến giới hạn xây dựng ứng dụng trên kế hoạch này.', + fullTip1: 'Nâng cấp để tạo thêm ứng dụng', + fullTip2des: 'Chúng tôi khuyên bạn nên xóa các ứng dụng không hoạt động để giải phóng dung lượng, hoặc liên hệ với chúng tôi.', }, annotatedResponse: { fullTipLine1: 'Nâng cấp kế hoạch của bạn để', fullTipLine2: 'ghi chú thêm cuộc trò chuyện.', quotaTitle: 'Hạn Mức Quota Phản hồi Đã được Ghi chú', }, + usagePage: { + documentsUploadQuota: 'Hạn ngạch tải lên tài liệu', + annotationQuota: 'Hạn ngạch chú thích', + vectorSpaceTooltip: 'Các tài liệu với chế độ lập chỉ mục Chất lượng Cao sẽ tiêu tốn tài nguyên Lưu trữ Dữ liệu Kiến thức. Khi Lưu trữ Dữ liệu Kiến thức đạt giới hạn, các tài liệu mới sẽ không được tải lên.', + teamMembers: 'Các thành viên trong nhóm', + vectorSpace: 'Lưu trữ dữ liệu kiến thức', + buildApps: 'Xây dựng ứng dụng', + }, + teamMembers: 'Các thành viên trong nhóm', } export default translation diff --git a/web/i18n/vi-VN/common.ts b/web/i18n/vi-VN/common.ts index a047fb6c9f..323ca60152 100644 --- a/web/i18n/vi-VN/common.ts +++ b/web/i18n/vi-VN/common.ts @@ -54,6 +54,10 @@ const translation = { viewDetails: 'Xem chi tiết', copied: 'Sao chép', in: 'trong', + more: 'Hơn', + downloadFailed: 'Tải xuống thất bại. Vui lòng thử lại sau.', + format: 'Định dạng', + downloadSuccess: 'Tải xuống đã hoàn thành.', }, placeholder: { input: 'Vui lòng nhập', @@ -141,6 +145,8 @@ const translation = { newDataset: 'Tạo Kiến thức', tools: 'Công cụ', exploreMarketplace: 'Khám phá Marketplace', + appDetail: 'Chi tiết ứng dụng', + account: 'báo cáo', }, userProfile: { settings: 'Cài đặt', @@ -153,6 +159,9 @@ const translation = { community: 'Cộng đồng', about: 'Về chúng tôi', logout: 'Đăng xuất', + compliance: 'Tuân thủ', + github: 'GitHub', + support: 'Hỗ trợ', }, settings: { accountGroup: 'TÀI KHOẢN', @@ -202,6 +211,9 @@ const translation = { feedbackTitle: 'Phản hồi', feedbackLabel: 'Hãy cho chúng tôi biết lý do tại sao bạn xóa tài khoản của mình?', feedbackPlaceholder: 'Tùy chọn', + workspaceIcon: 'Biểu tượng không gian làm việc', + workspaceName: 'Tên không gian làm việc', + editWorkspaceInfo: 'Chỉnh sửa thông tin không gian làm việc', }, members: { team: 'Nhóm', @@ -455,7 +467,7 @@ const translation = { apiBasedExtension: { title: 'Các tiện ích API cung cấp quản lý API tập trung, giúp cấu hình dễ dàng sử dụng trên các ứng dụng của Dify.', link: 'Tìm hiểu cách phát triển Phần mở rộng API của riêng bạn.', - linkUrl: 'https://docs.dify.ai/features/extension/api_based_extension', + linkUrl: 'https://docs.dify.ai/en/guides/extension/api-based-extension/README', add: 'Thêm Phần mở rộng API', selector: { title: 'Phần mở rộng API', @@ -540,9 +552,10 @@ const translation = { vectorHash: 'Vector hash:', hitScore: 'Điểm truy xuất:', }, - inputPlaceholder: 'Nói chuyện với Bot', + inputPlaceholder: 'Nói chuyện với {{botName}}', thought: 'Tư duy', thinking: 'Suy nghĩ...', + resend: 'Gửi lại', }, promptEditor: { placeholder: 'Viết từ khóa của bạn ở đây, nhập \'{\' để chèn một biến, nhập \'/\' để chèn một khối nội dung nhắc nhở', @@ -633,10 +646,31 @@ const translation = { license: { expiring_plural: 'Hết hạn sau {{count}} ngày', expiring: 'Hết hạn trong một ngày', + unlimited: 'Vô hạn', }, pagination: { perPage: 'Mục trên mỗi trang', }, + theme: { + auto: 'hệ thống', + theme: 'Chủ đề', + light: 'ánh sáng', + dark: 'tối', + }, + compliance: { + iso27001: 'Chứng nhận ISO 27001:2022', + gdpr: 'GDPR DPA', + soc2Type1: 'Báo cáo loại SOC 2 Type I', + professionalUpgradeTooltip: 'Chỉ có sẵn với gói Team hoặc cao hơn.', + sandboxUpgradeTooltip: 'Chỉ có sẵn với gói Chuyên nghiệp hoặc Nhóm.', + soc2Type2: 'Báo cáo SOC 2 Type II', + }, + imageInput: { + supportedFormats: 'Hỗ trợ PNG, JPG, JPEG, WEBP và GIF', + dropImageHere: 'Kéo hình ảnh của bạn vào đây, hoặc', + browse: 'duyệt', + }, + you: 'Bạn', } export default translation diff --git a/web/i18n/vi-VN/custom.ts b/web/i18n/vi-VN/custom.ts index 6c91e519b0..4122b5d35c 100644 --- a/web/i18n/vi-VN/custom.ts +++ b/web/i18n/vi-VN/custom.ts @@ -3,9 +3,11 @@ const translation = { upgradeTip: { prefix: 'Nâng cấp gói của bạn để', suffix: 'tùy chỉnh thương hiệu.', + des: 'Nâng cấp kế hoạch của bạn để tùy chỉnh thương hiệu của bạn', + title: 'Nâng cấp gói của bạn', }, webapp: { - title: 'Tùy chỉnh thương hiệu WebApp', + title: 'Tùy chỉnh thương hiệu web app', removeBrand: 'Xóa "Được hỗ trợ bởi Dify"', changeLogo: 'Thay đổi logo "Được hỗ trợ bởi"', changeLogoTip: 'Định dạng SVG hoặc PNG với kích thước tối thiểu 40x40px', diff --git a/web/i18n/vi-VN/dataset-creation.ts b/web/i18n/vi-VN/dataset-creation.ts index cae2bfb814..f909a51770 100644 --- a/web/i18n/vi-VN/dataset-creation.ts +++ b/web/i18n/vi-VN/dataset-creation.ts @@ -22,7 +22,7 @@ const translation = { }, uploader: { title: 'Tải lên tệp văn bản', - button: 'Kéo và thả tệp, hoặc', + button: 'Kéo và thả các tập tin hoặc thư mục, hoặc', browse: 'Chọn tệp', tip: 'Hỗ trợ {{supportTypes}}. Tối đa {{size}}MB mỗi tệp.', validation: { @@ -63,7 +63,7 @@ const translation = { unknownError: 'Lỗi không xác định', extractOnlyMainContent: 'Chỉ trích xuất nội dung chính (không có đầu trang, điều hướng, chân trang, v.v.)', exceptionErrorTitle: 'Một ngoại lệ xảy ra trong khi chạy tác vụ Firecrawl:', - firecrawlDocLink: 'https://docs.dify.ai/guides/knowledge-base/sync-from-website', + firecrawlDocLink: 'https://docs.dify.ai/en/guides/knowledge-base/create-knowledge-and-upload-documents/import-content-data/sync-from-website', selectAll: 'Chọn tất cả', firecrawlTitle: 'Trích xuất nội dung web bằng 🔥Firecrawl', totalPageScraped: 'Tổng số trang được cạo:', @@ -82,6 +82,14 @@ const translation = { jinaReaderNotConfigured: 'Jina Reader không được cấu hình', jinaReaderNotConfiguredDescription: 'Thiết lập Jina Reader bằng cách nhập khóa API miễn phí của bạn để truy cập.', useSitemapTooltip: 'Thực hiện theo sơ đồ trang web để thu thập dữ liệu trang web. Nếu không, Jina Reader sẽ thu thập dữ liệu lặp đi lặp lại dựa trên mức độ liên quan của trang, mang lại ít trang hơn nhưng chất lượng cao hơn.', + configureWatercrawl: 'Cấu hình Watercrawl', + configureFirecrawl: 'Cấu hình Firecrawl', + configureJinaReader: 'Cấu hình Jina Reader', + waterCrawlNotConfiguredDescription: 'Cấu hình Watercrawl với khóa API để sử dụng nó.', + watercrawlDocLink: 'https://docs.dify.ai/en/guides/knowledge-base/create-knowledge-and-upload-documents/import-content-data/sync-from-website', + watercrawlTitle: 'Trích xuất nội dung web bằng Watercrawl', + watercrawlDoc: 'Tài liệu Watercrawl', + waterCrawlNotConfigured: 'Watercrawl chưa được cấu hình', }, cancel: 'Hủy', }, @@ -200,6 +208,11 @@ const translation = { description: 'Hiện tại, cơ sở tri thức của Dify chỉ có nguồn dữ liệu hạn chế. Đóng góp nguồn dữ liệu vào cơ sở kiến thức Dify là một cách tuyệt vời để giúp nâng cao tính linh hoạt và sức mạnh của nền tảng cho tất cả người dùng. Hướng dẫn đóng góp của chúng tôi giúp bạn dễ dàng bắt đầu. Vui lòng nhấp vào liên kết bên dưới để tìm hiểu thêm.', learnMore: 'Tìm hiểu thêm', }, + watercrawl: { + configWatercrawl: 'Cấu hình Watercrawl', + apiKeyPlaceholder: 'Khóa API từ watercrawl.dev', + getApiKeyLinkText: 'Lấy mã API của bạn từ watercrawl.dev', + }, } export default translation diff --git a/web/i18n/vi-VN/dataset-settings.ts b/web/i18n/vi-VN/dataset-settings.ts index 7add91884e..b24f130b97 100644 --- a/web/i18n/vi-VN/dataset-settings.ts +++ b/web/i18n/vi-VN/dataset-settings.ts @@ -25,6 +25,7 @@ const translation = { learnMore: 'Tìm hiểu thêm', description: ' về phương pháp truy xuất.', longDescription: ' về phương pháp truy xuất. Bạn có thể thay đổi điều này bất kỳ lúc nào trong cài đặt Kiến thức.', + method: 'Phương pháp truy xuất', }, save: 'Lưu', permissionsInvitedMembers: 'Thành viên một phần trong nhóm', diff --git a/web/i18n/vi-VN/dataset.ts b/web/i18n/vi-VN/dataset.ts index 1ab84bb85f..4227d712a0 100644 --- a/web/i18n/vi-VN/dataset.ts +++ b/web/i18n/vi-VN/dataset.ts @@ -168,6 +168,54 @@ const translation = { documentsDisabled: '{{num}} tài liệu bị vô hiệu hóa - không hoạt động trong hơn 30 ngày', allKnowledge: 'Tất cả kiến thức', allKnowledgeDescription: 'Chọn để hiển thị tất cả kiến thức trong không gian làm việc này. Chỉ Chủ sở hữu không gian làm việc mới có thể quản lý tất cả kiến thức.', + metadata: { + createMetadata: { + name: 'Tên', + namePlaceholder: 'Thêm tên siêu dữ liệu', + type: 'Loại', + title: 'Siêu dữ liệu mới', + back: 'Quay lại', + }, + checkName: { + invalid: 'Tên siêu dữ liệu chỉ có thể chứa chữ cái thường, số và dấu gạch dưới, và phải bắt đầu bằng một chữ cái thường.', + empty: 'Tên siêu dữ liệu không được để trống', + }, + batchEditMetadata: { + applyToAllSelectDocumentTip: 'Tự động tạo tất cả các siêu dữ liệu đã chỉnh sửa và mới cho tất cả các tài liệu được chọn, nếu không, việc chỉnh sửa siêu dữ liệu sẽ chỉ áp dụng cho các tài liệu có nó.', + multipleValue: 'Nhiều giá trị', + editDocumentsNum: 'Chỉnh sửa {{num}} tài liệu', + applyToAllSelectDocument: 'Áp dụng cho tất cả các tài liệu đã chọn', + editMetadata: 'Chỉnh sửa siêu dữ liệu', + }, + selectMetadata: { + manageAction: 'Quản lý', + search: 'Tìm kiếm siêu dữ liệu', + newAction: 'Siêu dữ liệu mới', + }, + datasetMetadata: { + disabled: 'Tắt', + rename: 'Đổi tên', + namePlaceholder: 'Tên siêu dữ liệu', + builtIn: 'Tích hợp sẵn', + deleteTitle: 'Xác nhận để xóa', + name: 'Tên', + values: '{{num}} Giá trị', + description: 'Bạn có thể quản lý tất cả metadata trong kiến thức này ở đây. Những thay đổi sẽ được đồng bộ hóa đến mọi tài liệu.', + deleteContent: 'Bạn có chắc chắn muốn xóa siêu dữ liệu "{{name}}" không?', + builtInDescription: 'Siêu dữ liệu được tích hợp sẵn sẽ tự động được trích xuất và tạo ra. Nó phải được bật trước khi sử dụng và không thể chỉnh sửa.', + addMetaData: 'Thêm siêu dữ liệu', + }, + documentMetadata: { + documentInformation: 'Thông tin tài liệu', + technicalParameters: 'Các Thông Số Kỹ Thuật', + metadataToolTip: 'Dữ liệu siêu thông tin đóng vai trò là một bộ lọc quan trọng giúp nâng cao độ chính xác và tính liên quan của việc truy xuất thông tin. Bạn có thể chỉnh sửa và thêm dữ liệu siêu thông tin cho tài liệu này ở đây.', + startLabeling: 'Bắt đầu gán nhãn', + }, + addMetadata: 'Thêm siêu dữ liệu', + chooseTime: 'Chọn một thời gian...', + metadata: 'Siêu dữ liệu', + }, + embeddingModelNotAvailable: 'Mô hình nhúng không khả dụng.', } export default translation diff --git a/web/i18n/vi-VN/education.ts b/web/i18n/vi-VN/education.ts new file mode 100644 index 0000000000..35614934d9 --- /dev/null +++ b/web/i18n/vi-VN/education.ts @@ -0,0 +1,47 @@ +const translation = { + toVerifiedTip: { + coupon: 'mã giảm giá độc quyền 100%', + front: 'Bạn hiện đủ điều kiện để có trạng thái Xác minh Giáo dục. Vui lòng nhập thông tin giáo dục của bạn bên dưới để hoàn tất quá trình và nhận một', + end: 'cho Kế hoạch Chuyên nghiệp Dify.', + }, + form: { + schoolName: { + placeholder: 'Nhập tên chính thức, không viết tắt của trường bạn', + title: 'Tên Trường Của Bạn', + }, + schoolRole: { + option: { + teacher: 'Giáo viên', + student: 'Học sinh', + administrator: 'Quản trị viên trường học', + }, + title: 'Vai trò của bạn ở trường', + }, + terms: { + desc: { + termsOfService: 'Điều khoản dịch vụ', + privacyPolicy: 'Chính sách bảo mật', + and: 'và', + end: 'Bằng cách gửi:', + front: 'Thông tin của bạn và việc sử dụng trạng thái Được xác minh Giáo dục của chúng tôi thuộc về điều kiện của chúng tôi', + }, + option: { + age: 'Tôi xác nhận rằng tôi ít nhất 18 tuổi', + inSchool: 'Tôi xác nhận rằng tôi đã đăng ký hoặc làm việc tại cơ sở đã cung cấp. Dify có thể yêu cầu chứng minh về việc đăng ký/làm việc. Nếu tôi cung cấp thông tin không chính xác về đủ điều kiện của mình, tôi đồng ý trả bất kỳ khoản phí nào ban đầu được miễn dựa trên tình trạng giáo dục của tôi.', + }, + title: 'Điều khoản & Thỏa thuận', + }, + }, + toVerified: 'Xác thực giáo dục', + successTitle: 'Bạn đã được xác minh giáo dục Dify', + submit: 'Gửi', + rejectTitle: 'Yêu cầu xác minh giáo dục Dify của bạn đã bị từ chối', + successContent: 'Chúng tôi đã phát hành một phiếu giảm giá 100% cho gói Dify Professional vào tài khoản của bạn. Phiếu giảm giá có hiệu lực trong một năm, vui lòng sử dụng nó trong thời gian hiệu lực.', + currentSigned: 'HIỆN ĐANG ĐĂNG NHẬP VÀO', + learn: 'Học cách xác minh trình độ giáo dục', + emailLabel: 'Email hiện tại của bạn', + rejectContent: 'Rất tiếc, bạn không đủ điều kiện để nhận trạng thái Xác minh Giáo dục và do đó không thể nhận được mã giảm giá độc quyền 100% cho Kế hoạch Chuyên nghiệp Dify nếu bạn sử dụng địa chỉ email này.', + submitError: 'Gửi mẫu không thành công. Vui lòng thử lại sau.', +} + +export default translation diff --git a/web/i18n/vi-VN/explore.ts b/web/i18n/vi-VN/explore.ts index b1254fa8cd..860bfd76f0 100644 --- a/web/i18n/vi-VN/explore.ts +++ b/web/i18n/vi-VN/explore.ts @@ -37,6 +37,7 @@ const translation = { HR: 'Nhân sự', Agent: 'Người đại lý', Workflow: 'Quy trình làm việc', + Entertainment: 'Giải trí', }, } diff --git a/web/i18n/vi-VN/login.ts b/web/i18n/vi-VN/login.ts index ab4ab68f48..cc81bd8193 100644 --- a/web/i18n/vi-VN/login.ts +++ b/web/i18n/vi-VN/login.ts @@ -105,6 +105,11 @@ const translation = { licenseExpired: 'Giấy phép đã hết hạn', licenseExpiredTip: 'Giấy phép Dify Enterprise cho không gian làm việc của bạn đã hết hạn. Vui lòng liên hệ với quản trị viên của bạn để tiếp tục sử dụng Dify.', licenseLostTip: 'Không thể kết nối máy chủ cấp phép Dify. Vui lòng liên hệ với quản trị viên của bạn để tiếp tục sử dụng Dify.', + webapp: { + noLoginMethod: 'Phương thức xác thực chưa được cấu hình cho ứng dụng web', + noLoginMethodTip: 'Vui lòng liên hệ với quản trị viên hệ thống để thêm phương thức xác thực.', + disabled: 'Xác thực webapp đã bị vô hiệu hóa. Vui lòng liên hệ với quản trị hệ thống để kích hoạt nó. Bạn có thể thử sử dụng ứng dụng trực tiếp.', + }, } export default translation diff --git a/web/i18n/vi-VN/plugin.ts b/web/i18n/vi-VN/plugin.ts index 512e27b6a5..4943888142 100644 --- a/web/i18n/vi-VN/plugin.ts +++ b/web/i18n/vi-VN/plugin.ts @@ -62,6 +62,7 @@ const translation = { settings: 'CÀI ĐẶT NGƯỜI DÙNG', empty: 'Nhấp vào nút \'+\' để thêm công cụ. Bạn có thể thêm nhiều công cụ.', unsupportedTitle: 'Hành động không được hỗ trợ', + toolSetting: 'Cài đặt công cụ', }, switchVersion: 'Chuyển đổi phiên bản', endpointDisableTip: 'Tắt điểm cuối', @@ -180,6 +181,8 @@ const translation = { sortBy: 'Thành phố đen', noPluginFound: 'Không tìm thấy plugin nào', and: 'và', + verifiedTip: 'Được xác nhận bởi Dify', + partnerTip: 'Được xác nhận bởi một đối tác của Dify', }, task: { installingWithError: 'Cài đặt {{installingLength}} plugins, {{successLength}} thành công, {{errorLength}} không thành công', @@ -195,7 +198,6 @@ const translation = { endpointsEnabled: '{{num}} bộ điểm cuối được kích hoạt', install: '{{num}} lượt cài đặt', findMoreInMarketplace: 'Tìm thêm trong Marketplace', - submitPlugin: 'Gửi plugin', search: 'Tìm kiếm', searchCategories: 'Danh mục tìm kiếm', installPlugin: 'Cài đặt plugin', @@ -204,6 +206,12 @@ const translation = { allCategories: 'Tất cả các danh mục', searchTools: 'Công cụ tìm kiếm...', installFrom: 'CÀI ĐẶT TỪ', + metadata: { + title: 'Plugin', + }, + difyVersionNotCompatible: 'Phiên bản Dify hiện tại không tương thích với plugin này, vui lòng nâng cấp lên phiên bản tối thiểu cần thiết: {{minimalDifyVersion}}', + requestAPlugin: 'Yêu cầu một plugin', + publishPlugins: 'Xuất bản plugin', } export default translation diff --git a/web/i18n/vi-VN/share-app.ts b/web/i18n/vi-VN/share-app.ts index 7078ecc299..12a31bd40b 100644 --- a/web/i18n/vi-VN/share-app.ts +++ b/web/i18n/vi-VN/share-app.ts @@ -26,6 +26,12 @@ const translation = { }, tryToSolve: 'Thử giải quyết', temporarySystemIssue: 'Xin lỗi, hệ thống đang gặp sự cố tạm thời.', + expand: 'Mở rộng', + collapse: 'Thu gọn', + chatFormTip: 'Cài đặt trò chuyện không thể được thay đổi sau khi cuộc trò chuyện đã bắt đầu.', + newChatTip: 'Đã ở trong một cuộc trò chuyện mới', + viewChatSettings: 'Xem cài đặt trò chuyện', + chatSettingsTitle: 'Cài đặt trò chuyện mới', }, generation: { tabs: { @@ -64,6 +70,11 @@ const translation = { moreThanMaxLengthLine: 'Dòng {{rowIndex}}: {{varName}} không thể chứa quá {{maxLength}} ký tự', atLeastOne: 'Vui lòng nhập ít nhất một dòng vào tệp đã tải lên.', }, + executions: '{{num}} ÁN TỬ HÌNH', + execution: 'THI HÀNH', + }, + login: { + backToHome: 'Trở về Trang Chủ', }, } diff --git a/web/i18n/vi-VN/time.ts b/web/i18n/vi-VN/time.ts index e2410dd34b..9c07eceb4d 100644 --- a/web/i18n/vi-VN/time.ts +++ b/web/i18n/vi-VN/time.ts @@ -1,3 +1,37 @@ -const translation = {} +const translation = { + daysInWeek: { + Mon: 'Môn', + Sat: 'Ngồi', + Wed: 'Thứ Tư', + Sun: 'Mặt trời', + Tue: 'Thứ Ba', + Fri: 'Thứ Sáu', + Thu: 'Thu', + }, + months: { + April: 'Tháng Tư', + June: 'Tháng Sáu', + September: 'Tháng Chín', + March: 'Tháng Ba', + February: 'Tháng Hai', + August: 'Tháng Tám', + May: 'Tháng Năm', + October: 'Tháng Mười', + December: 'Tháng Mười Hai', + January: 'Tháng Một', + July: 'Tháng Bảy', + November: 'Tháng Mười Một', + }, + operation: { + ok: 'Được rồi', + pickDate: 'Chọn Ngày', + now: 'Bây giờ', + cancel: 'Hủy bỏ', + }, + title: { + pickTime: 'Chọn Thời Gian', + }, + defaultPlaceholder: 'Chọn một thời gian...', +} export default translation diff --git a/web/i18n/vi-VN/tools.ts b/web/i18n/vi-VN/tools.ts index 75331b5251..ec4665cbf5 100644 --- a/web/i18n/vi-VN/tools.ts +++ b/web/i18n/vi-VN/tools.ts @@ -14,7 +14,6 @@ const translation = { }, author: 'Tác giả', auth: { - unauthorized: 'Chưa xác thực', authorized: 'Đã xác thực', setup: 'Thiết lập xác thực để sử dụng', setupModalTitle: 'Thiết lập xác thực', diff --git a/web/i18n/vi-VN/workflow.ts b/web/i18n/vi-VN/workflow.ts index db56350daf..07b804749f 100644 --- a/web/i18n/vi-VN/workflow.ts +++ b/web/i18n/vi-VN/workflow.ts @@ -38,8 +38,6 @@ const translation = { setVarValuePlaceholder: 'Đặt giá trị biến', needConnectTip: 'Bước này không được kết nối với bất kỳ điều gì', maxTreeDepth: 'Giới hạn tối đa {{depth}} nút trên mỗi nhánh', - needEndNode: 'Phải thêm khối Kết thúc', - needAnswerNode: 'Phải thêm khối Trả lời', workflowProcess: 'Quy trình làm việc', notRunning: 'Chưa chạy', previewPlaceholder: 'Nhập nội dung vào hộp bên dưới để bắt đầu gỡ lỗi Chatbot', @@ -58,7 +56,6 @@ const translation = { learnMore: 'Tìm hiểu thêm', copy: 'Sao chép', duplicate: 'Nhân bản', - addBlock: 'Thêm khối', pasteHere: 'Dán vào đây', pointerMode: 'Chế độ con trỏ', handMode: 'Chế độ tay', @@ -106,6 +103,18 @@ const translation = { addFailureBranch: 'Thêm nhánh Fail', loadMore: 'Tải thêm quy trình làm việc', noHistory: 'Không có lịch sử', + versionHistory: 'Lịch sử phiên bản', + publishUpdate: 'Cập nhật xuất bản', + exportSVG: 'Xuất dưới dạng SVG', + exitVersions: 'Phiên bản thoát', + exportImage: 'Xuất hình ảnh', + exportPNG: 'Xuất dưới dạng PNG', + noExist: 'Không có biến như vậy', + exportJPEG: 'Xuất dưới dạng JPEG', + referenceVar: 'Biến tham chiếu', + needAnswerNode: 'Nút Trả lời phải được thêm vào', + addBlock: 'Thêm Node', + needEndNode: 'Nút Kết thúc phải được thêm vào', }, env: { envPanelTitle: 'Biến Môi Trường', @@ -167,19 +176,19 @@ const translation = { stepForward_other: '{{count}} bước tiến', sessionStart: 'Bắt đầu phiên', currentState: 'Trạng thái hiện tại', - nodeTitleChange: 'Tiêu đề khối đã thay đổi', - nodeDescriptionChange: 'Mô tả khối đã thay đổi', - nodeDragStop: 'Khối đã di chuyển', - nodeChange: 'Khối đã thay đổi', - nodeConnect: 'Khối đã kết nối', - nodePaste: 'Khối đã dán', - nodeDelete: 'Khối đã xóa', - nodeAdd: 'Khối đã thêm', - nodeResize: 'Khối đã thay đổi kích thước', noteAdd: 'Ghi chú đã thêm', noteChange: 'Ghi chú đã thay đổi', noteDelete: 'Ghi chú đã xóa', - edgeDelete: 'Khối đã ngắt kết nối', + nodeAdd: 'Đã thêm nút', + nodeChange: 'Node đã thay đổi', + nodeDescriptionChange: 'Mô tả nút đã thay đổi', + nodeTitleChange: 'Tiêu đề nút đã được thay đổi', + nodeDelete: 'Nút đã bị xóa', + nodeDragStop: 'Nút đã được di chuyển', + nodeConnect: 'Nút đã kết nối', + nodeResize: 'Kích thước nút đã được thay đổi', + nodePaste: 'Node đã dán', + edgeDelete: 'Nút đã bị ngắt kết nối', }, errorMsg: { fieldRequired: '{{field}} là bắt buộc', @@ -205,10 +214,9 @@ const translation = { testRunIteration: 'Lặp chạy thử nghiệm', back: 'Quay lại', iteration: 'Lặp', + loop: 'Vòng', }, tabs: { - 'searchBlock': 'Tìm kiếm khối', - 'blocks': 'Khối', 'tools': 'Công cụ', 'allTool': 'Tất cả', 'builtInTool': 'Tích hợp sẵn', @@ -218,10 +226,12 @@ const translation = { 'logic': 'Logic', 'transform': 'Chuyển đổi', 'utilities': 'Tiện ích', - 'noResult': 'Không tìm thấy kế;t quả phù hợp', + 'noResult': 'Không tìm thấy kế. t quả phù hợp', 'searchTool': 'Công cụ tìm kiếm', 'agent': 'Chiến lược đại lý', 'plugin': 'Plugin', + 'blocks': 'Nút', + 'searchBlock': 'Tìm kiếm nút', }, blocks: { 'start': 'Bắt đầu', @@ -243,6 +253,9 @@ const translation = { 'list-operator': 'Toán tử danh sách', 'document-extractor': 'Trình trích xuất tài liệu', 'agent': 'Người đại lý', + 'loop': 'Vòng', + 'loop-end': 'Thoát vòng lặp', + 'loop-start': 'Bắt đầu vòng lặp', }, blocksAbout: { 'start': 'Định nghĩa các tham số ban đầu để khởi chạy quy trình làm việc', @@ -263,6 +276,8 @@ const translation = { 'document-extractor': 'Được sử dụng để phân tích cú pháp các tài liệu đã tải lên thành nội dung văn bản dễ hiểu bởi LLM.', 'list-operator': 'Được sử dụng để lọc hoặc sắp xếp nội dung mảng.', 'agent': 'Gọi các mô hình ngôn ngữ lớn để trả lời câu hỏi hoặc xử lý ngôn ngữ tự nhiên', + 'loop': 'Thực hiện một vòng lặp logic cho đến khi điều kiện dừng được đáp ứng hoặc số lần lặp tối đa được đạt.', + 'loop-end': 'Tương đương với "dừng lại". Nút này không có các mục cấu hình. Khi thân vòng lặp đến nút này, vòng lặp sẽ kết thúc.', }, operator: { zoomIn: 'Phóng to', @@ -273,20 +288,21 @@ const translation = { }, panel: { userInputField: 'Trường đầu vào của người dùng', - changeBlock: 'Thay đổi khối', helpLink: 'Liên kết trợ giúp', about: 'Giới thiệu', createdBy: 'Tạo bởi ', nextStep: 'Bước tiếp theo', - addNextStep: 'Thêm khối tiếp theo trong quy trình làm việc này', - selectNextStep: 'Chọn khối tiếp theo', runThisStep: 'Chạy bước này', checklist: 'Danh sách kiểm tra', checklistTip: 'Đảm bảo rằng tất cả các vấn đề đã được giải quyết trước khi xuất bản', checklistResolved: 'Tất cả các vấn đề đã được giải quyết', - organizeBlocks: 'Tổ chức các khối', change: 'Thay đổi', optional: '(tùy chọn)', + moveToThisNode: 'Di chuyển đến nút này', + changeBlock: 'Thay đổi Node', + selectNextStep: 'Chọn bước tiếp theo', + organizeBlocks: 'Tổ chức các nút', + addNextStep: 'Thêm bước tiếp theo trong quy trình này', }, nodes: { common: { @@ -404,6 +420,34 @@ const translation = { variable: 'Biến', }, sysQueryInUser: 'sys.query trong tin nhắn của người dùng là bắt buộc', + jsonSchema: { + warningTips: { + saveSchema: 'Vui lòng hoàn thành việc chỉnh sửa trường hiện tại trước khi lưu sơ đồ.', + }, + promptTooltip: 'Chuyển mô tả văn bản thành cấu trúc JSON Schema chuẩn.', + stringValidations: 'Xác thực chuỗi', + instruction: 'Hướng dẫn', + regenerate: 'Tái tạo', + fieldNamePlaceholder: 'Tên trường', + generateJsonSchema: 'Tạo Schema JSON', + back: 'Quay lại', + import: 'Nhập khẩu từ JSON', + generationTip: 'Bạn có thể sử dụng ngôn ngữ tự nhiên để tạo nhanh một JSON Schema.', + doc: 'Tìm hiểu thêm về đầu ra có cấu trúc', + required: 'cần thiết', + generate: 'Tạo ra', + addField: 'Thêm trường', + resultTip: 'Đây là kết quả đã được tạo ra. Nếu bạn không hài lòng, bạn có thể quay lại và chỉnh sửa yêu cầu của mình.', + generating: 'Tạo sơ đồ JSON...', + descriptionPlaceholder: 'Thêm mô tả', + resetDefaults: 'Đặt lại', + promptPlaceholder: 'Mô tả Sơ đồ JSON của bạn...', + showAdvancedOptions: 'Hiển thị tùy chọn nâng cao', + generatedResult: 'Kết quả được tạo ra', + apply: 'Áp dụng', + addChildField: 'Thêm trường trẻ em', + title: 'Sơ đồ đầu ra có cấu trúc', + }, }, knowledgeRetrieval: { queryVariable: 'Biến truy vấn', @@ -416,6 +460,33 @@ const translation = { url: 'URL phân đoạn', metadata: 'Siêu dữ liệu khác', }, + metadata: { + options: { + disabled: { + subTitle: 'Không bật lọc siêu dữ liệu', + title: 'Tắt', + }, + automatic: { + desc: 'Tự động tạo điều kiện lọc siêu dữ liệu dựa trên biến truy vấn', + title: 'Tự động', + subTitle: 'Tự động tạo điều kiện lọc siêu dữ liệu dựa trên truy vấn của người dùng', + }, + manual: { + title: 'Hướng dẫn', + subTitle: 'Thêm thủ công các điều kiện lọc siêu dữ liệu', + }, + }, + panel: { + add: 'Thêm điều kiện', + conditions: 'Điều kiện', + title: 'Điều kiện lọc siêu dữ liệu', + select: 'Chọn biến...', + datePlaceholder: 'Chọn một thời gian...', + placeholder: 'Nhập giá trị', + search: 'Tìm kiếm siêu dữ liệu', + }, + title: 'Lọc siêu dữ liệu', + }, }, http: { inputVars: 'Biến đầu vào', @@ -505,6 +576,8 @@ const translation = { 'not in': 'không có trong', 'in': 'trong', 'all of': 'tất cả', + 'before': 'trước', + 'after': 'sau', }, enterValue: 'Nhập giá trị', addCondition: 'Thêm điều kiện', @@ -520,6 +593,7 @@ const translation = { }, addSubVariable: 'Biến phụ', select: 'Lựa', + condition: 'Điều kiện', }, variableAssigner: { title: 'Gán biến', @@ -562,6 +636,8 @@ const translation = { '+=': '+=', 'set': 'Cài', 'overwrite': 'Ghi đè lên', + 'remove-last': 'Xóa Lần Cuối', + 'remove-first': 'Xóa đầu tiên', }, 'setParameter': 'Đặt tham số...', 'selectAssignedVariable': 'Chọn biến được gán...', @@ -572,7 +648,6 @@ const translation = { 'variables': 'Biến', }, tool: { - toAuthorize: 'Ủy quyền', inputVars: 'Biến đầu vào', outputVars: { text: 'nội dung do công cụ tạo ra', @@ -585,6 +660,7 @@ const translation = { }, json: 'JSON được tạo bởi công cụ', }, + authorize: 'Ủy quyền', }, questionClassifiers: { model: 'mô hình', @@ -766,6 +842,38 @@ const translation = { strategyNotFoundDesc: 'Phiên bản plugin đã cài đặt không cung cấp chiến lược này.', toolbox: 'hộp công cụ', }, + loop: { + ErrorMethod: { + continueOnError: 'Tiếp tục khi có lỗi', + removeAbnormalOutput: 'Xóa đầu ra bất thường', + operationTerminated: 'Chấm dứt', + }, + breakConditionTip: 'Chỉ có thể tham chiếu đến các biến trong vòng lặp có điều kiện kết thúc và các biến hội thoại.', + deleteTitle: 'Xóa nút vòng lặp?', + variableName: 'Tên Biến', + input: 'Nhập', + exitConditionTip: 'Một nút vòng lặp cần ít nhất một điều kiện thoát.', + breakCondition: 'Điều kiện dừng vòng lặp', + totalLoopCount: 'Tổng số lần lặp: {{count}}', + setLoopVariables: 'Đặt biến trong phạm vi vòng lặp', + currentLoopCount: 'Số vòng lặp hiện tại: {{count}}', + deleteDesc: 'Xóa nút vòng sẽ xóa tất cả các nút con', + inputMode: 'Chế độ đầu vào', + currentLoop: 'Vòng lặp hiện tại', + loopMaxCountError: 'Vui lòng nhập số vòng lặp tối đa hợp lệ, trong khoảng từ 1 đến {{maxCount}}', + loop_other: '{{count}} Vòng lặp', + finalLoopVariables: 'Biến Vòng Lặp Cuối', + initialLoopVariables: 'Biến Vòng Lặp Đầu Tiên', + loop_one: '{{count}} Vòng lặp', + error_other: '{{count}} Lỗi', + output: 'Biến đầu ra', + errorResponseMethod: 'Phương pháp phản hồi lỗi', + loopMaxCount: 'Số lần lặp tối đa', + comma: ',', + loopVariables: 'Biến Lặp', + error_one: '{{count}} Lỗi', + loopNode: 'Nút Lặp', + }, }, tracing: { stopBy: 'Dừng bởi {{user}}', @@ -777,6 +885,38 @@ const translation = { conversationVars: 'Biến cuộc trò chuyện', noVarsForOperation: 'Không có biến nào có sẵn để gán với hoạt động đã chọn.', }, + versionHistory: { + filter: { + onlyYours: 'Chỉ của bạn', + empty: 'Không tìm thấy lịch sử phiên bản phù hợp', + onlyShowNamedVersions: 'Chỉ hiển thị các phiên bản có tên', + reset: 'Đặt lại bộ lọc', + all: 'Tất cả', + }, + editField: { + releaseNotesLengthLimit: 'Ghi chú phát hành không được vượt quá {{limit}} ký tự.', + title: 'Tiêu đề', + releaseNotes: 'Ghi chú phát hành', + titleLengthLimit: 'Tiêu đề không được vượt quá {{limit}} ký tự', + }, + action: { + deleteFailure: 'Xóa phiên bản thất bại', + updateFailure: 'Cập nhật phiên bản không thành công', + deleteSuccess: 'Phiên bản đã bị xóa', + updateSuccess: 'Phiên bản đã được cập nhật', + restoreSuccess: 'Phiên bản đã được khôi phục', + restoreFailure: 'Không thể khôi phục phiên bản', + }, + defaultName: 'Phiên bản không được đặt tên', + releaseNotesPlaceholder: 'Mô tả những gì đã thay đổi', + deletionTip: 'Việc xóa là không thể phục hồi, vui lòng xác nhận.', + currentDraft: 'Dự thảo hiện tại', + editVersionInfo: 'Chỉnh sửa thông tin phiên bản', + latest: 'Mới nhất', + nameThisVersion: 'Đặt tên cho phiên bản này', + restorationTip: 'Sau khi phục hồi phiên bản, bản nháp hiện tại sẽ bị ghi đè.', + title: 'Các phiên bản', + }, } export default translation diff --git a/web/i18n/zh-Hans/app-api.ts b/web/i18n/zh-Hans/app-api.ts index f59d9065a6..70b8413244 100644 --- a/web/i18n/zh-Hans/app-api.ts +++ b/web/i18n/zh-Hans/app-api.ts @@ -41,7 +41,7 @@ const translation = { messageFeedbackApi: '消息反馈(点赞)', messageFeedbackApiTip: '代表最终用户对返回消息进行评价,可以点赞与点踩,该数据将在“日志与标注”页中可见,并用于后续的模型微调。', messageIDTip: '消息 ID', - ratingTip: 'like 或 dislike, 空值为撤销', + ratingTip: 'like 或 dislike,空值为撤销', parametersApi: '获取应用配置信息', parametersApiTip: '获取已配置的 Input 参数,包括变量名、字段名称、类型与默认值。通常用于客户端加载后显示这些字段的表单或填入默认值。', }, @@ -58,7 +58,7 @@ const translation = { messageFeedbackApi: '消息反馈(点赞)', messageFeedbackApiTip: '代表最终用户对返回消息进行评价,可以点赞与点踩,该数据将在“日志与标注”页中可见,并用于后续的模型微调。', messageIDTip: '消息 ID', - ratingTip: 'like 或 dislike, 空值为撤销', + ratingTip: 'like 或 dislike,空值为撤销', chatMsgHistoryApi: '获取会话历史消息', chatMsgHistoryApiTip: '滚动加载形式返回历史聊天记录,第一页返回最新 `limit` 条,即:倒序返回。', chatMsgHistoryConversationIdTip: '会话 ID', diff --git a/web/i18n/zh-Hans/app-debug.ts b/web/i18n/zh-Hans/app-debug.ts index c2c659b41f..4f84b396d0 100644 --- a/web/i18n/zh-Hans/app-debug.ts +++ b/web/i18n/zh-Hans/app-debug.ts @@ -123,7 +123,7 @@ const translation = { ok: '好的', contextVarNotEmpty: '上下文查询变量不能为空', deleteContextVarTitle: '删除变量“{{varName}}”?', - deleteContextVarTip: '该变量已被设置为上下文查询变量,删除该变量将影响知识库的正常使用。 如果您仍需要删除它,请在上下文部分中重新选择它。', + deleteContextVarTip: '该变量已被设置为上下文查询变量,删除该变量将影响知识库的正常使用。如果您仍需要删除它,请在上下文部分中重新选择它。', }, }, tools: { @@ -214,7 +214,7 @@ const translation = { modalTitle: '图片上传设置', }, bar: { - empty: '开启功能增强 webapp 用户体验', + empty: '开启功能增强 web app 用户体验', enableText: '功能已开启', manage: '管理', }, @@ -361,6 +361,7 @@ const translation = { 'inputPlaceholder': '请输入', 'labelName': '显示名称', 'required': '必填', + 'hide': '隐藏', 'file': { supportFileTypes: '支持的文件类型', image: { @@ -402,9 +403,9 @@ const translation = { visionSettings: { title: '视觉设置', resolution: '分辨率', - resolutionTooltip: `低分辨率模式将使模型接收图像的低分辨率版本,尺寸为512 x 512,并使用65 Tokens 来表示图像。这样可以使API更快地返回响应,并在不需要高细节的用例中消耗更少的输入。 + resolutionTooltip: `低分辨率模式将使模型接收图像的低分辨率版本,尺寸为 512 x 512,并使用 65 Tokens 来表示图像。这样可以使 API 更快地返回响应,并在不需要高细节的用例中消耗更少的输入。 \n - 高分辨率模式将首先允许模型查看低分辨率图像,然后根据输入图像的大小创建512像素的详细裁剪图像。每个详细裁剪图像使用两倍的预算总共为129 Tokens。`, + 高分辨率模式将首先允许模型查看低分辨率图像,然后根据输入图像的大小创建 512 像素的详细裁剪图像。每个详细裁剪图像使用两倍的预算总共为 129 Tokens。`, high: '高', low: '低', uploadMethod: '上传方式', @@ -437,7 +438,7 @@ const translation = { openingQuestion: '开场问题', noDataPlaceHolder: '在对话型应用中,让 AI 主动说第一段话可以拉近与用户间的距离。', - varTip: '你可以使用变量, 试试输入 {{variable}}', + varTip: '你可以使用变量,试试输入 {{variable}}', tooShort: '对话前提示词至少 20 字才能生成开场白', notIncludeKey: '前缀提示词中不包含变量 {{key}}。请在前缀提示词中添加该变量', }, @@ -466,9 +467,9 @@ const translation = { noResult: '输出结果展示在这', datasetConfig: { settingTitle: '召回设置', - knowledgeTip: '点击 “+” 按钮添加知识库', + knowledgeTip: '点击“+”按钮添加知识库', retrieveOneWay: { - title: 'N选1召回', + title: 'N 选 1 召回', description: '根据用户意图和知识库描述,由 Agent 自主判断选择最匹配的单个知识库来查询相关文本,适合知识库区分度大且知识库数量偏少的应用。', }, retrieveMultiWay: { diff --git a/web/i18n/zh-Hans/app-log.ts b/web/i18n/zh-Hans/app-log.ts index 8c7ad62b4f..4c18157876 100644 --- a/web/i18n/zh-Hans/app-log.ts +++ b/web/i18n/zh-Hans/app-log.ts @@ -29,7 +29,7 @@ const translation = { noOutput: '无输出', element: { title: '这里有人吗', - content: '在这里观测和标注最终用户和 AI 应用程序之间的交互,以不断提高 AI 的准确性。您可以<testLink>试试</testLink> WebApp 或<shareLink>分享</shareLink>出去,然后返回此页面。', + content: '在这里观测和标注最终用户和 AI 应用程序之间的交互,以不断提高 AI 的准确性。您可以<testLink>试试</testLink> web app 或<shareLink>分享</shareLink>出去,然后返回此页面。', }, }, }, diff --git a/web/i18n/zh-Hans/app-overview.ts b/web/i18n/zh-Hans/app-overview.ts index aebee63ed6..a41a86975a 100644 --- a/web/i18n/zh-Hans/app-overview.ts +++ b/web/i18n/zh-Hans/app-overview.ts @@ -1,6 +1,6 @@ const translation = { welcome: { - firstStepTip: '开始之前,', + firstStepTip: '开始之前,', enterKeyTip: '请先在下方输入你的 OpenAI API Key', getKeyTip: '从 OpenAI 获取你的 API Key', placeholder: '你的 OpenAI API Key(例如 sk-xxxx)', @@ -9,11 +9,11 @@ const translation = { cloud: { trial: { title: '您正在使用 {{providerName}} 的试用配额。', - description: '试用配额仅供您测试使用。 在试用配额用完之前,请自行设置模型提供商或购买额外配额。', + description: '试用配额仅供您测试使用。在试用配额用完之前,请自行设置模型提供商或购买额外配额。', }, exhausted: { - title: '您的试用额度已用完,请设置您的APIKey。', - description: '您的试用配额已用完。 请设置您自己的模型提供商或购买额外配额。', + title: '您的试用额度已用完,请设置您的 APIKey。', + description: '您的试用配额已用完。请设置您自己的模型提供商或购买额外配额。', }, }, selfHost: { @@ -30,7 +30,7 @@ const translation = { overview: { title: '概览', appInfo: { - explanation: '开箱即用的 AI WebApp', + explanation: '开箱即用的 AI web app', accessibleAddress: '公开访问 URL', preview: '预览', launch: '启动', @@ -39,19 +39,19 @@ const translation = { preUseReminder: '使用前请先打开开关', settings: { entry: '设置', - title: 'WebApp 设置', - modalTip: '客户端 WebApp 设置。', - webName: 'WebApp 名称', - webDesc: 'WebApp 描述', + title: 'web app 设置', + modalTip: '客户端 web app 设置。', + webName: 'web app 名称', + webDesc: 'web app 描述', webDescTip: '以下文字将展示在客户端中,对应用进行说明和使用上的基本引导', - webDescPlaceholder: '请输入 WebApp 的描述', + webDescPlaceholder: '请输入 web app 的描述', language: '语言', workflow: { title: '工作流', subTitle: '工作流详情', show: '显示', hide: '隐藏', - showDesc: '在 WebApp 中展示或者隐藏工作流详情', + showDesc: '在 web app 中展示或者隐藏工作流详情', }, chatColorTheme: '聊天颜色主题', chatColorThemeDesc: '设置聊天机器人的颜色主题', @@ -60,14 +60,14 @@ const translation = { invalidPrivacyPolicy: '无效的隐私政策链接,请使用以 http 或 https 开头的有效链接', sso: { label: '单点登录认证', - title: 'WebApp SSO 认证', + title: 'web app SSO 认证', description: '启用后,所有用户都需要先进行 SSO 认证才能访问', - tooltip: '联系管理员以开启 WebApp SSO 认证', + tooltip: '联系管理员以开启 web app SSO 认证', }, more: { entry: '展示更多设置', copyright: '版权', - copyrightTip: '在 WebApp 中展示版权信息', + copyrightTip: '在 web app 中展示版权信息', copyrightTooltip: '请升级到专业版或者更高', copyRightPlaceholder: '请输入作者或组织名称', privacyPolicy: '隐私政策', @@ -96,7 +96,7 @@ const translation = { customize: { way: '方法', entry: '定制化', - title: '定制化 AI WebApp', + title: '定制化 AI web app', explanation: '你可以定制化 Web App 前端以符合你的情景与风格需求', way1: { name: 'Fork 客户端代码修改后部署到 Vercel(推荐)', diff --git a/web/i18n/zh-Hans/app.ts b/web/i18n/zh-Hans/app.ts index 7ef8c1b514..4ec1e65059 100644 --- a/web/i18n/zh-Hans/app.ts +++ b/web/i18n/zh-Hans/app.ts @@ -23,7 +23,7 @@ const translation = { importFromDSLFile: '文件', importFromDSLUrl: 'URL', importFromDSLUrlPlaceholder: '输入 DSL 文件的 URL', - deleteAppConfirmTitle: '确认删除应用?', + deleteAppConfirmTitle: '确认删除应用?', deleteAppConfirmContent: '删除应用将无法撤销。用户将不能访问你的应用,所有 Prompt 编排配置和日志均将一并被删除。', appDeleted: '应用已删除', @@ -113,9 +113,9 @@ const translation = { image: '图片', }, answerIcon: { - title: '使用 WebApp 图标替换 🤖', - description: '是否使用 WebApp 图标替换分享的应用界面中的 🤖', - descriptionInExplore: '是否使用 WebApp 图标替换 Explore 界面中的 🤖', + title: '使用 web app 图标替换 🤖', + description: '是否使用 web app 图标替换分享的应用界面中的 🤖', + descriptionInExplore: '是否使用 web app 图标替换 Explore 界面中的 🤖', }, switch: '迁移为工作流编排', switchTipStart: '将为您创建一个使用工作流编排的新应用。新应用将', @@ -169,9 +169,13 @@ const translation = { publicKey: '公钥', secretKey: '密钥', viewDocsLink: '查看 {{key}} 的文档', - removeConfirmTitle: '删除 {{key}} 配置?', + removeConfirmTitle: '删除 {{key}} 配置?', removeConfirmContent: '当前配置正在使用中,删除它将关闭追踪功能。', }, + weave: { + title: '编织', + description: 'Weave 是一个开源平台,用于评估、测试和监控大型语言模型应用程序。', + }, }, appSelector: { label: '应用', @@ -192,6 +196,43 @@ const translation = { modelNotSupported: '模型不支持', modelNotSupportedTip: '当前模型不支持此功能,将自动降级为提示注入。', }, + accessControl: 'Web 应用访问控制', + accessItemsDescription: { + anyone: '任何人都可以访问该 web 应用(无需登录)', + specific: '仅指定的平台内成员可访问该 Web 应用', + organization: '平台内所有成员均可访问该 Web 应用', + external: '仅经认证的外部用户可访问该 Web 应用', + }, + accessControlDialog: { + title: 'Web 应用访问权限', + description: '设置 web 应用访问权限。', + accessLabel: '谁可以访问', + accessItems: { + anyone: '任何人', + specific: '平台内指定成员', + organization: '平台内所有成员', + external: '经认证的外部用户', + }, + groups_one: '{{count}} 个组', + groups_other: '{{count}} 个组', + members_one: '{{count}} 个成员', + members_other: '{{count}} 个成员', + noGroupsOrMembers: '未选择分组或成员', + webAppSSONotEnabledTip: '请联系企业管理员配置 Web 应用外部认证方式。', + operateGroupAndMember: { + searchPlaceholder: '搜索组或成员', + allMembers: '所有成员', + expand: '展开', + noResult: '没有结果', + }, + updateSuccess: '更新成功', + }, + publishApp: { + title: '谁可以访问 web 应用', + notSet: '未设置', + notSetDesc: '当前任何人都无法访问 Web 应用。请设置访问权限。', + }, + noAccessPermission: '没有权限访问 web 应用', } export default translation diff --git a/web/i18n/zh-Hans/billing.ts b/web/i18n/zh-Hans/billing.ts index 8bddbfc2ba..00d9cca2cc 100644 --- a/web/i18n/zh-Hans/billing.ts +++ b/web/i18n/zh-Hans/billing.ts @@ -54,6 +54,10 @@ const translation = { vectorSpaceTooltip: '采用高质量索引模式的文档会消耗知识数据存储资源。当知识数据存储达到限制时,将不会上传新文档。', documentsRequestQuota: '{{count,number}}/分钟 知识库请求频率限制', documentsRequestQuotaTooltip: '指每分钟内,一个空间在知识库中可执行的操作总数,包括数据集的创建、删除、更新,文档的上传、修改、归档,以及知识库查询等,用于评估知识库请求的性能。例如,Sandbox 用户在 1 分钟内连续执行 10 次命中测试,其工作区将在接下来的 1 分钟内无法继续执行以下操作:数据集的创建、删除、更新,文档的上传、修改等操作。', + apiRateLimit: 'API 请求频率限制', + apiRateLimitUnit: '{{count,number}} 次/天', + unlimitedApiRate: 'API 请求频率无限制', + apiRateLimitTooltip: 'API 请求频率限制涵盖所有通过 Dify API 发起的调用,例如文本生成、聊天对话、工作流执行和文档处理等。', documentProcessingPriority: '文档处理', documentProcessingPriorityUpgrade: '以更快的速度、更高的精度处理更多的数据。', priority: { @@ -89,7 +93,7 @@ const translation = { messageRequest: { title: '{{count,number}} 条消息额度', titlePerMonth: '{{count,number}} 条消息额度/月', - tooltip: '为不同方案提供基于OpenAl模型的消息响应额度。', + tooltip: '消息额度旨在帮助您便捷地试用 Dify 中的各类 OpenAI 模型。不同模型会消耗不同额度。额度用尽后,您可以切换为使用自己的 OpenAI API 密钥。', }, annotatedResponse: { title: '{{count,number}} 个标注回复数', @@ -180,6 +184,7 @@ const translation = { fullTipLine2: '标注更多对话。', quotaTitle: '标注的配额', }, + teamMembers: '团队成员', } export default translation diff --git a/web/i18n/zh-Hans/common.ts b/web/i18n/zh-Hans/common.ts index 8ed1e28fd8..00c8a33837 100644 --- a/web/i18n/zh-Hans/common.ts +++ b/web/i18n/zh-Hans/common.ts @@ -1,4 +1,10 @@ const translation = { + theme: { + theme: '主题', + light: '浅色', + dark: '深色', + auto: '自动', + }, api: { success: '成功', actionSuccess: '操作成功', @@ -57,6 +63,7 @@ const translation = { submit: '提交', skip: '跳过', format: '格式化', + more: '更多', }, errorMsg: { fieldRequired: '{{field}} 为必填项', @@ -119,7 +126,7 @@ const translation = { '影响常见与罕见词汇使用。\n值较大时,倾向于生成不常见的词汇和表达方式。\n值越小,更倾向于使用常见和普遍接受的词汇或短语。', max_tokens: '单次回复限制 max_tokens', max_tokensTip: - '用于限制回复的最大长度,以 token 为单位。\n较大的值可能会限制给提示词、聊天记录和知识库留出的空间。\n建议将其设置在三分之二以下。\ngpt-4-1106-preview、gpt-4-vision-preview 最大长度 (输入128k,输出4k)', + '用于限制回复的最大长度,以 token 为单位。\n较大的值可能会限制给提示词、聊天记录和知识库留出的空间。\n建议将其设置在三分之二以下。\ngpt-4-1106-preview、gpt-4-vision-preview 最大长度 (输入 128k,输出 4k)', maxTokenSettingTip: '您设置的最大 tokens 数较大,可能会导致 prompt、用户问题、知识库内容没有 token 空间进行处理,建议设置到 2/3 以下。', setToCurrentModelMaxTokenTip: '最大令牌数更新为当前模型最大的令牌数 {{maxToken}} 的 80%。', stop_sequences: '停止序列 stop_sequences', @@ -140,11 +147,13 @@ const translation = { status: 'beta', explore: '探索', apps: '工作室', + appDetail: '应用详情', + account: '账户', plugins: '插件', exploreMarketplace: '探索 Marketplace', pluginsTips: '集成第三方插件或创建与 ChatGPT 兼容的 AI 插件。', datasets: '知识库', - datasetsTips: '即将到来: 上传自己的长文本数据,或通过 Webhook 集成自己的数据源', + datasetsTips: '即将到来:上传自己的长文本数据,或通过 Webhook 集成自己的数据源', newApp: '创建应用', newDataset: '创建知识库', tools: '工具', @@ -189,7 +198,7 @@ const translation = { account: { account: '账户', myAccount: '我的账户', - studio: 'Dify 工作室', + studio: '工作室', avatar: '头像', name: '用户名', email: '邮箱', @@ -201,8 +210,8 @@ const translation = { newPassword: '新密码', notEqual: '两个密码不相同', confirmPassword: '确认密码', - langGeniusAccount: 'Dify 账号', - langGeniusAccountTip: '您的 Dify 账号和相关的用户数据。', + langGeniusAccount: '账号关联数据', + langGeniusAccountTip: '您的账号相关的用户数据。', editName: '编辑名字', showAppLength: '显示 {{length}} 个应用', delete: '删除账户', @@ -261,7 +270,7 @@ const translation = { deleteMember: '删除成员', you: '(你)', builderTip: '可以构建和编辑自己的应用程序', - setBuilder: 'Set as builder (设置为构建器)', + setBuilder: 'Set as builder(设置为构建器)', builder: '构建器', }, integrations: { @@ -339,7 +348,7 @@ const translation = { }, embeddingModel: { key: 'Embedding 模型', - tip: '设置知识库文档嵌入处理的默认模型,检索和导入知识库均使用该Embedding模型进行向量化处理,切换后将导致已导入的知识库与问题之间的向量维度不一致,从而导致检索失败。为避免检索失败,请勿随意切换该模型。', + tip: '设置知识库文档嵌入处理的默认模型,检索和导入知识库均使用该 Embedding 模型进行向量化处理,切换后将导致已导入的知识库与问题之间的向量维度不一致,从而导致检索失败。为避免检索失败,请勿随意切换该模型。', required: '请选择 Embedding 模型', }, speechToTextModel: { @@ -375,7 +384,7 @@ const translation = { buyQuota: '购买额度', priorityUse: '优先使用', removeKey: '删除 API 密钥', - tip: '已付费额度将优先考虑。 试用额度将在付费额度用完后使用。', + tip: '已付费额度将优先考虑。试用额度将在付费额度用完后使用。', }, item: { deleteDesc: '{{modelName}} 被用作系统推理模型。删除后部分功能将无法使用。请确认。', @@ -406,7 +415,7 @@ const translation = { getFreeTokens: '获得免费 Tokens', priorityUsing: '优先使用', deprecated: '已弃用', - confirmDelete: '确认删除?', + confirmDelete: '确认删除?', quotaTip: '剩余免费额度', loadPresets: '加载预设', parameters: '参数', @@ -475,7 +484,7 @@ const translation = { apiBasedExtension: { title: 'API 扩展提供了一个集中式的 API 管理,在此统一添加 API 配置后,方便在 Dify 上的各类应用中直接使用。', link: '了解如何开发您自己的 API 扩展。', - linkUrl: 'https://docs.dify.ai/v/zh-hans/guides/extension/api-based-extension', + linkUrl: 'https://docs.dify.ai/zh-hans/guides/extension/api-based-extension', add: '新增 API 扩展', selector: { title: 'API 扩展', @@ -560,7 +569,7 @@ const translation = { vectorHash: '向量哈希:', hitScore: '召回得分:', }, - inputPlaceholder: '和机器人聊天', + inputPlaceholder: '和 {{botName}} 聊天', thinking: '深度思考中...', thought: '已深度思考', resend: '重新发送', @@ -650,6 +659,7 @@ const translation = { license: { expiring: '许可证还有 1 天到期', expiring_plural: '许可证还有 {{count}} 天到期', + unlimited: '无限制', }, pagination: { perPage: '每页显示', @@ -657,8 +667,9 @@ const translation = { imageInput: { dropImageHere: '将图片拖放到此处,或', browse: '浏览', - supportedFormats: '支持PNG、JPG、JPEG、WEBP和GIF格式', + supportedFormats: '支持 PNG、JPG、JPEG、WEBP 和 GIF 格式', }, + you: '你', } export default translation diff --git a/web/i18n/zh-Hans/custom.ts b/web/i18n/zh-Hans/custom.ts index 4bec191a60..d388c285ff 100644 --- a/web/i18n/zh-Hans/custom.ts +++ b/web/i18n/zh-Hans/custom.ts @@ -7,7 +7,7 @@ const translation = { suffix: '定制您的品牌。', }, webapp: { - title: '定制 WebApp 品牌', + title: '定制 web app 品牌', removeBrand: '移除 Powered by Dify', changeLogo: '更改 Powered by Brand 图片', changeLogoTip: 'SVG 或 PNG 格式,最小尺寸为 40x40px', diff --git a/web/i18n/zh-Hans/dataset-creation.ts b/web/i18n/zh-Hans/dataset-creation.ts index aec029be2e..6f4c5af7eb 100644 --- a/web/i18n/zh-Hans/dataset-creation.ts +++ b/web/i18n/zh-Hans/dataset-creation.ts @@ -35,7 +35,7 @@ const translation = { }, uploader: { title: '上传文本文件', - button: '拖拽文件至此,或者', + button: '拖拽文件或文件夹至此,或者', browse: '选择文件', tip: '已支持 {{supportTypes}},每个文件不超过 {{size}}MB。', validation: { @@ -79,7 +79,7 @@ const translation = { run: '运行', firecrawlTitle: '使用 🔥Firecrawl 提取网页内容', firecrawlDoc: 'Firecrawl 文档', - firecrawlDocLink: 'https://docs.dify.ai/v/zh-hans/guides/knowledge-base/sync-from-website', + firecrawlDocLink: 'https://docs.dify.ai/zh-hans/guides/knowledge-base/create-knowledge-and-upload-documents/import-content-data/sync-from-website', jinaReaderTitle: '将整个站点内容转换为 Markdown 格式', jinaReaderDoc: '了解更多关于 Jina Reader', jinaReaderDocLink: 'https://jina.ai/reader', @@ -92,14 +92,18 @@ const translation = { excludePaths: '排除路径', includeOnlyPaths: '仅包含路径', extractOnlyMainContent: '仅提取主要内容(无标题、导航、页脚等)', - exceptionErrorTitle: '运行时发生异常:', + exceptionErrorTitle: '运行时发生异常:', unknownError: '未知错误', - totalPageScraped: '抓取页面总数:', + totalPageScraped: '抓取页面总数:', selectAll: '全选', resetAll: '重置全部', scrapTimeInfo: '总共在 {{time}}秒 内抓取了 {{total}} 个页面', preview: '预览', - maxDepthTooltip: '相对于输入 URL 的最大抓取深度。深度0仅抓取输入 URL 本身的页面,深度1抓取输入 URL 及其后的一层目录(一个 /),依此类推。', + maxDepthTooltip: '相对于输入 URL 的最大抓取深度。深度 0 仅抓取输入 URL 本身的页面,深度 1 抓取输入 URL 及其后的一层目录(一个 /),依此类推。', + watercrawlDocLink: '从网站同步', + watercrawlDoc: 'Watercrawl 文档', + configureWatercrawl: '配置水爬行', + watercrawlTitle: '使用 Watercrawl 提取网页内容', }, }, stepTwo: { @@ -117,19 +121,19 @@ const translation = { paragraph: '段落', paragraphTip: '此模式根据分隔符和最大块长度将文本拆分为段落,使用拆分文本作为检索的父块', fullDoc: '全文', - fullDocTip: '整个文档用作父块并直接检索。请注意,出于性能原因,超过10000个标记的文本将被自动截断。', + fullDocTip: '整个文档用作父块并直接检索。请注意,出于性能原因,超过 10000 个标记的文本将被自动截断。', separator: '分段标识符', separatorTip: '分隔符是用于分隔文本的字符。\\n\\n 和 \\n 是常用于分隔段落和行的分隔符。用逗号连接分隔符(\\n\\n,\\n),当段落超过最大块长度时,会按行进行分割。你也可以使用自定义的特殊分隔符(例如 ***)。', separatorPlaceholder: '\\n\\n 用于分段;\\n 用于分行', maxLength: '分段最大长度', maxLengthCheck: '分段最大长度不能大于 {{limit}}', overlap: '分段重叠长度', - overlapTip: '设置分段之间的重叠长度可以保留分段之间的语义关系,提升召回效果。建议设置为最大分段长度的10%-25%', + overlapTip: '设置分段之间的重叠长度可以保留分段之间的语义关系,提升召回效果。建议设置为最大分段长度的 10%-25%', overlapCheck: '分段重叠长度不能大于分段最大长度', rules: '文本预处理规则', removeExtraSpaces: '替换掉连续的空格、换行符和制表符', removeUrlEmails: '删除所有 URL 和电子邮件地址', - removeStopwords: '去除停用词,例如 “a”,“an”,“the” 等', + removeStopwords: '去除停用词,例如“a”,“an”,“the”等', preview: '预览', previewChunk: '预览块', reset: '重置', @@ -137,11 +141,11 @@ const translation = { qualified: '高质量', highQualityTip: '使用高质量模式进行嵌入后,无法切换回经济模式。', recommend: '推荐', - qualifiedTip: '调用嵌入模型处理文档以实现更精确的检索,可以帮助LLM生成高质量的答案。', + qualifiedTip: '调用嵌入模型处理文档以实现更精确的检索,可以帮助 LLM 生成高质量的答案。', warning: '请先完成模型供应商的 API KEY 设置。.', click: '前往设置', economical: '经济', - economicalTip: '每个数据块使用10个关键词进行检索,不会消耗任何tokens,但会以降低检索准确性为代价。', + economicalTip: '每个数据块使用 10 个关键词进行检索,不会消耗任何 tokens,但会以降低检索准确性为代价。', QATitle: '采用 Q&A 分段模式', QATip: '开启后将会消耗额外的 token', QALanguage: '分段使用', diff --git a/web/i18n/zh-Hans/dataset-settings.ts b/web/i18n/zh-Hans/dataset-settings.ts index f23355dbe1..bf0e1b8d68 100644 --- a/web/i18n/zh-Hans/dataset-settings.ts +++ b/web/i18n/zh-Hans/dataset-settings.ts @@ -7,7 +7,7 @@ const translation = { nameError: '名称不能为空', desc: '知识库描述', descInfo: '请写出清楚的文字描述来概述知识库的内容。当从多个知识库中进行选择匹配时,该描述将用作匹配的基础。', - descPlaceholder: '描述该数据集的内容。详细描述可以让 AI 更快地访问数据集的内容。如果为空,LangGenius 将使用默认的命中策略。', + descPlaceholder: '描述该数据集的内容。详细描述可以让 AI 更快地访问数据集的内容。如果为空,Dify 将使用默认的命中策略。', helpText: '学习如何编写一份优秀的数据集描述。', descWrite: '了解如何编写更好的知识库描述。', permissions: '可见权限', diff --git a/web/i18n/zh-Hans/dataset.ts b/web/i18n/zh-Hans/dataset.ts index 064ceb3c03..9b5691ae24 100644 --- a/web/i18n/zh-Hans/dataset.ts +++ b/web/i18n/zh-Hans/dataset.ts @@ -27,7 +27,7 @@ const translation = { createDataset: '创建知识库', noExternalKnowledge: '还没有外部知识库 API,点击此处创建', createExternalAPI: '添加外部知识库 API', - createNewExternalAPI: '创建新的外部知识库API', + createNewExternalAPI: '创建新的外部知识库 API', editExternalAPIFormTitle: '编辑外部知识库 API', editExternalAPITooltipTitle: '个关联知识库', editExternalAPIConfirmWarningContent: { @@ -69,8 +69,8 @@ const translation = { createDatasetIntro: '导入您自己的文本数据或通过 Webhook 实时写入数据以增强 LLM 的上下文。', deleteDatasetConfirmTitle: '要删除知识库吗?', deleteDatasetConfirmContent: - '删除知识库是不可逆的。用户将无法再访问您的知识库,所有的提示配置和日志将被永久删除。', - datasetUsedByApp: '某些应用正在使用该知识库。应用将无法再使用该知识库,所有的提示配置和日志将被永久删除。', + '删除知识库是不可逆的。用户将无法再访问您的知识库,所有的提示配置和日志将被永久删除。', + datasetUsedByApp: '某些应用正在使用该知识库。应用将无法再使用该知识库,所有的提示配置和日志将被永久删除。', datasetDeleted: '知识库已删除', datasetDeleteFailed: '删除知识库失败', selectExternalKnowledgeAPI: { @@ -207,7 +207,7 @@ const translation = { builtIn: '内置', builtInDescription: '内置元数据是系统预定义的元数据,您可以在此处查看和管理内置元数据。', deleteTitle: '确定删除', - deleteContent: '你确定要删除元数据 "{{name}}" 吗?', + deleteContent: '你确定要删除元数据 "{{name}}" 吗?', }, documentMetadata: { metadataToolTip: '元数据是关于文档的数据,用于描述文档的属性。元数据可以帮助您更好地组织和管理文档。', diff --git a/web/i18n/zh-Hans/education.ts b/web/i18n/zh-Hans/education.ts index ca4d2cb3cc..9d27698346 100644 --- a/web/i18n/zh-Hans/education.ts +++ b/web/i18n/zh-Hans/education.ts @@ -2,7 +2,7 @@ const translation = { toVerified: '获取教育版认证', toVerifiedTip: { front: '您现在符合教育版认证的资格。请在下方输入您的教育信息,以完成认证流程,并领取 Dify Professional 版的', - coupon: '50% 独家优惠券', + coupon: '100% 独家优惠券', end: '。', }, currentSigned: '您当前登录的账户是', @@ -38,11 +38,10 @@ const translation = { submitError: '提交表单失败,请稍后重新提交问卷。', learn: '了解如何获取教育版认证', successTitle: '您已成功获得 Dify 教育版认证!', - successContent: '我们已向您的账户发放 Dify Professional 版 50% 折扣优惠券。该优惠券有效期为一年,请在有效期内使用。', + successContent: '我们已向您的账户发放 Dify Professional 版 100% 折扣优惠券。该优惠券有效期为一年,请在有效期内使用。', rejectTitle: '您的 Dify 教育版认证已被拒绝', - rejectContent: '非常遗憾,您无法使用此电子邮件以获得教育版认证资格,也无法领取 Dify Professional 版的 50% 独家优惠券。', + rejectContent: '非常遗憾,您无法使用此电子邮件以获得教育版认证资格,也无法领取 Dify Professional 版的 100% 独家优惠券。', emailLabel: '您当前的邮箱', - } export default translation diff --git a/web/i18n/zh-Hans/explore.ts b/web/i18n/zh-Hans/explore.ts index 896a80ab25..7f16cd32f2 100644 --- a/web/i18n/zh-Hans/explore.ts +++ b/web/i18n/zh-Hans/explore.ts @@ -16,7 +16,7 @@ const translation = { }, }, apps: { - title: '探索 Dify 的应用', + title: '探索应用', description: '使用这些模板应用程序,或根据模板自定义您自己的应用程序。', allCategories: '推荐', }, diff --git a/web/i18n/zh-Hans/login.ts b/web/i18n/zh-Hans/login.ts index 7f64c954b1..a37fc104eb 100644 --- a/web/i18n/zh-Hans/login.ts +++ b/web/i18n/zh-Hans/login.ts @@ -59,12 +59,12 @@ const translation = { emailInValid: '请输入有效的邮箱地址', nameEmpty: '用户名不能为空', passwordEmpty: '密码不能为空', - passwordInvalid: '密码必须包含字母和数字,且长度不小于8位', + passwordInvalid: '密码必须包含字母和数字,且长度不小于 8 位', passwordLengthInValid: '密码必须至少为 8 个字符', registrationNotAllowed: '账户不存在,请联系系统管理员注册账户', }, license: { - tip: '启动 Dify 社区版之前, 请阅读 GitHub 上的', + tip: '启动 Dify 社区版之前,请阅读 GitHub 上的', link: '开源协议', }, join: '加入 ', @@ -105,6 +105,11 @@ const translation = { licenseLostTip: '无法连接 Dify 许可证服务器,请联系管理员以继续使用 Dify。', licenseInactive: '许可证未激活', licenseInactiveTip: '您所在空间的 Dify Enterprise 许可证尚未激活,请联系管理员以继续使用 Dify。', + webapp: { + noLoginMethod: 'Web 应用未配置身份认证方式', + noLoginMethodTip: '请联系系统管理员添加身份认证方式', + disabled: 'Web 应用身份认证已禁用,请联系系统管理员启用。您也可以尝试直接使用应用。', + }, } export default translation diff --git a/web/i18n/zh-Hans/plugin.ts b/web/i18n/zh-Hans/plugin.ts index e088557dfb..bc3e6b6784 100644 --- a/web/i18n/zh-Hans/plugin.ts +++ b/web/i18n/zh-Hans/plugin.ts @@ -67,7 +67,7 @@ const translation = { endpointsDocLink: '查看文档', endpointsEmpty: '点击 \'+\' 按钮添加 API 端点', endpointDisableTip: '停用 API 端点', - endpointDisableContent: '是否要停用 {{name}} 的 API 端点 ?', + endpointDisableContent: '是否要停用 {{name}} 的 API 端点?', endpointDeleteTip: '移除 API 端点', endpointDeleteContent: '是否要移除 {{name}} ?', endpointModalTitle: '设置 API 端点', @@ -77,6 +77,7 @@ const translation = { modelNum: '{{num}} 模型已包含', toolSelector: { title: '添加工具', + toolSetting: '工具设置', toolLabel: '工具', descriptionLabel: '工具描述', descriptionPlaceholder: '简要描述工具目的,例如,获取特定位置的温度。', @@ -123,7 +124,7 @@ const translation = { pluginInfo: '插件信息', delete: '移除插件', deleteContentLeft: '是否要移除 ', - deleteContentRight: ' 插件?', + deleteContentRight: ' 插件?', usedInApps: '此插件正在 {{num}} 个应用中使用。', }, installModal: { @@ -208,7 +209,8 @@ const translation = { installedError: '{{errorLength}} 个插件安装失败', clearAll: '清除所有', }, - submitPlugin: '上传插件', + requestAPlugin: '申请插件', + publishPlugins: '发布插件', difyVersionNotCompatible: '当前 Dify 版本不兼容该插件,其最低版本要求为 {{minimalDifyVersion}}', } diff --git a/web/i18n/zh-Hans/share-app.ts b/web/i18n/zh-Hans/share-app.ts index bfd17ef7a3..ce1270dae8 100644 --- a/web/i18n/zh-Hans/share-app.ts +++ b/web/i18n/zh-Hans/share-app.ts @@ -30,6 +30,8 @@ const translation = { }, tryToSolve: '尝试解决', temporarySystemIssue: '抱歉,临时系统问题。', + expand: '展开', + collapse: '折叠', }, generation: { tabs: { @@ -66,11 +68,14 @@ const translation = { empty: '上传文件的内容不能为空', fileStructNotMatch: '上传文件的内容与结构不匹配', emptyLine: '第 {{rowIndex}} 行的内容为空', - invalidLine: '第 {{rowIndex}} 行: {{varName}}值必填', - moreThanMaxLengthLine: '第 {{rowIndex}} 行: {{varName}}值超过最大长度 {{maxLength}}', + invalidLine: '第 {{rowIndex}} 行:{{varName}}值必填', + moreThanMaxLengthLine: '第 {{rowIndex}} 行:{{varName}}值超过最大长度 {{maxLength}}', atLeastOne: '上传文件的内容不能少于一条', }, }, + login: { + backToHome: '返回首页', + }, } export default translation diff --git a/web/i18n/zh-Hans/time.ts b/web/i18n/zh-Hans/time.ts index 8a223d9dd1..5158a710b5 100644 --- a/web/i18n/zh-Hans/time.ts +++ b/web/i18n/zh-Hans/time.ts @@ -26,6 +26,7 @@ const translation = { now: '此刻', ok: '确定', cancel: '取消', + pickDate: '选择日期', }, title: { pickTime: '选择时间', diff --git a/web/i18n/zh-Hans/tools.ts b/web/i18n/zh-Hans/tools.ts index 98e7b6e271..9a573ad308 100644 --- a/web/i18n/zh-Hans/tools.ts +++ b/web/i18n/zh-Hans/tools.ts @@ -15,7 +15,6 @@ const translation = { }, author: '作者', auth: { - unauthorized: '去授权', authorized: '已授权', setup: '要使用请先授权', setupModalTitle: '设置授权', @@ -30,7 +29,7 @@ const translation = { added: '已添加', manageInTools: '去工具列表管理', emptyTitle: '没有可用的工作流工具', - emptyTip: '去 “工作流 -> 发布为工具” 添加', + emptyTip: '去“工作流 -> 发布为工具”添加', emptyTitleCustom: '没有可用的自定义工具', emptyTipCustom: '创建自定义工具', }, @@ -53,8 +52,8 @@ const translation = { urlError: '请输入有效的 URL', examples: '例子', exampleOptions: { - json: '天气(JSON)', - yaml: '宠物商店(YAML)', + json: '天气 (JSON)', + yaml: '宠物商店 (YAML)', blankTemplate: '空白模版', }, availableTools: { @@ -99,7 +98,7 @@ const translation = { methodParameter: 'LLM 填入', methodParameterTip: 'LLM 在推理过程中填写', label: '标签', - labelPlaceholder: '选择标签(可选)', + labelPlaceholder: '选择标签 (可选)', description: '描述', descriptionPlaceholder: '参数意义的描述', }, @@ -136,7 +135,7 @@ const translation = { infoAndSetting: '信息和设置', }, noCustomTool: { - title: '没有自定义工具!', + title: '没有自定义工具!', content: '在此统一添加和管理你的自定义工具,方便构建应用时使用。', createTool: '创建工具', }, diff --git a/web/i18n/zh-Hans/workflow.ts b/web/i18n/zh-Hans/workflow.ts index ab56c468ce..79b9e674fd 100644 --- a/web/i18n/zh-Hans/workflow.ts +++ b/web/i18n/zh-Hans/workflow.ts @@ -58,7 +58,7 @@ const translation = { processData: '数据处理', input: '输入', output: '输出', - jinjaEditorPlaceholder: '输入 “/” 或 “{” 插入变量', + jinjaEditorPlaceholder: '输入“/”或“{”插入变量', viewOnly: '只读', showRunHistory: '显示运行历史', enableJinja: '开启支持 Jinja 模板', @@ -271,7 +271,7 @@ const translation = { 'variable-aggregator': '将多路分支的变量聚合为一个变量,以实现下游节点统一配置。', 'iteration': '对列表对象执行多次步骤直至输出所有结果。', 'loop': '循环执行一段逻辑直到满足结束条件或者到达循环次数上限。', - 'loop-end': '相当于“break” 此节点没有配置项,当循环体内运行到此节点后循环终止。', + 'loop-end': '相当于“break”此节点没有配置项,当循环体内运行到此节点后循环终止。', 'parameter-extractor': '利用 LLM 从自然语言内推理提取出结构化参数,用于后置的工具调用或 HTTP 请求。', 'document-extractor': '用于将用户上传的文档解析为 LLM 便于理解的文本内容。', 'list-operator': '用于过滤或排序数组内容。', @@ -307,6 +307,7 @@ const translation = { organizeBlocks: '整理节点', change: '更改', optional: '(选填)', + moveToThisNode: '定位至此节点', }, nodes: { common: { @@ -537,7 +538,7 @@ const translation = { writePlaceholder: '输入写入超时(以秒为单位)', }, curl: { - title: '导入cURL', + title: '导入 cURL', placeholder: '粘贴 cURL 字符串', }, }, @@ -638,6 +639,8 @@ const translation = { 'clear': '清空', 'extend': '扩展', 'append': '追加', + 'remove-first': '移除首项', + 'remove-last': '移除末项', '+=': '+=', '-=': '-=', '*=': '*=', @@ -649,7 +652,7 @@ const translation = { 'assignedVarsDescription': '赋值变量必须是可写入的变量,例如会话变量。', }, tool: { - toAuthorize: '授权', + authorize: '授权', inputVars: '输入变量', outputVars: { text: '工具生成的内容', @@ -658,9 +661,9 @@ const translation = { type: '支持类型。现在只支持图片', transfer_method: '传输方式。值为 remote_url 或 local_file', url: '图片链接', - upload_file_id: '上传文件ID', + upload_file_id: '上传文件 ID', }, - json: '工具生成的json', + json: '工具生成的 json', }, }, questionClassifiers: { @@ -737,6 +740,9 @@ const translation = { loop_one: '{{count}} 个循环', loop_other: '{{count}} 个循环', currentLoop: '当前循环', + comma: ',', + error_one: '{{count}}个失败', + error_other: '{{count}}个失败', breakCondition: '循环终止条件', breakConditionTip: '支持引用终止条件循环内的变量和会话变量。', loopMaxCount: '最大循环次数', @@ -782,7 +788,7 @@ const translation = { outputVars: { text: '提取的文本', }, - supportFileTypes: '支持的文件类型: {{types}}。', + supportFileTypes: '支持的文件类型:{{types}}。', learnMore: '了解更多', }, listFilter: { @@ -857,9 +863,9 @@ const translation = { type: '支持类型。现在只支持图片', transfer_method: '传输方式。值为 remote_url 或 local_file', url: '图片链接', - upload_file_id: '上传文件ID', + upload_file_id: '上传文件 ID', }, - json: 'agent 生成的json', + json: 'agent 生成的 json', }, checkList: { strategyNotSelected: '未选择策略', diff --git a/web/i18n/zh-Hant/app-api.ts b/web/i18n/zh-Hant/app-api.ts index 1a2cff7202..db43cd8b77 100644 --- a/web/i18n/zh-Hant/app-api.ts +++ b/web/i18n/zh-Hant/app-api.ts @@ -40,7 +40,7 @@ const translation = { messageFeedbackApi: '訊息反饋(點贊)', messageFeedbackApiTip: '代表終端使用者對返回訊息進行評價,可以點贊與點踩,該資料將在“日誌與標註”頁中可見,並用於後續的模型微調。', messageIDTip: '訊息 ID', - ratingTip: 'like 或 dislike, 空值為撤銷', + ratingTip: 'like 或 dislike,空值為撤銷', parametersApi: '獲取應用配置資訊', parametersApiTip: '獲取已配置的 Input 引數,包括變數名、欄位名稱、型別與預設值。通常用於客戶端載入後顯示這些欄位的表單或填入預設值。', }, @@ -57,7 +57,7 @@ const translation = { messageFeedbackApi: '訊息反饋(點贊)', messageFeedbackApiTip: '代表終端使用者對返回訊息進行評價,可以點贊與點踩,該資料將在“日誌與標註”頁中可見,並用於後續的模型微調。', messageIDTip: '訊息 ID', - ratingTip: 'like 或 dislike, 空值為撤銷', + ratingTip: 'like 或 dislike,空值為撤銷', chatMsgHistoryApi: '獲取會話歷史訊息', chatMsgHistoryApiTip: '滾動載入形式返回歷史聊天記錄,第一頁返回最新 `limit` 條,即:倒序返回。', chatMsgHistoryConversationIdTip: '會話 ID', diff --git a/web/i18n/zh-Hant/app-debug.ts b/web/i18n/zh-Hant/app-debug.ts index ad29195cb5..16374992b5 100644 --- a/web/i18n/zh-Hant/app-debug.ts +++ b/web/i18n/zh-Hant/app-debug.ts @@ -123,7 +123,7 @@ const translation = { ok: '好的', contextVarNotEmpty: '上下文查詢變數不能為空', deleteContextVarTitle: '刪除變數“{{varName}}”?', - deleteContextVarTip: '該變數已被設定為上下文查詢變數,刪除該變數將影響知識庫的正常使用。 如果您仍需要刪除它,請在上下文部分中重新選擇它。', + deleteContextVarTip: '該變數已被設定為上下文查詢變數,刪除該變數將影響知識庫的正常使用。如果您仍需要刪除它,請在上下文部分中重新選擇它。', }, }, tools: { @@ -264,6 +264,7 @@ const translation = { 'inputPlaceholder': '請輸入', 'labelName': '顯示名稱', 'required': '必填', + 'hide': '隱藏', 'errorMsg': { varNameRequired: '變數名稱必填', labelNameRequired: '顯示名稱必填', @@ -279,9 +280,9 @@ const translation = { visionSettings: { title: '視覺設定', resolution: '解析度', - resolutionTooltip: `低解析度模式將使模型接收影象的低解析度版本,尺寸為512 x 512,並使用65 Tokens 來表示影象。這樣可以使API更快地返回響應,並在不需要高細節的用例中消耗更少的輸入。 + resolutionTooltip: `低解析度模式將使模型接收影象的低解析度版本,尺寸為 512 x 512,並使用 65 Tokens 來表示影象。這樣可以使 API 更快地返回響應,並在不需要高細節的用例中消耗更少的輸入。 \n - 高解析度模式將首先允許模型檢視低解析度影象,然後根據輸入影象的大小建立512畫素的詳細裁剪影象。每個詳細裁剪影象使用兩倍的預算總共為129 Tokens。`, + 高解析度模式將首先允許模型檢視低解析度影象,然後根據輸入影象的大小建立 512 畫素的詳細裁剪影象。每個詳細裁剪影象使用兩倍的預算總共為 129 Tokens。`, high: '高', low: '低', uploadMethod: '上傳方式', @@ -314,7 +315,7 @@ const translation = { openingQuestion: '開場問題', noDataPlaceHolder: '在對話型應用中,讓 AI 主動說第一段話可以拉近與使用者間的距離。', - varTip: '你可以使用變數, 試試輸入 {{variable}}', + varTip: '你可以使用變數,試試輸入 {{variable}}', tooShort: '對話前提示詞至少 20 字才能生成開場白', notIncludeKey: '字首提示詞中不包含變數 {{key}}。請在字首提示詞中新增該變數', }, @@ -342,9 +343,9 @@ const translation = { result: '結果', datasetConfig: { settingTitle: '召回設定', - knowledgeTip: '點選 “+” 按鈕新增知識庫', + knowledgeTip: '點選“+”按鈕新增知識庫', retrieveOneWay: { - title: 'N選1召回', + title: 'N 選 1 召回', description: '根據使用者意圖和知識庫描述,由 Agent 自主判斷選擇最匹配的單個知識庫來查詢相關文字,適合知識庫區分度大且知識庫數量偏少的應用。', }, retrieveMultiWay: { diff --git a/web/i18n/zh-Hant/app-log.ts b/web/i18n/zh-Hant/app-log.ts index 3813c8bde0..fcedbbe53d 100644 --- a/web/i18n/zh-Hant/app-log.ts +++ b/web/i18n/zh-Hant/app-log.ts @@ -29,7 +29,7 @@ const translation = { noOutput: '無輸出', element: { title: '這裡有人嗎', - content: '在這裡觀測和標註終端使用者和 AI 應用程式之間的互動,以不斷提高 AI 的準確性。您可以<testLink>試試</testLink> WebApp 或<shareLink>分享</shareLink>出去,然後返回此頁面。', + content: '在這裡觀測和標註終端使用者和 AI 應用程式之間的互動,以不斷提高 AI 的準確性。您可以<testLink>試試</testLink> web app 或<shareLink>分享</shareLink>出去,然後返回此頁面。', }, }, }, diff --git a/web/i18n/zh-Hant/app-overview.ts b/web/i18n/zh-Hant/app-overview.ts index 609c3a5dda..21d9247361 100644 --- a/web/i18n/zh-Hant/app-overview.ts +++ b/web/i18n/zh-Hant/app-overview.ts @@ -1,6 +1,6 @@ const translation = { welcome: { - firstStepTip: '開始之前,', + firstStepTip: '開始之前,', enterKeyTip: '請先在下方輸入你的 OpenAI API Key', getKeyTip: '從 OpenAI 獲取你的 API Key', placeholder: '你的 OpenAI API Key(例如 sk-xxxx)', @@ -9,11 +9,11 @@ const translation = { cloud: { trial: { title: '您正在使用 {{providerName}} 的試用配額。', - description: '試用配額僅供您測試使用。 在試用配額用完之前,請自行設定模型提供商或購買額外配額。', + description: '試用配額僅供您測試使用。在試用配額用完之前,請自行設定模型提供商或購買額外配額。', }, exhausted: { - title: '您的試用額度已用完,請設定您的APIKey。', - description: '您的試用配額已用完。 請設定您自己的模型提供商或購買額外配額。', + title: '您的試用額度已用完,請設定您的 APIKey。', + description: '您的試用配額已用完。請設定您自己的模型提供商或購買額外配額。', }, }, selfHost: { @@ -30,7 +30,7 @@ const translation = { overview: { title: '概覽', appInfo: { - explanation: '開箱即用的 AI WebApp', + explanation: '開箱即用的 AI web app', accessibleAddress: '公開訪問 URL', preview: '預覽', regenerate: '重新生成', @@ -38,18 +38,18 @@ const translation = { preUseReminder: '使用前請先開啟開關', settings: { entry: '設定', - title: 'WebApp 設定', - webName: 'WebApp 名稱', - webDesc: 'WebApp 描述', + title: 'web app 設定', + webName: 'web app 名稱', + webDesc: 'web app 描述', webDescTip: '以下文字將展示在客戶端中,對應用進行說明和使用上的基本引導', - webDescPlaceholder: '請輸入 WebApp 的描述', + webDescPlaceholder: '請輸入 web app 的描述', language: '語言', workflow: { title: '工作流程步驟', show: '展示', hide: '隱藏', subTitle: '工作流詳細資訊', - showDesc: '在 WebApp 中顯示或隱藏工作流詳細資訊', + showDesc: '在 web app 中顯示或隱藏工作流詳細資訊', }, chatColorTheme: '聊天顏色主題', chatColorThemeDesc: '設定聊天機器人的顏色主題', @@ -70,9 +70,9 @@ const translation = { copyrightTooltip: '請升級至專業計劃或以上', }, sso: { - description: '所有使用者在使用 WebApp 之前都需要使用 SSO 登錄', - title: 'WebApp SSO', - tooltip: '聯繫管理員以啟用 WebApp SSO', + description: '所有使用者在使用 web app 之前都需要使用 SSO 登錄', + title: 'web app SSO', + tooltip: '聯繫管理員以啟用 web app SSO', label: 'SSO 身份驗證', }, modalTip: '用戶端 Web 應用程式設置。', @@ -95,7 +95,7 @@ const translation = { customize: { way: '方法', entry: '定製化', - title: '定製化 AI WebApp', + title: '定製化 AI web app', explanation: '你可以定製化 Web App 前端以符合你的情景與風格需求', way1: { name: 'Fork 客戶端程式碼修改後部署到 Vercel(推薦)', @@ -136,11 +136,11 @@ const translation = { }, activeUsers: { title: '活躍使用者數', - explanation: '每日AI互動次數。', + explanation: '每日 AI 互動次數。', }, totalConversations: { title: '總對話數', - explanation: '每日AI對話次數;不包括提示工程/調試。', + explanation: '每日 AI 對話次數;不包括提示工程/調試。', }, tokenUsage: { title: '費用消耗', diff --git a/web/i18n/zh-Hant/app.ts b/web/i18n/zh-Hant/app.ts index 44412bfcca..c43b2ee308 100644 --- a/web/i18n/zh-Hant/app.ts +++ b/web/i18n/zh-Hant/app.ts @@ -15,7 +15,7 @@ const translation = { exportFailed: '匯出 DSL 失敗', importDSL: '匯入 DSL 檔案', createFromConfigFile: '透過 DSL 檔案建立', - deleteAppConfirmTitle: '確認刪除應用?', + deleteAppConfirmTitle: '確認刪除應用?', deleteAppConfirmContent: '刪除應用將無法復原。使用者將無法存取你的應用,所有 Prompt 設定和日誌都將一併被刪除。', appDeleted: '應用已刪除', @@ -30,17 +30,17 @@ const translation = { chatbotDescription: '使用大型語言模型構建聊天助手', completionDescription: '構建一個根據提示生成高品質文字的應用程式,例如生成文章、摘要、翻譯等。', completionWarning: '該類型不久後將不再支援建立', - agentDescription: '構建一個智慧Agent,可以自主選擇工具來完成任務', - workflowDescription: '以工作流的形式編排生成型應用,提供更多的自訂設定。 它適合有經驗的使用者。', + agentDescription: '構建一個智慧 Agent,可以自主選擇工具來完成任務', + workflowDescription: '以工作流的形式編排生成型應用,提供更多的自訂設定。它適合有經驗的使用者。', workflowWarning: '正在進行 Beta 測試', chatbotType: '聊天助手編排方法', basic: '基礎編排', basicTip: '新手適用,可以切換成工作流編排', basicFor: '新手適用', - basicDescription: '基本編排允許使用簡單的設定編排聊天機器人應用程式,而無需修改內建提示。 它適合初學者。', + basicDescription: '基本編排允許使用簡單的設定編排聊天機器人應用程式,而無需修改內建提示。它適合初學者。', advanced: '工作流編排', advancedFor: '進階使用者適用', - advancedDescription: '工作流編排以工作流的形式編排聊天機器人,提供自訂設定,包括編輯內建提示的能力。 它適合有經驗的使用者。', + advancedDescription: '工作流編排以工作流的形式編排聊天機器人,提供自訂設定,包括編輯內建提示的能力。它適合有經驗的使用者。', captionName: '應用名稱 & 圖示', appNamePlaceholder: '給你的應用起個名字', captionDescription: '描述', @@ -120,7 +120,7 @@ const translation = { }, tracing: { title: '追蹤應用程式效能', - description: '配置第三方LLMOps提供商並追蹤應用程式效能。', + description: '配置第三方 LLMOps 提供商並追蹤應用程式效能。', config: '配置', view: '查看', collapse: '收起', @@ -129,7 +129,7 @@ const translation = { disabled: '已禁用', disabledTip: '請先配置提供商', enabled: '服務中', - tracingDescription: '捕獲應用程式執行的完整上下文,包括LLM調用、上下文、提示、HTTP請求等,到第三方追蹤平台。', + tracingDescription: '捕獲應用程式執行的完整上下文,包括 LLM 調用、上下文、提示、HTTP 請求等,到第三方追蹤平台。', configProviderTitle: { configured: '已配置', notConfigured: '配置提供商以啟用追蹤', @@ -137,11 +137,11 @@ const translation = { }, langsmith: { title: 'LangSmith', - description: '一個全方位的開發者平台,用於LLM驅動的應用程式生命週期的每個步驟。', + description: '一個全方位的開發者平台,用於 LLM 驅動的應用程式生命週期的每個步驟。', }, langfuse: { title: 'Langfuse', - description: '追蹤、評估、提示管理和指標,用於調試和改進您的LLM應用程式。', + description: '追蹤、評估、提示管理和指標,用於調試和改進您的 LLM 應用程式。', }, inUse: '使用中', configProvider: { @@ -158,11 +158,15 @@ const translation = { title: '奧皮克', description: 'Opik 是一個用於評估、測試和監控 LLM 應用程式的開源平臺。', }, + weave: { + title: '編織', + description: 'Weave 是一個開源平台,用於評估、測試和監控大型語言模型應用程序。', + }, }, answerIcon: { - descriptionInExplore: '是否使用 WebApp 圖示在 Explore 中取代 🤖', - title: '使用 WebApp 圖示取代 🤖', - description: '是否在共享應用程式中使用 WebApp 圖示進行取代 🤖', + descriptionInExplore: '是否使用 web app 圖示在 Explore 中取代 🤖', + title: '使用 web app 圖示取代 🤖', + description: '是否在共享應用程式中使用 web app 圖示進行取代 🤖', }, importFromDSLUrl: '寄件者 URL', importFromDSL: '從 DSL 導入', @@ -193,6 +197,54 @@ const translation = { params: '應用程式參數', label: '應用程式', }, + structOutput: { + moreFillTip: '顯示最多 10 層的嵌套', + required: '必需的', + LLMResponse: 'LLM 回應', + structured: '結構化的', + configure: '配置', + modelNotSupported: '模型不支持', + modelNotSupportedTip: '當前模型不支持此功能,並自動降級為提示注入。', + structuredTip: '結構化輸出是一項功能,確保模型始終生成符合您提供的 JSON 架構的響應。', + notConfiguredTip: '結構化輸出尚未配置', + }, + accessItemsDescription: { + anyone: '任何人都可以訪問這個網絡應用程式', + specific: '只有特定的群體或成員可以訪問這個網絡應用程序', + organization: '組織中的任何人都可以訪問該網絡應用程序', + external: '只有經過身份驗證的外部用戶才能訪問該網絡應用程序', + }, + accessControlDialog: { + accessItems: { + anyone: '擁有鏈接的人', + specific: '特定群體或成員', + organization: '只有企業內部成員', + external: '經過驗證的外部用戶', + }, + operateGroupAndMember: { + searchPlaceholder: '搜尋群組和成員', + allMembers: '所有成員', + expand: '擴大', + noResult: '沒有結果', + }, + title: '網頁應用程式存取控制', + description: '設定網頁應用程式訪問權限', + accessLabel: '誰可以訪問', + groups_one: '{{count}} 群組', + groups_other: '{{count}} 組', + members_one: '{{count}} 成員', + members_other: '{{count}} 成員', + noGroupsOrMembers: '未選擇任何群組或成員', + webAppSSONotEnabledTip: '請聯絡企業管理員配置網頁應用程式的身份驗證方法。', + updateSuccess: '更新成功', + }, + publishApp: { + title: '誰可以訪問網絡應用程序', + notSet: '未設定', + notSetDesc: '目前沒有人能夠訪問網絡應用程序。請設置權限。', + }, + accessControl: '網頁應用程式存取控制', + noAccessPermission: '沒有權限訪問網絡應用程式', } export default translation diff --git a/web/i18n/zh-Hant/billing.ts b/web/i18n/zh-Hant/billing.ts index 208102c5be..6ede2c6213 100644 --- a/web/i18n/zh-Hant/billing.ts +++ b/web/i18n/zh-Hant/billing.ts @@ -9,7 +9,7 @@ const translation = { buyPermissionDeniedTip: '請聯絡企業管理員訂閱', plansCommon: { title: '選擇適合您的套餐', - yearlyTip: '訂閱年度計劃可免費獲得 2個月!', + yearlyTip: '訂閱年度計劃可免費獲得 2 個月!', mostPopular: '最受歡迎', planRange: { monthly: '按月', @@ -31,7 +31,7 @@ const translation = { buildApps: '構建應用程式數', vectorSpace: '向量空間', vectorSpaceTooltip: '向量空間是 LLMs 理解您的資料所需的長期記憶系統。', - vectorSpaceBillingTooltip: '向量儲存是將知識庫向量化處理後為讓 LLMs 理解資料而使用的長期記憶儲存,1MB 大約能滿足1.2 million character 的向量化後資料儲存(以 OpenAI Embedding 模型估算,不同模型計算方式有差異)。在向量化過程中,實際的壓縮或尺寸減小取決於內容的複雜性和冗餘性。', + vectorSpaceBillingTooltip: '向量儲存是將知識庫向量化處理後為讓 LLMs 理解資料而使用的長期記憶儲存,1MB 大約能滿足 1.2 million character 的向量化後資料儲存(以 OpenAI Embedding 模型估算,不同模型計算方式有差異)。在向量化過程中,實際的壓縮或尺寸減小取決於內容的複雜性和冗餘性。', documentsUploadQuota: '文件上傳配額', documentProcessingPriority: '文件處理優先順序', documentProcessingPriorityTip: '如需更高的文件處理優先順序,請升級您的套餐', @@ -69,6 +69,7 @@ const translation = { messageRequest: { title: '訊息額度', tooltip: '為不同方案提供基於 OpenAI 模型的訊息響應額度。', + titlePerMonth: '{{count,number}} 消息/月', }, annotatedResponse: { title: '標註回覆數', @@ -77,27 +78,94 @@ const translation = { ragAPIRequestTooltip: '指單獨呼叫 Dify 知識庫資料處理能力的 API。', receiptInfo: '只有團隊所有者和團隊管理員才能訂閱和檢視賬單資訊', annotationQuota: '註釋配額', + self: '自我主持', + apiRateLimitUnit: '{{count,number}}/天', + freeTrialTipPrefix: '註冊並獲得一個', + annualBilling: '年度計費', + freeTrialTipSuffix: '無需信用卡', + comparePlanAndFeatures: '比較計劃和功能', + teamMember_one: '{{count,number}} 團隊成員', + priceTip: '每個工作區/', + cloud: '雲服務', + documentsRequestQuota: '{{count,number}}/分鐘 知識請求速率限制', + unlimitedApiRate: '沒有 API 速率限制', + apiRateLimitTooltip: 'API 使用次數限制適用於通過 Dify API 所做的所有請求,包括文本生成、聊天對話、工作流執行和文檔處理。', + getStarted: '開始使用', + freeTrialTip: '200 次 OpenAI 通話的免費試用。', + teamWorkspace: '{{count,number}} 團隊工作空間', + documents: '{{count,number}} 知識文件', + apiRateLimit: 'API 限速', + teamMember_other: '{{count,number}} 團隊成員', + documentsTooltip: '從知識數據來源導入的文件數量配額。', + documentsRequestQuotaTooltip: '指定工作區在知識基礎中每分鐘可以執行的總操作次數,包括數據集的創建、刪除、更新、文檔上傳、修改、歸檔和知識基礎查詢。這個指標用於評估知識基礎請求的性能。例如,如果一個沙箱用戶在一分鐘內連續執行 10 次命中測試,他們的工作區將在接下來的一分鐘內暫時禁止執行以下操作:數據集的創建、刪除、更新以及文檔上傳或修改。', }, plans: { sandbox: { name: 'Sandbox', - description: '200次 GPT 免費試用', + description: '200 次 GPT 免費試用', includesTitle: '包括:', + for: '核心功能免費試用', }, professional: { name: 'Professional', description: '讓個人和小團隊能夠以經濟實惠的方式釋放更多能力。', includesTitle: 'Sandbox 計劃中的一切,加上:', + for: '適合獨立開發者/小型團隊', }, team: { name: 'Team', description: '協作無限制並享受頂級效能。', includesTitle: 'Professional 計劃中的一切,加上:', + for: '適用於中型團隊', }, enterprise: { name: 'Enterprise', description: '獲得大規模關鍵任務系統的完整功能和支援。', includesTitle: 'Team 計劃中的一切,加上:', + features: { + 1: '商業許可證授權', + 6: '先進安全與控制', + 3: '多個工作區及企業管理', + 2: '專屬企業功能', + 4: '單一登入', + 8: '專業技術支援', + 0: '企業級可擴展部署解決方案', + 7: 'Dify 官方的更新和維護', + 5: '由 Dify 合作夥伴協商的服務水平協議', + }, + price: '自訂', + btnText: '聯繫銷售', + priceTip: '年度計費のみ', + for: '適用於大規模團隊', + }, + community: { + features: { + 0: '所有核心功能均在公共存儲庫下釋出', + 2: '遵循 Dify 開源許可證', + 1: '單一工作區域', + }, + includesTitle: '免費功能:', + btnText: '開始使用社區', + name: '社區', + for: '適用於個別用戶、小型團隊或非商業項目', + description: '適用於個別用戶、小型團隊或非商業項目', + price: '免費', + }, + premium: { + features: { + 2: '網頁應用程序標誌及品牌自定義', + 0: '各種雲端服務提供商的自我管理可靠性', + 1: '單一工作區域', + 3: '優先電子郵件及聊天支持', + }, + for: '適用於中型組織和團隊', + comingSoon: '微軟 Azure 與 Google Cloud 支持即將推出', + priceTip: '根據雲端市場', + btnText: '獲取高級版在', + name: '高級', + description: '適用於中型組織和團隊', + price: '可擴展的', + includesTitle: '來自社群的一切,加上:', }, }, vectorSpace: { @@ -107,12 +175,26 @@ const translation = { apps: { fullTipLine1: '升級您的套餐以', fullTipLine2: '構建更多的程式。', + fullTip1: '升級以創建更多應用程序', + fullTip2des: '建議清除不活躍的應用程式以釋放使用空間,或聯繫我們。', + contactUs: '聯繫我們', + fullTip1des: '您已達到此計劃建構應用程序的限制', + fullTip2: '計劃限制已達', }, annotatedResponse: { fullTipLine1: '升級您的套餐以', fullTipLine2: '標註更多對話。', quotaTitle: '標註的配額', }, + usagePage: { + documentsUploadQuota: '文件上傳配額', + vectorSpaceTooltip: '使用高品質索引模式的文件將消耗知識數據存儲資源。當知識數據存儲達到限制後,將不會上傳新文件。', + annotationQuota: '註解配額', + vectorSpace: '知識數據儲存', + buildApps: '建構應用程式', + teamMembers: '團隊成員', + }, + teamMembers: '團隊成員', } export default translation diff --git a/web/i18n/zh-Hant/common.ts b/web/i18n/zh-Hant/common.ts index 18f42a3b93..296fae9e7e 100644 --- a/web/i18n/zh-Hant/common.ts +++ b/web/i18n/zh-Hant/common.ts @@ -54,6 +54,10 @@ const translation = { viewDetails: '查看詳情', in: '在', copied: '複製', + more: '更多', + downloadSuccess: '下載完成。', + downloadFailed: '下載失敗。請稍後再試。', + format: '格式', }, placeholder: { input: '請輸入', @@ -112,7 +116,7 @@ const translation = { '影響常見與罕見詞彙使用。\n值較大時,傾向於生成不常見的詞彙和表達方式。\n值越小,更傾向於使用常見和普遍接受的詞彙或短語。', max_tokens: '單次回覆限制 max_tokens', max_tokensTip: - '用於限制回覆的最大長度,以 token 為單位。\n較大的值可能會限制給提示詞、聊天記錄和知識庫留出的空間。\n建議將其設定在三分之二以下。\ngpt-4-1106-preview、gpt-4-vision-preview 最大長度 (輸入128k,輸出4k)', + '用於限制回覆的最大長度,以 token 為單位。\n較大的值可能會限制給提示詞、聊天記錄和知識庫留出的空間。\n建議將其設定在三分之二以下。\ngpt-4-1106-preview、gpt-4-vision-preview 最大長度 (輸入 128k,輸出 4k)', maxTokenSettingTip: '您設定的最大 tokens 數較大,可能會導致 prompt、使用者問題、知識庫內容沒有 token 空間進行處理,建議設定到 2/3 以下。', setToCurrentModelMaxTokenTip: '最大令牌數更新為當前模型最大的令牌數 {{maxToken}} 的 80%。', stop_sequences: '停止序列 stop_sequences', @@ -133,10 +137,12 @@ const translation = { status: 'beta', explore: '探索', apps: '工作室', + appDetail: '應用詳情', + account: '我的帳戶', plugins: '外掛', pluginsTips: '整合第三方外掛或建立與 ChatGPT 相容的 AI 外掛。', datasets: '知識庫', - datasetsTips: '即將到來: 上傳自己的長文字資料,或透過 Webhook 整合自己的資料來源', + datasetsTips: '即將到來:上傳自己的長文字資料,或透過 Webhook 整合自己的資料來源', newApp: '建立應用', newDataset: '建立知識庫', tools: '工具', @@ -153,6 +159,9 @@ const translation = { community: '社群', about: '關於', logout: '登出', + support: '支持', + github: 'GitHub', + compliance: '合規', }, settings: { accountGroup: '賬戶', @@ -180,8 +189,8 @@ const translation = { newPassword: '新密碼', notEqual: '兩個密碼不相同', confirmPassword: '確認密碼', - langGeniusAccount: 'Dify 賬號', - langGeniusAccountTip: '您的 Dify 賬號和相關的使用者資料。', + langGeniusAccount: '賬號数据', + langGeniusAccountTip: '您的賬號和相關的使用者資料。', editName: '編輯名字', showAppLength: '顯示 {{length}} 個應用', delete: '刪除帳戶', @@ -189,19 +198,22 @@ const translation = { deleteConfirmTip: '請將以下內容從您的註冊電子郵件發送至 ', account: '帳戶', myAccount: '我的帳戶', - studio: 'Dify 工作室', + studio: '工作室', deletePrivacyLinkTip: '有關我們如何處理您的數據的更多資訊,請參閱我們的', deletePrivacyLink: '隱私策略。', deleteSuccessTip: '您的帳戶需要時間才能完成刪除。完成後,我們會給您發送電子郵件。', deleteLabel: '要確認,請在下方輸入您的電子郵件', deletePlaceholder: '請輸入您的電子郵件', verificationLabel: '驗證碼', - verificationPlaceholder: '粘貼6位代碼', + verificationPlaceholder: '粘貼 6 位代碼', permanentlyDeleteButton: '永久刪除帳戶', feedbackTitle: '反饋', feedbackLabel: '告訴我們您刪除帳戶的原因?', feedbackPlaceholder: '自選', sendVerificationButton: '發送驗證碼', + workspaceName: '工作區名稱', + workspaceIcon: '工作區域圖示', + editWorkspaceInfo: '編輯工作區資訊', }, members: { team: '團隊', @@ -238,7 +250,7 @@ const translation = { disInvite: '取消邀請', deleteMember: '刪除成員', you: '(你)', - setBuilder: 'Set as builder (設置為建構器)', + setBuilder: 'Set as builder(設置為建構器)', datasetOperator: '知識管理員', builder: '建築工人', builderTip: '可以構建和編輯自己的應用程式', @@ -319,7 +331,7 @@ const translation = { }, embeddingModel: { key: 'Embedding 模型', - tip: '設定知識庫文件嵌入處理的預設模型,檢索和匯入知識庫均使用該Embedding模型進行向量化處理,切換後將導致已匯入的知識庫與問題之間的向量維度不一致,從而導致檢索失敗。為避免檢索失敗,請勿隨意切換該模型。', + tip: '設定知識庫文件嵌入處理的預設模型,檢索和匯入知識庫均使用該 Embedding 模型進行向量化處理,切換後將導致已匯入的知識庫與問題之間的向量維度不一致,從而導致檢索失敗。為避免檢索失敗,請勿隨意切換該模型。', required: '請選擇 Embedding 模型', }, speechToTextModel: { @@ -355,7 +367,7 @@ const translation = { buyQuota: '購買額度', priorityUse: '優先使用', removeKey: '刪除 API 金鑰', - tip: '已付費額度將優先考慮。 試用額度將在付費額度用完後使用。', + tip: '已付費額度將優先考慮。試用額度將在付費額度用完後使用。', }, item: { deleteDesc: '{{modelName}} 被用作系統推理模型。刪除後部分功能將無法使用。請確認。', @@ -386,7 +398,7 @@ const translation = { getFreeTokens: '獲得免費 Tokens', priorityUsing: '優先使用', deprecated: '已棄用', - confirmDelete: '確認刪除?', + confirmDelete: '確認刪除?', quotaTip: '剩餘免費額度', loadPresets: '載入預設', parameters: '引數', @@ -396,7 +408,7 @@ const translation = { configLoadBalancing: '配置負載均衡', loadBalancingDescription: '使用多組憑證減輕壓力。', addConfig: '添加配置', - upgradeForLoadBalancing: '升級您的計劃以啟用Load Balancing。', + upgradeForLoadBalancing: '升級您的計劃以啟用 Load Balancing。', apiKey: 'API 金鑰', loadBalancing: '負載均衡', providerManagedDescription: '使用模型提供程式提供的單組憑證。', @@ -455,7 +467,7 @@ const translation = { apiBasedExtension: { title: 'API 擴充套件提供了一個集中式的 API 管理,在此統一新增 API 配置後,方便在 Dify 上的各類應用中直接使用。', link: '瞭解如何開發您自己的 API 擴充套件。', - linkUrl: 'https://docs.dify.ai/v/zh-hans/guides/extension/api-based-extension', + linkUrl: 'https://docs.dify.ai/zh-hans/guides/tools/extensions/api-based/api-based-extension', add: '新增 API 擴充套件', selector: { title: 'API 擴充套件', @@ -540,9 +552,10 @@ const translation = { vectorHash: '向量雜湊:', hitScore: '召回得分:', }, - inputPlaceholder: '與 Bot 對話', + inputPlaceholder: '與 {{botName}} 對話', thinking: '思維。。。', thought: '思想', + resend: '重新發送', }, promptEditor: { placeholder: '在這裡寫你的提示詞,輸入\'{\' 插入變數、輸入\'/\' 插入提示內容塊', @@ -618,7 +631,7 @@ const translation = { }, errorMsg: { fieldRequired: '{{field}} 為必填項', - urlError: 'URL應以 http:// 或 https:// 開頭', + urlError: 'URL 應以 http:// 或 https:// 開頭', }, fileUploader: { pasteFileLink: '粘貼文件連結', @@ -631,12 +644,33 @@ const translation = { uploadFromComputerLimit: '上傳文件不能超過 {{size}}', }, license: { - expiring: '將在1天內過期', + expiring: '將在 1 天內過期', expiring_plural: '將在 {{count}} 天后過期', + unlimited: '無限制', }, pagination: { perPage: '每頁項目數', }, + theme: { + light: '光', + auto: '系統', + dark: '黑暗', + theme: '主題', + }, + compliance: { + sandboxUpgradeTooltip: '僅可用於專業或團隊計劃。', + soc2Type1: 'SOC 2 類型 I 報告', + professionalUpgradeTooltip: '僅可用於團隊計劃或更高版本。', + gdpr: 'GDPR DPA', + soc2Type2: 'SOC 2 類型 II 報告', + iso27001: 'ISO 27001:2022 認證', + }, + imageInput: { + supportedFormats: '支援 PNG、JPG、JPEG、WEBP 和 GIF', + browse: '瀏覽', + dropImageHere: '將您的圖片放在這裡,或', + }, + you: '你', } export default translation diff --git a/web/i18n/zh-Hant/custom.ts b/web/i18n/zh-Hant/custom.ts index 85b954c27a..08dd332a8c 100644 --- a/web/i18n/zh-Hant/custom.ts +++ b/web/i18n/zh-Hant/custom.ts @@ -3,9 +3,11 @@ const translation = { upgradeTip: { prefix: '升級您的計劃以', suffix: '定製您的品牌。', + des: '升級您的計劃以自訂您的品牌', + title: '升級您的計劃', }, webapp: { - title: '定製 WebApp 品牌', + title: '定製 web app 品牌', removeBrand: '移除 Powered by Dify', changeLogo: '更改 Powered by Brand 圖片', changeLogoTip: 'SVG 或 PNG 格式,最小尺寸為 40x40px', diff --git a/web/i18n/zh-Hant/dataset-creation.ts b/web/i18n/zh-Hant/dataset-creation.ts index be183ae72f..8955deadc8 100644 --- a/web/i18n/zh-Hant/dataset-creation.ts +++ b/web/i18n/zh-Hant/dataset-creation.ts @@ -22,7 +22,7 @@ const translation = { }, uploader: { title: '上傳文字檔案', - button: '拖拽檔案至此,或者', + button: '拖拽檔案或檔案夾至此,或者', browse: '選擇檔案', tip: '已支援 {{supportTypes}},每個檔案不超過 {{size}}MB。', validation: { @@ -61,7 +61,7 @@ const translation = { fireCrawlNotConfiguredDescription: '使用 API 金鑰配置 Firecrawl 以使用它。', limit: '限制', crawlSubPage: '抓取子頁面', - firecrawlDocLink: 'https://docs.dify.ai/guides/knowledge-base/sync-from-website', + firecrawlDocLink: 'https://docs.dify.ai/en/guides/knowledge-base/create-knowledge-and-upload-documents/import-content-data/sync-from-website', preview: '預覽', configure: '配置', excludePaths: '排除路徑', @@ -83,6 +83,14 @@ const translation = { useSitemap: '使用網站地圖', jinaReaderDocLink: 'https://jina.ai/reader', useSitemapTooltip: '按照網站地圖對網站進行爬網。否則,Jina Reader 將根據頁面相關性反覆運算抓取,從而生成更少但品質更高的頁面。', + watercrawlDoc: 'Watercrawl 文檔', + configureFirecrawl: '配置 Firecrawl', + configureWatercrawl: '配置水爬行', + watercrawlTitle: '使用 Watercrawl 提取網頁內容', + watercrawlDocLink: 'https://docs.dify.ai/zh-TW/guides/knowledge-base/create-knowledge-and-upload-documents/import-content-data/sync-from-website', + waterCrawlNotConfiguredDescription: '配置 Watercrawl 並使用 API 金鑰來使用它。', + configureJinaReader: '配置 Jina Reader', + waterCrawlNotConfigured: 'Watercrawl 尚未配置', }, }, stepTwo: { @@ -95,12 +103,12 @@ const translation = { separatorPlaceholder: '例如換行符(\n)或特定的分隔符(如 "***")', maxLength: '分段最大長度', overlap: '分段重疊長度', - overlapTip: '設定分段之間的重疊長度可以保留分段之間的語義關係,提升召回效果。建議設定為最大分段長度的10%-25%', + overlapTip: '設定分段之間的重疊長度可以保留分段之間的語義關係,提升召回效果。建議設定為最大分段長度的 10%-25%', overlapCheck: '分段重疊長度不能大於分段最大長度', rules: '文字預處理規則', removeExtraSpaces: '替換掉連續的空格、換行符和製表符', removeUrlEmails: '刪除所有 URL 和電子郵件地址', - removeStopwords: '去除停用詞,例如 “a”,“an”,“the” 等', + removeStopwords: '去除停用詞,例如“a”,“an”,“the”等', preview: '預覽', reset: '重置', indexMode: '索引方式', @@ -143,14 +151,14 @@ const translation = { datasetSettingLink: '知識庫設定。', websiteSource: '預處理網站', webpageUnit: '頁面', - separatorTip: '分隔符是用於分隔文字的字元。\\n\\n 和 \\n 是分隔段落和行的常用分隔符。與逗號 (\\n\\n,\\n) 組合使用時,當超過最大區塊長度時,段落將按行分段。您也可以使用自定義的特殊分隔符(例如 ***)。', + separatorTip: '分隔符是用於分隔文字的字元。\\n\\n 和 \\n 是分隔段落和行的常用分隔符。與逗號(\\n\\n,\\n)組合使用時,當超過最大區塊長度時,段落將按行分段。您也可以使用自定義的特殊分隔符(例如 ***)。', maxLengthCheck: '塊最大長度應小於 {{limit}}', general: '常規', previewChunkCount: '{{count}}估計塊數', - useQALanguage: '使用Q&A格式的塊', - qaSwitchHighQualityTipContent: '目前,只有高品質索引方法支援Q&A格式分塊。是否要切換到高品質模式?', + useQALanguage: '使用 Q&A 格式的塊', + qaSwitchHighQualityTipContent: '目前,只有高品質索引方法支援 Q&A 格式分塊。是否要切換到高品質模式?', previewChunk: '預覽資料塊(Preview Chunk)', - fullDocTip: '整個文件用作父塊並直接檢索。請注意,出於性能原因,超過10000個令牌的文本將被自動截斷。', + fullDocTip: '整個文件用作父塊並直接檢索。請注意,出於性能原因,超過 10000 個令牌的文本將被自動截斷。', parentChunkForContext: '父母的背景', previewChunkTip: '點擊左側的 『Preview Chunk』 按鈕載入預覽', parentChild: '父子', @@ -158,7 +166,7 @@ const translation = { parentChildChunkDelimiterTip: '分隔符是用於分隔文字的字元。建議使用 \\n 將父塊拆分為小的子塊。您還可以使用自己定義的特殊分隔符。', parentChildDelimiterTip: '分隔符是用於分隔文字的字元。建議將原始文檔拆分為多個大型父塊。您還可以使用自己定義的特殊分隔符。', generalTip: '常規文本分塊模式,檢索和調用的塊是相同的。', - highQualityTip: '在 High Quality 模式下完成嵌入後,將無法恢復到 Economical (經濟) 模式。', + highQualityTip: '在 High Quality 模式下完成嵌入後,將無法恢復到 Economical(經濟)模式。', childChunkForRetrieval: '用於檢索的 Child-chunk', paragraphTip: '此模式根據分隔符和最大區塊長度將文本拆分為段落,使用拆分文本作為父區塊進行檢索。', paragraph: '段', @@ -200,6 +208,11 @@ const translation = { description: '目前,Dify 的知識庫只有有限的數據源。向 Dify 知識庫貢獻數據源是説明所有使用者增強平台靈活性和強大功能的絕佳方式。我們的貢獻指南使入門變得容易。請點擊下面的連結瞭解更多資訊。', title: '連接到其他數據源?', }, + watercrawl: { + apiKeyPlaceholder: '來自 watercrawl.dev 的 API 金鑰', + configWatercrawl: '配置水爬行', + getApiKeyLinkText: '從 watercrawl.dev 獲取您的 API 金鑰', + }, } export default translation diff --git a/web/i18n/zh-Hant/dataset-documents.ts b/web/i18n/zh-Hant/dataset-documents.ts index 5ad2c8f61f..60b1df80f3 100644 --- a/web/i18n/zh-Hant/dataset-documents.ts +++ b/web/i18n/zh-Hant/dataset-documents.ts @@ -355,7 +355,7 @@ const translation = { addChunk: '添加數據塊', addChildChunk: '添加子塊', addAnother: '添加另一個', - childChunkAdded: '添加了1個子塊', + childChunkAdded: '添加了 1 個子塊', editParentChunk: '編輯父塊(Edit Parent Chunk)', editChildChunk: '編輯子塊', chunkDetail: '數據塊詳細資訊', diff --git a/web/i18n/zh-Hant/dataset-settings.ts b/web/i18n/zh-Hant/dataset-settings.ts index 768937c168..9068706762 100644 --- a/web/i18n/zh-Hant/dataset-settings.ts +++ b/web/i18n/zh-Hant/dataset-settings.ts @@ -25,11 +25,12 @@ const translation = { learnMore: '瞭解更多', description: '關於檢索方法。', longDescription: '關於檢索方法,您可以隨時在知識庫設定中更改此設定。', + method: '檢索方法', }, save: '儲存', permissionsInvitedMembers: '部分團隊成員', me: '(您)', - externalKnowledgeID: '外部知識ID', + externalKnowledgeID: '外部知識 ID', externalKnowledgeAPI: '外部知識 API', retrievalSettings: '檢索設置', indexMethodChangeToEconomyDisabledTip: '不適用於從 HQ 降級到 ECO', diff --git a/web/i18n/zh-Hant/dataset.ts b/web/i18n/zh-Hant/dataset.ts index 6a0d9cab21..0a6b015155 100644 --- a/web/i18n/zh-Hant/dataset.ts +++ b/web/i18n/zh-Hant/dataset.ts @@ -7,7 +7,7 @@ const translation = { createDatasetIntro: '匯入您自己的文字資料或透過 Webhook 實時寫入資料以增強 LLM 的上下文。', deleteDatasetConfirmTitle: '要刪除知識庫嗎?', deleteDatasetConfirmContent: - '刪除知識庫是不可逆的。使用者將無法再訪問您的知識庫,所有的提示配置和日誌將被永久刪除。', + '刪除知識庫是不可逆的。使用者將無法再訪問您的知識庫,所有的提示配置和日誌將被永久刪除。', datasetUsedByApp: '這些知識正被一些應用程序使用。應用程序將無法再使用這些知識,所有提示配置和日誌將被永久刪除。', datasetDeleted: '知識庫已刪除', datasetDeleteFailed: '刪除知識庫失敗', @@ -68,9 +68,9 @@ const translation = { semantic: '語義', keyword: '關鍵詞', }, - nTo1RetrievalLegacy: 'N對1檢索將從9月起正式棄用。建議使用最新的多路徑檢索以獲得更好的結果。', + nTo1RetrievalLegacy: 'N 對 1 檢索將從 9 月起正式棄用。建議使用最新的多路徑檢索以獲得更好的結果。', nTo1RetrievalLegacyLink: '了解更多', - nTo1RetrievalLegacyLinkText: 'N對1檢索將於9月正式棄用。', + nTo1RetrievalLegacyLinkText: 'N 對 1 檢索將於 9 月正式棄用。', defaultRetrievalTip: '默認情況下,使用多路徑檢索。從多個知識庫中檢索知識,然後重新排名。', editExternalAPIConfirmWarningContent: { end: 'external knowledge,並且此修改將應用於所有這些 Knowledge。是否確實要保存此更改?', @@ -98,7 +98,7 @@ const translation = { content: { link: '瞭解如何創建外部 API', front: '要連接到外部知識庫,您需要先創建外部 API。請仔細閱讀並參考', - end: '.然後找到對應的知識ID並在左側的表單中填寫。如果資訊全部正確,點擊連接按鈕后,會自動跳轉到知識庫中的檢索測試。', + end: '.然後找到對應的知識 ID 並在左側的表單中填寫。如果資訊全部正確,點擊連接按鈕后,會自動跳轉到知識庫中的檢索測試。', }, title: '如何連接到外部知識庫', learnMore: '瞭解更多資訊', @@ -138,7 +138,7 @@ const translation = { allExternalTip: '僅使用外部知識時,用戶可以選擇是否啟用 Rerank 模型。如果未啟用,則檢索到的數據塊將根據分數進行排序。當不同知識庫的檢索策略不一致時,就會不準確。', externalKnowledgeIdPlaceholder: '請輸入 Knowledge ID', editExternalAPIFormTitle: '編輯外部知識 API', - externalKnowledgeId: '外部知識ID', + externalKnowledgeId: '外部知識 ID', externalAPIPanelDescription: '外部知識 API 用於連接到 Dify 外部的知識庫,並從該知識庫中檢索知識。', externalAPI: '外部 API', editExternalAPITooltipTitle: '關聯知識', @@ -168,6 +168,54 @@ const translation = { preprocessDocument: '{{num}}預處理文件', allKnowledge: '所有知識', allKnowledgeDescription: '選擇以顯示此工作區中的所有知識。只有 Workspace 擁有者可以管理所有知識。', + metadata: { + createMetadata: { + type: '類型', + title: '新元數據', + back: '返回', + namePlaceholder: '添加元數據名稱', + name: '名字', + }, + checkName: { + empty: '元數據名稱不能為空', + invalid: '元數據名稱只能包含小寫字母、數字和底線,並且必須以小寫字母開頭', + }, + batchEditMetadata: { + applyToAllSelectDocumentTip: '自動為所有選定文檔創建上述所有編輯和新元數據,否則編輯元數據將僅適用於具有該元數據的文檔。', + editDocumentsNum: '編輯 {{num}} 份文件', + multipleValue: '多重價值', + applyToAllSelectDocument: '應用於所有選定的文件', + editMetadata: '編輯元資料', + }, + selectMetadata: { + search: '搜尋元數據', + newAction: '新元數據', + manageAction: '管理', + }, + datasetMetadata: { + rename: '重新命名', + addMetaData: '添加元數據', + builtInDescription: '內建的元數據會自動提取和生成。在使用之前必須啟用,且無法編輯。', + name: '名字', + disabled: '禁用', + deleteTitle: '確認刪除', + values: '{{num}} 值', + deleteContent: '您確定要刪除元數據 "{{name}}" 嗎?', + builtIn: '內建的', + description: '您可以在這裡管理所有的元數據。對所有文件的修改將會同步。', + namePlaceholder: '元數據名稱', + }, + documentMetadata: { + startLabeling: '開始標記', + documentInformation: '文件資訊', + technicalParameters: '技術參數', + metadataToolTip: '元數據作為一個關鍵的過濾器,提高了信息檢索的準確性和相關性。您可以在此處修改和添加此文檔的元數據。', + }, + metadata: '元數據', + chooseTime: '選擇一個時間...', + addMetadata: '添加元數據', + }, + embeddingModelNotAvailable: '嵌入模型無法使用。', } export default translation diff --git a/web/i18n/zh-Hant/education.ts b/web/i18n/zh-Hant/education.ts new file mode 100644 index 0000000000..8efc70f4d6 --- /dev/null +++ b/web/i18n/zh-Hant/education.ts @@ -0,0 +1,47 @@ +const translation = { + toVerifiedTip: { + end: '用於 Dify 專業計劃。', + coupon: '獨家 100% 優惠券', + front: '您現在符合教育驗證狀態的資格。請在下面輸入您的教育資訊以完成此流程並獲得一個', + }, + form: { + schoolName: { + title: '你的學校名稱', + placeholder: '請輸入您學校的正式全名', + }, + schoolRole: { + option: { + teacher: '老師', + student: '學生', + administrator: '校園行政人員', + }, + title: '你的學校角色', + }, + terms: { + desc: { + and: '和', + privacyPolicy: '隱私政策', + termsOfService: '服務條款', + front: '您的資訊及使用教育驗證狀態需遵循我們的', + end: '透過提交:', + }, + option: { + age: '我確認我至少 18 歲', + inSchool: '我確認我已在所提供的機構註冊或受僱。Dify 可能會要求提供註冊/就業的證明。如果我錯誤表述我的資格,我同意支付根據我的教育狀況最初免除的任何費用。', + }, + title: '條款與協議', + }, + }, + rejectContent: '不幸的是,您不符合教育驗證狀態,因此如果您使用此電子郵件地址,將無法獲得 Dify 專業計劃的 100% 獨家優惠券。', + successContent: '我們已經向您的帳戶發放了 Dify 專業計劃的 100% 折扣優惠券。該優惠券有效期為一年,請在有效期內使用它。', + learn: '了解如何進行教育驗證', + rejectTitle: '您的 Dify 教育驗證已被拒絕', + submitError: '表單提交失敗。請稍後再試。', + currentSigned: '當前以以下身份登入', + emailLabel: '您當前的電子郵件', + submit: '提交', + successTitle: '您已獲得 Dify 教育認證', + toVerified: '獲取教育證明', +} + +export default translation diff --git a/web/i18n/zh-Hant/explore.ts b/web/i18n/zh-Hant/explore.ts index 80dfe095dd..7ff61a39bc 100644 --- a/web/i18n/zh-Hant/explore.ts +++ b/web/i18n/zh-Hant/explore.ts @@ -16,7 +16,7 @@ const translation = { }, }, apps: { - title: '探索 Dify 的應用', + title: '探索應用', description: '使用這些模板應用程式,或根據模板自定義您自己的應用程式。', allCategories: '推薦', }, @@ -37,6 +37,7 @@ const translation = { HR: '人力資源', Agent: '代理', Workflow: '工作流', + Entertainment: '娛樂', }, } diff --git a/web/i18n/zh-Hant/login.ts b/web/i18n/zh-Hant/login.ts index 6f2b834118..a928b3b800 100644 --- a/web/i18n/zh-Hant/login.ts +++ b/web/i18n/zh-Hant/login.ts @@ -52,12 +52,12 @@ const translation = { emailInValid: '請輸入有效的郵箱地址', nameEmpty: '使用者名稱不能為空', passwordEmpty: '密碼不能為空', - passwordInvalid: '密碼必須包含字母和數字,且長度不小於8位', - passwordLengthInValid: '密碼必須至少為8個字元', + passwordInvalid: '密碼必須包含字母和數字,且長度不小於 8 位', + passwordLengthInValid: '密碼必須至少為 8 個字元', registrationNotAllowed: '找不到帳戶。請聯繫系統管理員進行註冊。', }, license: { - tip: '啟動 Dify 社群版之前, 請閱讀 GitHub 上的', + tip: '啟動 Dify 社群版之前,請閱讀 GitHub 上的', link: '開源協議', }, join: '加入', @@ -78,7 +78,7 @@ const translation = { emptyCode: '驗證碼是必需的', checkYourEmail: '檢查您的電子郵件', tips: '我們將驗證碼發送到 <strong>{{email}}</strong>', - verificationCodePlaceholder: '輸入6位代碼', + verificationCodePlaceholder: '輸入 6 位代碼', useAnotherMethod: '使用其他方法', validTime: '請記住,該代碼的有效期為 5 分鐘', verificationCode: '驗證碼', @@ -105,6 +105,11 @@ const translation = { licenseInactive: '許可證處於非活動狀態', licenseInactiveTip: '您的工作區的 Dify Enterprise 許可證處於非活動狀態。請聯繫您的管理員以繼續使用 Dify。', licenseLostTip: '無法連接 Dify 許可證伺服器。請聯繫您的管理員以繼續使用 Dify。', + webapp: { + noLoginMethod: '未為網絡應用程序配置身份驗證方法', + noLoginMethodTip: '請聯絡系統管理員以添加身份驗證方法。', + disabled: '網頁應用程序身份驗證已被禁用。請聯繫系統管理員以啟用它。您可以嘗試直接使用應用程序。', + }, } export default translation diff --git a/web/i18n/zh-Hant/plugin.ts b/web/i18n/zh-Hant/plugin.ts index 1f71611265..286f4d40c9 100644 --- a/web/i18n/zh-Hant/plugin.ts +++ b/web/i18n/zh-Hant/plugin.ts @@ -20,23 +20,23 @@ const translation = { github: '從 GitHub 安裝', marketplace: '從 Marketplace 安裝', }, - noInstalled: '未安裝外掛程式', - notFound: '未找到外掛程式', + noInstalled: '未安裝插件', + notFound: '未找到插件', }, source: { marketplace: '市場', local: '本地包檔', - github: 'GitHub的', + github: 'GitHub 的', }, detailPanel: { categoryTip: { marketplace: '從 Marketplace 安裝', - debugging: '調試外掛程式', + debugging: '調試插件', github: '從 Github 安裝', - local: '本地外掛程式', + local: '本地插件', }, operation: { - info: '外掛程式資訊', + info: '插件資訊', detail: '詳', remove: '刪除', install: '安裝', @@ -45,7 +45,7 @@ const translation = { checkUpdate: '檢查更新', }, toolSelector: { - uninstalledContent: '此外掛程式是從local/GitHub儲存庫安裝的。請在安裝後使用。', + uninstalledContent: '此插件是從 local/GitHub 儲存庫安裝的。請在安裝後使用。', descriptionLabel: '工具描述', params: '推理配置', paramsTip2: '當 \'Automatic\' 關閉時,使用預設值。', @@ -56,26 +56,27 @@ const translation = { uninstalledTitle: '未安裝工具', auto: '自動', title: '添加工具', - unsupportedContent: '已安裝的外掛程式版本不提供此作。', + unsupportedContent: '已安裝的插件版本不提供此作。', settings: '用戶設置', - uninstalledLink: '在外掛程式中管理', + uninstalledLink: '在插件中管理', empty: '點擊 『+』 按鈕添加工具。您可以新增多個工具。', unsupportedContent2: '按兩下以切換版本。', paramsTip1: '控制 LLM 推理參數。', + toolSetting: '工具設定', }, actionNum: '{{num}}{{作}}包括', switchVersion: 'Switch 版本', strategyNum: '{{num}}{{策略}}包括', endpoints: '端點', endpointDisableTip: '禁用端點', - endpointsTip: '此外掛程式通過終端節點提供特定功能,您可以為當前工作區配置多個終端節點集。', + endpointsTip: '此插件通過終端節點提供特定功能,您可以為當前工作區配置多個終端節點集。', modelNum: '{{num}}包含的型號', endpointsEmpty: '按兩下「+」按鈕添加端點', endpointDisableContent: '您想禁用 {{name}} 嗎?', configureApp: '配置 App', endpointDeleteContent: '您想刪除 {{name}} 嗎?', configureTool: '配置工具', - endpointModalDesc: '配置后,即可使用外掛程式通過 API 端點提供的功能。', + endpointModalDesc: '配置后,即可使用插件通過 API 端點提供的功能。', disabled: '禁用', serviceOk: '服務正常', endpointDeleteTip: '刪除端點', @@ -88,26 +89,26 @@ const translation = { title: '調試', }, privilege: { - whoCanDebug: '誰可以調試外掛程式?', - whoCanInstall: '誰可以安裝和管理外掛程式?', + whoCanDebug: '誰可以調試插件?', + whoCanInstall: '誰可以安裝和管理插件?', noone: '沒人', - title: '外掛程式首選項', + title: '插件首選項', everyone: '每個人 都', admins: '管理員', }, pluginInfoModal: { repository: '存儲庫', release: '釋放', - title: '外掛程式資訊', + title: '插件資訊', packageName: '包', }, action: { - deleteContentRight: '外掛程式?', + deleteContentRight: '插件?', deleteContentLeft: '是否要刪除', - usedInApps: '此外掛程式正在 {{num}} 個應用程式中使用。', - pluginInfo: '外掛程式資訊', + usedInApps: '此插件正在 {{num}} 個應用程式中使用。', + pluginInfo: '插件資訊', checkForUpdates: '檢查更新', - delete: '刪除外掛程式', + delete: '刪除插件', }, installModal: { labels: { @@ -115,26 +116,26 @@ const translation = { version: '版本', package: '包', }, - readyToInstallPackage: '即將安裝以下外掛程式', + readyToInstallPackage: '即將安裝以下插件', back: '返回', installFailed: '安裝失敗', - readyToInstallPackages: '即將安裝以下 {{num}} 個外掛程式', + readyToInstallPackages: '即將安裝以下 {{num}} 個插件', next: '下一個', - dropPluginToInstall: '將外掛程式包拖放到此處進行安裝', - pluginLoadError: '外掛程式載入錯誤', + dropPluginToInstall: '將插件包拖放到此處進行安裝', + pluginLoadError: '插件載入錯誤', installedSuccessfully: '安裝成功', uploadFailed: '上傳失敗', - installFailedDesc: '外掛程式安裝失敗。', - fromTrustSource: '請確保您只從<trustSource>受信任的來源</trustSource>安裝外掛程式。', - pluginLoadErrorDesc: '此外掛程式將不會被安裝', + installFailedDesc: '插件安裝失敗。', + fromTrustSource: '請確保您只從<trustSource>受信任的來源</trustSource>安裝插件。', + pluginLoadErrorDesc: '此插件將不會被安裝', installComplete: '安裝完成', install: '安裝', - installedSuccessfullyDesc: '外掛程式已成功安裝。', + installedSuccessfullyDesc: '插件已成功安裝。', close: '關閉', uploadingPackage: '正在上傳 {{packageName}}...', - readyToInstall: '即將安裝以下外掛程式', + readyToInstall: '即將安裝以下插件', cancel: '取消', - installPlugin: '安裝外掛程式', + installPlugin: '安裝插件', installing: '安裝。。。', }, installFromGitHub: { @@ -144,18 +145,18 @@ const translation = { uploadFailed: '上傳失敗', selectVersion: '選擇版本', selectVersionPlaceholder: '請選擇一個版本', - updatePlugin: '從 GitHub 更新外掛程式', - installPlugin: '從 GitHub 安裝外掛程式', + updatePlugin: '從 GitHub 更新插件', + installPlugin: '從 GitHub 安裝插件', installedSuccessfully: '安裝成功', selectPackage: '選擇套餐', - installNote: '請確保您只從受信任的來源安裝外掛程式。', + installNote: '請確保您只從受信任的來源安裝插件。', }, upgrade: { close: '關閉', - title: '安裝外掛程式', + title: '安裝插件', upgrade: '安裝', upgrading: '安裝。。。', - description: '即將安裝以下外掛程式', + description: '即將安裝以下插件', usedInApps: '用於 {{num}} 個應用', successfulTitle: '安裝成功', }, @@ -172,7 +173,7 @@ const translation = { mostPopular: '最受歡迎', }, discover: '發現', - noPluginFound: '未找到外掛程式', + noPluginFound: '未找到插件', empower: '為您的 AI 開發提供支援', moreFrom: '來自 Marketplace 的更多內容', and: '和', @@ -180,22 +181,25 @@ const translation = { viewMore: '查看更多', difyMarketplace: 'Dify 市場', pluginsResult: '{{num}} 個結果', + verifiedTip: '由 Dify 驗證', + partnerTip: '由 Dify 合作夥伴驗證', }, task: { installingWithError: '安裝 {{installingLength}} 個插件,{{successLength}} 成功,{{errorLength}} 失敗', - installedError: '{{errorLength}} 個外掛程式安裝失敗', - installError: '{{errorLength}} 個外掛程式安裝失敗,點擊查看', + installedError: '{{errorLength}} 個插件安裝失敗', + installError: '{{errorLength}} 個插件安裝失敗,點擊查看', installingWithSuccess: '安裝 {{installingLength}} 個插件,{{successLength}} 成功。', clearAll: '全部清除', - installing: '安裝 {{installingLength}} 個外掛程式,0 個完成。', + installing: '安裝 {{installingLength}} 個插件,0 個完成。', }, - submitPlugin: '提交外掛程式', + requestAPlugin: '申请插件', + publishPlugins: '發佈插件', findMoreInMarketplace: '在 Marketplace 中查找更多內容', - installPlugin: '安裝外掛程式', + installPlugin: '安裝插件', search: '搜索', allCategories: '全部分類', from: '從', - searchPlugins: '搜索外掛程式', + searchPlugins: '搜索插件', searchTools: '搜尋工具...', installAction: '安裝', installFrom: '安裝起始位置', @@ -204,6 +208,10 @@ const translation = { endpointsEnabled: '{{num}} 組已啟用端點', fromMarketplace: '從 Marketplace', searchCategories: '搜索類別', + metadata: { + title: '插件', + }, + difyVersionNotCompatible: '當前的 Dify 版本與此插件不兼容,請升級至所需的最低版本:{{minimalDifyVersion}}', } export default translation diff --git a/web/i18n/zh-Hant/share-app.ts b/web/i18n/zh-Hant/share-app.ts index ea5f206985..e25aa0c0de 100644 --- a/web/i18n/zh-Hant/share-app.ts +++ b/web/i18n/zh-Hant/share-app.ts @@ -26,6 +26,12 @@ const translation = { }, tryToSolve: '嘗試解決', temporarySystemIssue: '抱歉,臨時系統問題。', + expand: '展開', + collapse: '摺疊', + newChatTip: '已經在一個新聊天中', + chatSettingsTitle: '新的聊天設置', + chatFormTip: '聊天設定在聊天開始後無法修改。', + viewChatSettings: '查看聊天設定', }, generation: { tabs: { @@ -60,10 +66,15 @@ const translation = { empty: '上傳檔案的內容不能為空', fileStructNotMatch: '上傳檔案的內容與結構不匹配', emptyLine: '第 {{rowIndex}} 行的內容為空', - invalidLine: '第 {{rowIndex}} 行: {{varName}}值必填', - moreThanMaxLengthLine: '第 {{rowIndex}} 行: {{varName}}值超過最大長度 {{maxLength}}', + invalidLine: '第 {{rowIndex}} 行:{{varName}}值必填', + moreThanMaxLengthLine: '第 {{rowIndex}} 行:{{varName}}值超過最大長度 {{maxLength}}', atLeastOne: '上傳檔案的內容不能少於一條', }, + execution: '執行', + executions: '{{num}} 執行', + }, + login: { + backToHome: '返回首頁', }, } diff --git a/web/i18n/zh-Hant/time.ts b/web/i18n/zh-Hant/time.ts index e2410dd34b..3be2511fcc 100644 --- a/web/i18n/zh-Hant/time.ts +++ b/web/i18n/zh-Hant/time.ts @@ -1,3 +1,37 @@ -const translation = {} +const translation = { + daysInWeek: { + Tue: '星期二', + Wed: '星期三', + Fri: '自由', + Mon: '懷念', + Sun: '太陽', + Sat: '星期六', + Thu: '星期四', + }, + months: { + January: '一月', + June: '六月', + March: '三月', + February: '二月', + August: '八月', + April: '四月', + May: '五月', + July: '七月', + November: '十一月', + December: '十二月', + September: '九月', + October: '十月', + }, + operation: { + cancel: '取消', + now: '現在', + ok: '好', + pickDate: '選擇日期', + }, + title: { + pickTime: '選擇時間', + }, + defaultPlaceholder: '選擇一個時間...', +} export default translation diff --git a/web/i18n/zh-Hant/tools.ts b/web/i18n/zh-Hant/tools.ts index c4ffb4f83d..6e5a95f2a5 100644 --- a/web/i18n/zh-Hant/tools.ts +++ b/web/i18n/zh-Hant/tools.ts @@ -14,7 +14,6 @@ const translation = { }, author: '作者', auth: { - unauthorized: '去授權', authorized: '已授權', setup: '要使用請先授權', setupModalTitle: '設定授權', @@ -36,8 +35,8 @@ const translation = { urlError: '請輸入有效的 URL', examples: '例子', exampleOptions: { - json: '天氣(JSON)', - yaml: '寵物商店(YAML)', + json: '天氣 (JSON)', + yaml: '寵物商店 (YAML)', blankTemplate: '空白模版', }, availableTools: { @@ -92,7 +91,7 @@ const translation = { }, description: '描述', nameForToolCall: '工具調用名稱', - confirmTitle: '確認儲存 ?', + confirmTitle: '確認儲存?', descriptionPlaceholder: '工具用途的簡要描述,例如,獲取特定位置的溫度。', nameForToolCallTip: '僅支援數位、字母和下劃線。', confirmTip: '使用此工具的應用程式將受到影響', @@ -124,7 +123,7 @@ const translation = { file: '檔', }, noCustomTool: { - title: '沒有自定義工具!', + title: '沒有自定義工具!', content: '在此統一新增和管理你的自定義工具,方便構建應用時使用。', createTool: '建立工具', }, diff --git a/web/i18n/zh-Hant/workflow.ts b/web/i18n/zh-Hant/workflow.ts index 1e4fd2ef21..5a1b49a0f1 100644 --- a/web/i18n/zh-Hant/workflow.ts +++ b/web/i18n/zh-Hant/workflow.ts @@ -51,7 +51,7 @@ const translation = { processData: '數據處理', input: '輸入', output: '輸出', - jinjaEditorPlaceholder: '輸入 “/” 或 “{” 插入變量', + jinjaEditorPlaceholder: '輸入“/”或“{”插入變量', viewOnly: '只讀', showRunHistory: '顯示運行歷史', enableJinja: '開啟支持 Jinja 模板', @@ -73,7 +73,7 @@ const translation = { backupCurrentDraft: 'Backup Current Draft', overwriteAndImport: '覆蓋和導入', importSuccess: '導入成功', - chooseDSL: '選擇 DSL(yml) 檔', + chooseDSL: '選擇 DSL(yml)檔', syncingData: '同步數據,只需幾秒鐘。', importDSLTip: '當前草稿將被覆蓋。在導入之前將工作流匯出為備份。', importFailure: '匯入失敗', @@ -106,10 +106,19 @@ const translation = { addFailureBranch: '添加 Fail Branch', loadMore: '載入更多工作流', noHistory: '無歷史記錄', + publishUpdate: '發布更新', + referenceVar: '參考變量', + exportSVG: '匯出為 SVG', + exportPNG: '匯出為 PNG', + noExist: '沒有這個變數', + versionHistory: '版本歷史', + exitVersions: '退出版本', + exportImage: '匯出圖像', + exportJPEG: '匯出為 JPEG', }, env: { envPanelTitle: '環境變數', - envDescription: '環境變數可用於存儲私人信息和憑證。它們是唯讀的,並且可以在導出時與DSL文件分開。', + envDescription: '環境變數可用於存儲私人信息和憑證。它們是唯讀的,並且可以在導出時與 DSL 文件分開。', envPanelButton: '添加變數', modal: { title: '添加環境變數', @@ -119,13 +128,13 @@ const translation = { namePlaceholder: '環境名稱', value: '值', valuePlaceholder: '環境值', - secretTip: '用於定義敏感信息或數據,DSL設置配置為防止洩露。', + secretTip: '用於定義敏感信息或數據,DSL 設置配置為防止洩露。', }, export: { title: '導出機密環境變數?', checkbox: '導出機密值', - ignore: '導出DSL', - export: '導出帶有機密值的DSL', + ignore: '導出 DSL', + export: '導出帶有機密值的 DSL', }, }, chatVariable: { @@ -195,7 +204,7 @@ const translation = { }, invalidVariable: '無效的變量', rerankModelRequired: '在開啟 Rerank 模型之前,請在設置中確認模型配置成功。', - toolParameterRequired: '{{field}}: 参數 [{{param}}] 為必填項', + toolParameterRequired: '{{field}}:参數 [{{param}}] 為必填項', noValidTool: '{{field}} 未選擇有效工具', }, singleRun: { @@ -205,6 +214,7 @@ const translation = { testRunIteration: '測試運行迭代', back: '返回', iteration: '迭代', + loop: '循環', }, tabs: { 'searchBlock': '搜索節點', @@ -221,7 +231,7 @@ const translation = { 'noResult': '未找到匹配項', 'searchTool': '搜索工具', 'agent': '代理策略', - 'plugin': '外掛程式', + 'plugin': '插件', }, blocks: { 'start': '開始', @@ -245,6 +255,7 @@ const translation = { 'agent': '代理', 'loop-start': '循環開始', 'loop': '循環', + 'loop-end': '退出循環', }, blocksAbout: { 'start': '定義一個 workflow 流程啟動的參數', @@ -265,6 +276,8 @@ const translation = { 'document-extractor': '用於將上傳的文件解析為 LLM 易於理解的文字內容。', 'list-operator': '用於篩選或排序陣列內容。', 'agent': '調用大型語言模型來回答問題或處理自然語言', + 'loop-end': '等同於「中斷」。這個節點沒有配置項目。當循環體達到這個節點時,循環終止。', + 'loop': '執行邏輯迴圈,直到滿足終止條件或達到最大迴圈次數。', }, operator: { zoomIn: '放大', @@ -289,6 +302,7 @@ const translation = { organizeBlocks: '整理節點', change: '更改', optional: '(選擇性)', + moveToThisNode: '定位至此節點', }, nodes: { common: { @@ -322,9 +336,9 @@ const translation = { failBranch: { title: '失敗分支', desc: '當發生錯誤時,它會執行 exception 分支', - customize: '轉到畫布以自定義fail分支邏輯。', - inLog: 'Node 異常,將自動執行fail分支。節點輸出將返回錯誤類型和錯誤消息,並將其傳遞給下游。', - customizeTip: '啟動fail分支後,節點引發的異常不會終止進程。相反,它將自動執行預定義的fail分支,允許您靈活地提供錯誤消息、報告、修復或跳過操作。', + customize: '轉到畫布以自定義 fail 分支邏輯。', + inLog: 'Node 異常,將自動執行 fail 分支。節點輸出將返回錯誤類型和錯誤消息,並將其傳遞給下游。', + customizeTip: '啟動 fail 分支後,節點引發的異常不會終止進程。相反,它將自動執行預定義的 fail 分支,允許您靈活地提供錯誤消息、報告、修復或跳過操作。', }, partialSucceeded: { tip: '進程中有 {{num}} 個節點運行異常,請前往 tracing 查看日誌。', @@ -406,6 +420,34 @@ const translation = { variable: '變量', }, sysQueryInUser: 'user message 中必須包含 sys.query', + jsonSchema: { + warningTips: { + saveSchema: '請在保存結構之前完成當前欄位的編輯', + }, + resetDefaults: '重置', + instruction: '指示', + apply: '申請', + promptPlaceholder: '描述你的 JSON 架構...', + addField: '新增字段', + generate: '生成', + descriptionPlaceholder: '添加描述', + fieldNamePlaceholder: '欄位名稱', + showAdvancedOptions: '顯示進階選項', + import: '從 JSON 匯入', + generatedResult: '生成的結果', + generateJsonSchema: '生成 JSON 架構', + promptTooltip: '將文本描述轉換成標準化的 JSON Schema 結構。', + doc: '了解更多有關結構化輸出的資訊', + addChildField: '新增子欄位', + title: '結構化輸出模式', + regenerate: '重新生成', + stringValidations: '字串驗證', + generationTip: '您可以使用自然語言快速創建 JSON Schema。', + generating: '生成 JSON 架構...', + back: '返回', + required: '必需的', + resultTip: '這是生成的結果。如果您不滿意,可以回去修改您的提示。', + }, }, knowledgeRetrieval: { queryVariable: '查詢變量', @@ -418,6 +460,33 @@ const translation = { url: '分段鏈接', metadata: '其他元數據', }, + metadata: { + options: { + disabled: { + subTitle: '不啟用元數據過濾', + title: '禁用', + }, + automatic: { + title: '自動的', + subTitle: '根據用戶查詢自動生成元數據過濾條件', + desc: '根據查詢變數自動生成元數據過濾條件', + }, + manual: { + title: '手動', + subTitle: '手動添加元數據過濾條件', + }, + }, + panel: { + add: '添加條件', + datePlaceholder: '選擇一個時間...', + search: '搜尋元數據', + conditions: '條件', + title: '元數據過濾條件', + select: '選擇變數...', + placeholder: '輸入數值', + }, + title: '元數據過濾', + }, }, http: { inputVars: '輸入變量', @@ -507,6 +576,8 @@ const translation = { 'in': '在', 'not in': '不在', 'not exists': '不存在', + 'after': '之後', + 'before': '之前', }, enterValue: '輸入值', addCondition: '添加條件', @@ -522,6 +593,7 @@ const translation = { }, select: '選擇', addSubVariable: '子變數', + condition: '條件', }, variableAssigner: { title: '變量賦值', @@ -564,17 +636,19 @@ const translation = { '-=': '-=', 'append': '附加', 'clear': '清除', + 'remove-first': '移除首項', + 'remove-last': '移除末項', }, 'noAssignedVars': '沒有可用的已分配變數', 'variables': '變數', 'selectAssignedVariable': '選擇配置的變數...', 'setParameter': '設定參數...', - 'noVarTip': '點擊 「+」 按鈕添加變數', + 'noVarTip': '點擊「+」按鈕添加變數', 'assignedVarsDescription': '分配的變數必須是可寫變數,例如對話變數。', 'varNotSet': '未設置變數', }, tool: { - toAuthorize: '授權', + authorize: '授權', inputVars: '輸入變量', outputVars: { text: '工具生成的內容', @@ -583,9 +657,9 @@ const translation = { type: '支持類型。現在只支持圖片', transfer_method: '傳輸方式。值為 remote_url 或 local_file', url: '圖片鏈接', - upload_file_id: '上傳文件ID', + upload_file_id: '上傳文件 ID', }, - json: '工具生成的JSON', + json: '工具生成的 JSON', }, }, questionClassifiers: { @@ -688,13 +762,13 @@ const translation = { result: '篩選結果', }, desc: '描述', - asc: 'ASC的', + asc: 'ASC 的', orderBy: '排序依據', inputVar: '輸入變數', filterConditionComparisonValue: 'Filter Condition 值', filterCondition: '篩選條件', limit: '前 N 名', - selectVariableKeyPlaceholder: 'Select sub variable key (選擇子變數鍵)', + selectVariableKeyPlaceholder: 'Select sub variable key(選擇子變數鍵)', filterConditionComparisonOperator: 'Filter Condition Comparison 運算符', filterConditionKey: '篩選條件鍵', extractsCondition: '提取第 N 項', @@ -715,13 +789,13 @@ const translation = { }, modelNotInMarketplace: { title: '未安裝模型', - manageInPlugins: '在外掛程式中管理', + manageInPlugins: '在插件中管理', desc: '此模型是從 Local 或 GitHub 儲存庫安裝的。請在安裝後使用。', }, modelNotSupport: { title: '不支援的型號', - desc: '已安裝的外掛程式版本不提供此模型。', - descForVersionSwitch: '已安裝的外掛程式版本不提供此模型。按兩下以切換版本。', + desc: '已安裝的插件版本不提供此模型。', + descForVersionSwitch: '已安裝的插件版本不提供此模型。按兩下以切換版本。', }, modelSelectorTooltips: { deprecated: '此模型已棄用', @@ -732,7 +806,7 @@ const translation = { transfer_method: '轉移方法。值為 remote_url 或 local_file', title: '代理生成的檔', url: '圖片網址', - upload_file_id: '上傳檔ID', + upload_file_id: '上傳檔 ID', }, text: '代理生成的內容', json: '代理生成的 JSON', @@ -741,18 +815,18 @@ const translation = { strategyNotSelected: '未選擇策略', }, installPlugin: { - title: '安裝外掛程式', + title: '安裝插件', changelog: '更新日誌', cancel: '取消', - desc: '即將安裝以下外掛程式', + desc: '即將安裝以下插件', install: '安裝', }, - pluginNotFoundDesc: '此外掛程式是從 GitHub 安裝的。請前往外掛程式 重新安裝', + pluginNotFoundDesc: '此插件是從 GitHub 安裝的。請前往插件 重新安裝', modelNotSelected: '未選擇模型', tools: '工具', - strategyNotFoundDesc: '已安裝的外掛程式版本不提供此策略。', - pluginNotInstalledDesc: '此外掛程式是從 GitHub 安裝的。請前往外掛程式 重新安裝', - strategyNotFoundDescAndSwitchVersion: '已安裝的外掛程式版本不提供此策略。按兩下以切換版本。', + strategyNotFoundDesc: '已安裝的插件版本不提供此策略。', + pluginNotInstalledDesc: '此插件是從 GitHub 安裝的。請前往插件 重新安裝', + strategyNotFoundDescAndSwitchVersion: '已安裝的插件版本不提供此策略。按兩下以切換版本。', strategyNotInstallTooltip: '{{strategy}} 未安裝', toolNotAuthorizedTooltip: '{{工具}}未授權', unsupportedStrategy: '不支援的策略', @@ -764,10 +838,42 @@ const translation = { toolbox: '工具箱', configureModel: '配置模型', learnMore: '瞭解更多資訊', - linkToPlugin: '連結到外掛程式', - pluginNotInstalled: '此外掛程式未安裝', + linkToPlugin: '連結到插件', + pluginNotInstalled: '此插件未安裝', notAuthorized: '未授權', }, + loop: { + ErrorMethod: { + operationTerminated: '終止', + continueOnError: '繼續出錯', + removeAbnormalOutput: '移除異常輸出', + }, + loop_other: '{{count}} 循環', + variableName: '變數名稱', + error_one: '{{count}} 錯誤', + loopMaxCount: '最大迴圈次數', + input: '輸入', + loopVariables: '循環變量', + output: '輸出變量', + comma: ',', + errorResponseMethod: '錯誤回應方法', + breakCondition: '迴圈終止條件', + loopMaxCountError: '請輸入一個有效的最大迴圈次數,範圍為 1 到 {{maxCount}}', + loop_one: '{{count}} 次循環', + exitConditionTip: '循環節點至少需要一個退出條件', + breakConditionTip: '只有在具有終止條件的循環內和對話變數中,才能引用變數。', + totalLoopCount: '總迴圈次數:{{count}}', + error_other: '{{count}} 錯誤', + currentLoop: '電流迴路', + finalLoopVariables: '最後迴圈變數', + currentLoopCount: '當前循環次數:{{count}}', + inputMode: '輸入模式', + loopNode: '循環節點', + initialLoopVariables: '初始迴圈變數', + deleteDesc: '刪除循環節點將移除所有子節點', + setLoopVariables: '在迴圈範圍內設置變數', + deleteTitle: '刪除循環節點嗎?', + }, }, tracing: { stopBy: '由{{user}}終止', @@ -779,6 +885,38 @@ const translation = { assignedVarsDescription: '分配的變數必須是可寫變數,例如', conversationVars: '對話變數', }, + versionHistory: { + filter: { + onlyShowNamedVersions: '僅顯示命名版本', + onlyYours: '只有妳的', + empty: '未找到匹配的版本歷史', + all: '所有', + reset: '重置過濾器', + }, + editField: { + releaseNotes: '發佈說明', + titleLengthLimit: '標題不能超過 {{limit}} 個字符', + releaseNotesLengthLimit: '發佈說明不能超過 {{limit}} 個字符', + title: '標題', + }, + action: { + updateFailure: '更新版本失敗', + restoreFailure: '無法恢復版本', + restoreSuccess: '恢復版本', + updateSuccess: '版本已更新', + deleteSuccess: '版本已刪除', + deleteFailure: '無法刪除版本', + }, + nameThisVersion: '給這個版本命名', + latest: '最新', + currentDraft: '當前草稿', + title: '版本', + editVersionInfo: '編輯版本信息', + restorationTip: '版本恢復後,當前草稿將被覆蓋。', + deletionTip: '刪除是不可逆的,請確認。', + releaseNotesPlaceholder: '描述發生了什麼變化', + defaultName: '未命名版本', + }, } export default translation diff --git a/web/middleware.ts b/web/middleware.ts index e3c82fc6e5..3fee535ea4 100644 --- a/web/middleware.ts +++ b/web/middleware.ts @@ -6,7 +6,7 @@ const NECESSARY_DOMAIN = '*.sentry.io http://localhost:* http://127.0.0.1:* http const wrapResponseWithXFrameOptions = (response: NextResponse, pathname: string) => { // prevent clickjacking: https://owasp.org/www-community/attacks/Clickjacking // Chatbot page should be allowed to be embedded in iframe. It's a feature - if (process.env.NEXT_PUBLIC_ALLOW_EMBED !== 'true' && !pathname.startsWith('/chat')) + if (process.env.NEXT_PUBLIC_ALLOW_EMBED !== 'true' && !pathname.startsWith('/chat') && !pathname.startsWith('/workflow') && !pathname.startsWith('/completion') && !pathname.startsWith('/webapp-signin')) response.headers.set('X-Frame-Options', 'DENY') return response @@ -37,7 +37,7 @@ export function middleware(request: NextRequest) { style-src 'self' 'unsafe-inline' ${scheme_source} ${whiteList}; worker-src 'self' ${scheme_source} ${csp} ${whiteList}; media-src 'self' ${scheme_source} ${csp} ${whiteList}; - img-src 'self' ${scheme_source} ${csp} ${whiteList}; + img-src * data: blob:; font-src 'self'; object-src 'none'; base-uri 'self'; diff --git a/web/models/access-control.ts b/web/models/access-control.ts new file mode 100644 index 0000000000..911662b5c4 --- /dev/null +++ b/web/models/access-control.ts @@ -0,0 +1,30 @@ +export enum SubjectType { + GROUP = 'group', + ACCOUNT = 'account', +} + +export enum AccessMode { + PUBLIC = 'public', + SPECIFIC_GROUPS_MEMBERS = 'private', + ORGANIZATION = 'private_all', + EXTERNAL_MEMBERS = 'sso_verified', +} + +export type AccessControlGroup = { + id: 'string' + name: 'string' + groupSize: 5 +} + +export type AccessControlAccount = { + id: 'string' + name: 'string' + email: 'string' + avatar: 'string' + avatarUrl: 'string' +} + +export type SubjectGroup = { subjectId: string; subjectType: SubjectType; groupData: AccessControlGroup } +export type SubjectAccount = { subjectId: string; subjectType: SubjectType; accountData: AccessControlAccount } + +export type Subject = SubjectGroup | SubjectAccount diff --git a/web/models/app.ts b/web/models/app.ts index 545819359f..b10ced703a 100644 --- a/web/models/app.ts +++ b/web/models/app.ts @@ -1,7 +1,64 @@ -import type { LangFuseConfig, LangSmithConfig, OpikConfig, TracingProvider } from '@/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/type' -import type { App, AppMode, AppSSO, AppTemplate, SiteConfig } from '@/types/app' +import type { LangFuseConfig, LangSmithConfig, OpikConfig, TracingProvider, WeaveConfig } from '@/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/type' +import type { App, AppTemplate, SiteConfig } from '@/types/app' import type { Dependency } from '@/app/components/plugins/types' +/* export type App = { + id: string + name: string + description: string + mode: AppMode + enable_site: boolean + enable_api: boolean + api_rpm: number + api_rph: number + is_demo: boolean + model_config: AppModelConfig + providers: Array<{ provider: string; token_is_set: boolean }> + site: SiteConfig + created_at: string +} + +export type AppModelConfig = { + provider: string + model_id: string + configs: { + prompt_template: string + prompt_variables: Array<PromptVariable> + completion_params: CompletionParam + } +} + +export type PromptVariable = { + key: string + name: string + description: string + type: string | number + default: string + options: string[] +} + +export type CompletionParam = { + max_tokens: number + temperature: number + top_p: number + echo: boolean + stop: string[] + presence_penalty: number + frequency_penalty: number +} + +export type SiteConfig = { + access_token: string + title: string + author: string + support_email: string + default_language: string + customize_domain: string + theme: string + customize_token_strategy: 'must' | 'allow' | 'not_allow' + prompt_public: boolean +} */ + export enum DSLImportMode { YAML_CONTENT = 'yaml-content', YAML_URL = 'yaml-url', @@ -35,8 +92,6 @@ export type DSLImportResponse = { leaked_dependencies: Dependency[] } -export type AppSSOResponse = { enabled: AppSSO['enable_sso'] } - export type AppTemplatesResponse = { data: AppTemplate[] } @@ -111,5 +166,5 @@ export type TracingStatus = { export type TracingConfig = { tracing_provider: TracingProvider - tracing_config: LangSmithConfig | LangFuseConfig | OpikConfig + tracing_config: LangSmithConfig | LangFuseConfig | OpikConfig | WeaveConfig } diff --git a/web/models/debug.ts b/web/models/debug.ts index 18c2c02b62..e582e8c18a 100644 --- a/web/models/debug.ts +++ b/web/models/debug.ts @@ -59,6 +59,7 @@ export type PromptVariable = { config?: Record<string, any> icon?: string icon_background?: string + hide?: boolean // used in frontend to hide variable } export type CompletionParams = { diff --git a/web/next.config.js b/web/next.config.js index de92fa37a4..9ce1b35644 100644 --- a/web/next.config.js +++ b/web/next.config.js @@ -1,4 +1,4 @@ -const { basePath } = require('./utils/var-basePath') +const { basePath, assetPrefix } = require('./utils/var-basePath') const { codeInspectorPlugin } = require('code-inspector-plugin') const withMDX = require('@next/mdx')({ extension: /\.mdx?$/, @@ -13,9 +13,16 @@ const withMDX = require('@next/mdx')({ }, }) +// the default url to prevent parse url error when running jest +const hasSetWebPrefix = process.env.NEXT_PUBLIC_WEB_PREFIX +const port = process.env.PORT || 3000 +const locImageURLs = !hasSetWebPrefix ? [new URL(`http://localhost:${port}/**`), new URL(`http://127.0.0.1:${port}/**`)] : [] +const remoteImageURLs = [hasSetWebPrefix ? new URL(`${process.env.NEXT_PUBLIC_WEB_PREFIX}/**`) : '', ...locImageURLs].filter(item => !!item) + /** @type {import('next').NextConfig} */ const nextConfig = { basePath, + assetPrefix, webpack: (config, { dev, isServer }) => { config.plugins.push(codeInspectorPlugin({ bundler: 'webpack' })) return config @@ -23,6 +30,16 @@ const nextConfig = { productionBrowserSourceMaps: false, // enable browser source map generation during the production build // Configure pageExtensions to include md and mdx pageExtensions: ['ts', 'tsx', 'js', 'jsx', 'md', 'mdx'], + // https://nextjs.org/docs/messages/next-image-unconfigured-host + images: { + remotePatterns: remoteImageURLs.map(remoteImageURL => ({ + protocol: remoteImageURL.protocol.replace(':', ''), + hostname: remoteImageURL.hostname, + port: remoteImageURL.port, + pathname: remoteImageURL.pathname, + search: '', + })), + }, experimental: { }, // fix all before production. Now it slow the develop speed. diff --git a/web/package.json b/web/package.json index b232f908a3..f583d859e6 100644 --- a/web/package.json +++ b/web/package.json @@ -1,10 +1,22 @@ { "name": "dify-web", - "version": "1.3.0", + "version": "1.4.3", "private": true, "engines": { "node": ">=v22.11.0" }, + "browserslist": [ + "last 1 Chrome version", + "last 1 Firefox version", + "last 1 Edge version", + "last 1 Safari version", + "iOS >=15", + "Android >= 10", + "and_chr >= 126", + "and_ff >= 137", + "and_uc >= 15.5", + "and_qq >= 14.9" + ], "scripts": { "dev": "cross-env NODE_OPTIONS='--inspect' next dev", "build": "next build", @@ -36,13 +48,13 @@ "@headlessui/react": "^2.2.0", "@heroicons/react": "^2.0.16", "@hookform/resolvers": "^3.9.0", - "@lexical/code": "^0.18.0", - "@lexical/link": "^0.18.0", - "@lexical/list": "^0.18.0", - "@lexical/react": "^0.18.0", - "@lexical/selection": "^0.18.0", - "@lexical/text": "^0.18.0", - "@lexical/utils": "^0.18.0", + "@lexical/code": "^0.30.0", + "@lexical/link": "^0.30.0", + "@lexical/list": "^0.30.0", + "@lexical/react": "^0.30.0", + "@lexical/selection": "^0.30.0", + "@lexical/text": "^0.30.0", + "@lexical/utils": "^0.30.0", "@mdx-js/loader": "^3.1.0", "@mdx-js/react": "^3.1.0", "@monaco-editor/react": "^4.6.0", @@ -61,6 +73,7 @@ "ahooks": "^3.8.4", "class-variance-authority": "^0.7.0", "classnames": "^2.5.1", + "clsx": "^2.1.1", "copy-to-clipboard": "^3.3.3", "crypto-js": "^4.2.0", "dayjs": "^1.11.13", @@ -83,7 +96,7 @@ "katex": "^0.16.21", "ky": "^1.7.2", "lamejs": "^1.2.1", - "lexical": "^0.18.0", + "lexical": "^0.30.0", "line-clamp": "^1.0.0", "lodash-es": "^4.17.21", "mermaid": "11.4.1", @@ -219,5 +232,10 @@ "@types/react": "19.0.11", "@types/react-dom": "19.0.4", "@storybook/test": "8.5.0" + }, + "pnpm": { + "overrides": { + "esbuild@<0.25.0": "0.25.0" + } } -} \ No newline at end of file +} diff --git a/web/pnpm-lock.yaml b/web/pnpm-lock.yaml index bf39194eaa..fce3b6581b 100644 --- a/web/pnpm-lock.yaml +++ b/web/pnpm-lock.yaml @@ -8,6 +8,7 @@ overrides: '@types/react': ~18.2.0 '@types/react-dom': ~18.2.0 string-width: 4.2.3 + esbuild@<0.25.0: 0.25.0 importers: @@ -41,29 +42,29 @@ importers: specifier: ^3.9.0 version: 3.10.0(react-hook-form@7.55.0(react@19.0.0)) '@lexical/code': - specifier: ^0.18.0 - version: 0.18.0 + specifier: ^0.30.0 + version: 0.30.0 '@lexical/link': - specifier: ^0.18.0 - version: 0.18.0 + specifier: ^0.30.0 + version: 0.30.0 '@lexical/list': - specifier: ^0.18.0 - version: 0.18.0 + specifier: ^0.30.0 + version: 0.30.0 '@lexical/react': - specifier: ^0.18.0 - version: 0.18.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(yjs@13.6.24) + specifier: ^0.30.0 + version: 0.30.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(yjs@13.6.24) '@lexical/selection': - specifier: ^0.18.0 - version: 0.18.0 + specifier: ^0.30.0 + version: 0.30.0 '@lexical/text': - specifier: ^0.18.0 - version: 0.18.0 + specifier: ^0.30.0 + version: 0.30.0 '@lexical/utils': - specifier: ^0.18.0 - version: 0.18.0 + specifier: ^0.30.0 + version: 0.30.0 '@mdx-js/loader': specifier: ^3.1.0 - version: 3.1.0(acorn@8.14.1)(webpack@5.99.5(esbuild@0.24.2)(uglify-js@3.19.3)) + version: 3.1.0(acorn@8.14.1)(webpack@5.99.5(esbuild@0.25.0)(uglify-js@3.19.3)) '@mdx-js/react': specifier: ^3.1.0 version: 3.1.0(@types/react@18.2.79)(react@19.0.0) @@ -72,7 +73,7 @@ importers: version: 4.7.0(monaco-editor@0.52.2)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) '@next/mdx': specifier: 15.2.3 - version: 15.2.3(@mdx-js/loader@3.1.0(acorn@8.14.1)(webpack@5.99.5(esbuild@0.24.2)(uglify-js@3.19.3)))(@mdx-js/react@3.1.0(@types/react@18.2.79)(react@19.0.0)) + version: 15.2.3(@mdx-js/loader@3.1.0(acorn@8.14.1)(webpack@5.99.5(esbuild@0.25.0)(uglify-js@3.19.3)))(@mdx-js/react@3.1.0(@types/react@18.2.79)(react@19.0.0)) '@octokit/core': specifier: ^6.1.2 version: 6.1.5 @@ -115,6 +116,9 @@ importers: classnames: specifier: ^2.5.1 version: 2.5.1 + clsx: + specifier: ^2.1.1 + version: 2.1.1 copy-to-clipboard: specifier: ^3.3.3 version: 3.3.3 @@ -182,8 +186,8 @@ importers: specifier: ^1.2.1 version: 1.2.1 lexical: - specifier: ^0.18.0 - version: 0.18.0 + specifier: ^0.30.0 + version: 0.30.0 line-clamp: specifier: ^1.0.0 version: 1.0.0 @@ -388,7 +392,7 @@ importers: version: 8.5.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(storybook@8.5.0) '@storybook/nextjs': specifier: 8.5.0 - version: 8.5.0(esbuild@0.24.2)(next@15.2.3(@babel/core@7.26.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(sass@1.86.3))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(sass@1.86.3)(storybook@8.5.0)(type-fest@4.39.1)(typescript@4.9.5)(uglify-js@3.19.3)(webpack-hot-middleware@2.26.1)(webpack@5.99.5(esbuild@0.24.2)(uglify-js@3.19.3)) + version: 8.5.0(esbuild@0.25.0)(next@15.2.3(@babel/core@7.26.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(sass@1.86.3))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(sass@1.86.3)(storybook@8.5.0)(type-fest@4.39.1)(typescript@4.9.5)(uglify-js@3.19.3)(webpack-hot-middleware@2.26.1)(webpack@5.99.5(esbuild@0.25.0)(uglify-js@3.19.3)) '@storybook/react': specifier: 8.5.0 version: 8.5.0(@storybook/test@8.5.0(storybook@8.5.0))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(storybook@8.5.0)(typescript@4.9.5) @@ -1286,8 +1290,8 @@ packages: resolution: {integrity: sha512-+zZymuVLH6zVwXPtCAtC+bDymxmEwEqDftdAK+f407IF1bnX49anIxvBhCA1AqUIfD6egj1jM1vUnSuijjNyYg==} engines: {node: '>=18'} - '@esbuild/aix-ppc64@0.24.2': - resolution: {integrity: sha512-thpVCb/rhxE/BnMLQ7GReQLLN8q9qbHmI55F4489/ByVg2aQaQ6kbcLb6FHkocZzQhxc4gx0sCk0tJkKBFzDhA==} + '@esbuild/aix-ppc64@0.25.0': + resolution: {integrity: sha512-O7vun9Sf8DFjH2UtqK8Ku3LkquL9SZL8OLY1T5NZkA34+wG3OQF7cl4Ql8vdNzM6fzBbYfLaiRLIOZ+2FOCgBQ==} engines: {node: '>=18'} cpu: [ppc64] os: [aix] @@ -1298,8 +1302,8 @@ packages: cpu: [ppc64] os: [aix] - '@esbuild/android-arm64@0.24.2': - resolution: {integrity: sha512-cNLgeqCqV8WxfcTIOeL4OAtSmL8JjcN6m09XIgro1Wi7cF4t/THaWEa7eL5CMoMBdjoHOTh/vwTO/o2TRXIyzg==} + '@esbuild/android-arm64@0.25.0': + resolution: {integrity: sha512-grvv8WncGjDSyUBjN9yHXNt+cq0snxXbDxy5pJtzMKGmmpPxeAmAhWxXI+01lU5rwZomDgD3kJwulEnhTRUd6g==} engines: {node: '>=18'} cpu: [arm64] os: [android] @@ -1310,8 +1314,8 @@ packages: cpu: [arm64] os: [android] - '@esbuild/android-arm@0.24.2': - resolution: {integrity: sha512-tmwl4hJkCfNHwFB3nBa8z1Uy3ypZpxqxfTQOcHX+xRByyYgunVbZ9MzUUfb0RxaHIMnbHagwAxuTL+tnNM+1/Q==} + '@esbuild/android-arm@0.25.0': + resolution: {integrity: sha512-PTyWCYYiU0+1eJKmw21lWtC+d08JDZPQ5g+kFyxP0V+es6VPPSUhM6zk8iImp2jbV6GwjX4pap0JFbUQN65X1g==} engines: {node: '>=18'} cpu: [arm] os: [android] @@ -1322,8 +1326,8 @@ packages: cpu: [arm] os: [android] - '@esbuild/android-x64@0.24.2': - resolution: {integrity: sha512-B6Q0YQDqMx9D7rvIcsXfmJfvUYLoP722bgfBlO5cGvNVb5V/+Y7nhBE3mHV9OpxBf4eAS2S68KZztiPaWq4XYw==} + '@esbuild/android-x64@0.25.0': + resolution: {integrity: sha512-m/ix7SfKG5buCnxasr52+LI78SQ+wgdENi9CqyCXwjVR2X4Jkz+BpC3le3AoBPYTC9NHklwngVXvbJ9/Akhrfg==} engines: {node: '>=18'} cpu: [x64] os: [android] @@ -1334,8 +1338,8 @@ packages: cpu: [x64] os: [android] - '@esbuild/darwin-arm64@0.24.2': - resolution: {integrity: sha512-kj3AnYWc+CekmZnS5IPu9D+HWtUI49hbnyqk0FLEJDbzCIQt7hg7ucF1SQAilhtYpIujfaHr6O0UHlzzSPdOeA==} + '@esbuild/darwin-arm64@0.25.0': + resolution: {integrity: sha512-mVwdUb5SRkPayVadIOI78K7aAnPamoeFR2bT5nszFUZ9P8UpK4ratOdYbZZXYSqPKMHfS1wdHCJk1P1EZpRdvw==} engines: {node: '>=18'} cpu: [arm64] os: [darwin] @@ -1346,8 +1350,8 @@ packages: cpu: [arm64] os: [darwin] - '@esbuild/darwin-x64@0.24.2': - resolution: {integrity: sha512-WeSrmwwHaPkNR5H3yYfowhZcbriGqooyu3zI/3GGpF8AyUdsrrP0X6KumITGA9WOyiJavnGZUwPGvxvwfWPHIA==} + '@esbuild/darwin-x64@0.25.0': + resolution: {integrity: sha512-DgDaYsPWFTS4S3nWpFcMn/33ZZwAAeAFKNHNa1QN0rI4pUjgqf0f7ONmXf6d22tqTY+H9FNdgeaAa+YIFUn2Rg==} engines: {node: '>=18'} cpu: [x64] os: [darwin] @@ -1358,8 +1362,8 @@ packages: cpu: [x64] os: [darwin] - '@esbuild/freebsd-arm64@0.24.2': - resolution: {integrity: sha512-UN8HXjtJ0k/Mj6a9+5u6+2eZ2ERD7Edt1Q9IZiB5UZAIdPnVKDoG7mdTVGhHJIeEml60JteamR3qhsr1r8gXvg==} + '@esbuild/freebsd-arm64@0.25.0': + resolution: {integrity: sha512-VN4ocxy6dxefN1MepBx/iD1dH5K8qNtNe227I0mnTRjry8tj5MRk4zprLEdG8WPyAPb93/e4pSgi1SoHdgOa4w==} engines: {node: '>=18'} cpu: [arm64] os: [freebsd] @@ -1370,8 +1374,8 @@ packages: cpu: [arm64] os: [freebsd] - '@esbuild/freebsd-x64@0.24.2': - resolution: {integrity: sha512-TvW7wE/89PYW+IevEJXZ5sF6gJRDY/14hyIGFXdIucxCsbRmLUcjseQu1SyTko+2idmCw94TgyaEZi9HUSOe3Q==} + '@esbuild/freebsd-x64@0.25.0': + resolution: {integrity: sha512-mrSgt7lCh07FY+hDD1TxiTyIHyttn6vnjesnPoVDNmDfOmggTLXRv8Id5fNZey1gl/V2dyVK1VXXqVsQIiAk+A==} engines: {node: '>=18'} cpu: [x64] os: [freebsd] @@ -1382,8 +1386,8 @@ packages: cpu: [x64] os: [freebsd] - '@esbuild/linux-arm64@0.24.2': - resolution: {integrity: sha512-7HnAD6074BW43YvvUmE/35Id9/NB7BeX5EoNkK9obndmZBUk8xmJJeU7DwmUeN7tkysslb2eSl6CTrYz6oEMQg==} + '@esbuild/linux-arm64@0.25.0': + resolution: {integrity: sha512-9QAQjTWNDM/Vk2bgBl17yWuZxZNQIF0OUUuPZRKoDtqF2k4EtYbpyiG5/Dk7nqeK6kIJWPYldkOcBqjXjrUlmg==} engines: {node: '>=18'} cpu: [arm64] os: [linux] @@ -1394,8 +1398,8 @@ packages: cpu: [arm64] os: [linux] - '@esbuild/linux-arm@0.24.2': - resolution: {integrity: sha512-n0WRM/gWIdU29J57hJyUdIsk0WarGd6To0s+Y+LwvlC55wt+GT/OgkwoXCXvIue1i1sSNWblHEig00GBWiJgfA==} + '@esbuild/linux-arm@0.25.0': + resolution: {integrity: sha512-vkB3IYj2IDo3g9xX7HqhPYxVkNQe8qTK55fraQyTzTX/fxaDtXiEnavv9geOsonh2Fd2RMB+i5cbhu2zMNWJwg==} engines: {node: '>=18'} cpu: [arm] os: [linux] @@ -1406,8 +1410,8 @@ packages: cpu: [arm] os: [linux] - '@esbuild/linux-ia32@0.24.2': - resolution: {integrity: sha512-sfv0tGPQhcZOgTKO3oBE9xpHuUqguHvSo4jl+wjnKwFpapx+vUDcawbwPNuBIAYdRAvIDBfZVvXprIj3HA+Ugw==} + '@esbuild/linux-ia32@0.25.0': + resolution: {integrity: sha512-43ET5bHbphBegyeqLb7I1eYn2P/JYGNmzzdidq/w0T8E2SsYL1U6un2NFROFRg1JZLTzdCoRomg8Rvf9M6W6Gg==} engines: {node: '>=18'} cpu: [ia32] os: [linux] @@ -1418,8 +1422,8 @@ packages: cpu: [ia32] os: [linux] - '@esbuild/linux-loong64@0.24.2': - resolution: {integrity: sha512-CN9AZr8kEndGooS35ntToZLTQLHEjtVB5n7dl8ZcTZMonJ7CCfStrYhrzF97eAecqVbVJ7APOEe18RPI4KLhwQ==} + '@esbuild/linux-loong64@0.25.0': + resolution: {integrity: sha512-fC95c/xyNFueMhClxJmeRIj2yrSMdDfmqJnyOY4ZqsALkDrrKJfIg5NTMSzVBr5YW1jf+l7/cndBfP3MSDpoHw==} engines: {node: '>=18'} cpu: [loong64] os: [linux] @@ -1430,8 +1434,8 @@ packages: cpu: [loong64] os: [linux] - '@esbuild/linux-mips64el@0.24.2': - resolution: {integrity: sha512-iMkk7qr/wl3exJATwkISxI7kTcmHKE+BlymIAbHO8xanq/TjHaaVThFF6ipWzPHryoFsesNQJPE/3wFJw4+huw==} + '@esbuild/linux-mips64el@0.25.0': + resolution: {integrity: sha512-nkAMFju7KDW73T1DdH7glcyIptm95a7Le8irTQNO/qtkoyypZAnjchQgooFUDQhNAy4iu08N79W4T4pMBwhPwQ==} engines: {node: '>=18'} cpu: [mips64el] os: [linux] @@ -1442,8 +1446,8 @@ packages: cpu: [mips64el] os: [linux] - '@esbuild/linux-ppc64@0.24.2': - resolution: {integrity: sha512-shsVrgCZ57Vr2L8mm39kO5PPIb+843FStGt7sGGoqiiWYconSxwTiuswC1VJZLCjNiMLAMh34jg4VSEQb+iEbw==} + '@esbuild/linux-ppc64@0.25.0': + resolution: {integrity: sha512-NhyOejdhRGS8Iwv+KKR2zTq2PpysF9XqY+Zk77vQHqNbo/PwZCzB5/h7VGuREZm1fixhs4Q/qWRSi5zmAiO4Fw==} engines: {node: '>=18'} cpu: [ppc64] os: [linux] @@ -1454,8 +1458,8 @@ packages: cpu: [ppc64] os: [linux] - '@esbuild/linux-riscv64@0.24.2': - resolution: {integrity: sha512-4eSFWnU9Hhd68fW16GD0TINewo1L6dRrB+oLNNbYyMUAeOD2yCK5KXGK1GH4qD/kT+bTEXjsyTCiJGHPZ3eM9Q==} + '@esbuild/linux-riscv64@0.25.0': + resolution: {integrity: sha512-5S/rbP5OY+GHLC5qXp1y/Mx//e92L1YDqkiBbO9TQOvuFXM+iDqUNG5XopAnXoRH3FjIUDkeGcY1cgNvnXp/kA==} engines: {node: '>=18'} cpu: [riscv64] os: [linux] @@ -1466,8 +1470,8 @@ packages: cpu: [riscv64] os: [linux] - '@esbuild/linux-s390x@0.24.2': - resolution: {integrity: sha512-S0Bh0A53b0YHL2XEXC20bHLuGMOhFDO6GN4b3YjRLK//Ep3ql3erpNcPlEFed93hsQAjAQDNsvcK+hV90FubSw==} + '@esbuild/linux-s390x@0.25.0': + resolution: {integrity: sha512-XM2BFsEBz0Fw37V0zU4CXfcfuACMrppsMFKdYY2WuTS3yi8O1nFOhil/xhKTmE1nPmVyvQJjJivgDT+xh8pXJA==} engines: {node: '>=18'} cpu: [s390x] os: [linux] @@ -1478,8 +1482,8 @@ packages: cpu: [s390x] os: [linux] - '@esbuild/linux-x64@0.24.2': - resolution: {integrity: sha512-8Qi4nQcCTbLnK9WoMjdC9NiTG6/E38RNICU6sUNqK0QFxCYgoARqVqxdFmWkdonVsvGqWhmm7MO0jyTqLqwj0Q==} + '@esbuild/linux-x64@0.25.0': + resolution: {integrity: sha512-9yl91rHw/cpwMCNytUDxwj2XjFpxML0y9HAOH9pNVQDpQrBxHy01Dx+vaMu0N1CKa/RzBD2hB4u//nfc+Sd3Cw==} engines: {node: '>=18'} cpu: [x64] os: [linux] @@ -1490,8 +1494,8 @@ packages: cpu: [x64] os: [linux] - '@esbuild/netbsd-arm64@0.24.2': - resolution: {integrity: sha512-wuLK/VztRRpMt9zyHSazyCVdCXlpHkKm34WUyinD2lzK07FAHTq0KQvZZlXikNWkDGoT6x3TD51jKQ7gMVpopw==} + '@esbuild/netbsd-arm64@0.25.0': + resolution: {integrity: sha512-RuG4PSMPFfrkH6UwCAqBzauBWTygTvb1nxWasEJooGSJ/NwRw7b2HOwyRTQIU97Hq37l3npXoZGYMy3b3xYvPw==} engines: {node: '>=18'} cpu: [arm64] os: [netbsd] @@ -1502,8 +1506,8 @@ packages: cpu: [arm64] os: [netbsd] - '@esbuild/netbsd-x64@0.24.2': - resolution: {integrity: sha512-VefFaQUc4FMmJuAxmIHgUmfNiLXY438XrL4GDNV1Y1H/RW3qow68xTwjZKfj/+Plp9NANmzbH5R40Meudu8mmw==} + '@esbuild/netbsd-x64@0.25.0': + resolution: {integrity: sha512-jl+qisSB5jk01N5f7sPCsBENCOlPiS/xptD5yxOx2oqQfyourJwIKLRA2yqWdifj3owQZCL2sn6o08dBzZGQzA==} engines: {node: '>=18'} cpu: [x64] os: [netbsd] @@ -1514,8 +1518,8 @@ packages: cpu: [x64] os: [netbsd] - '@esbuild/openbsd-arm64@0.24.2': - resolution: {integrity: sha512-YQbi46SBct6iKnszhSvdluqDmxCJA+Pu280Av9WICNwQmMxV7nLRHZfjQzwbPs3jeWnuAhE9Jy0NrnJ12Oz+0A==} + '@esbuild/openbsd-arm64@0.25.0': + resolution: {integrity: sha512-21sUNbq2r84YE+SJDfaQRvdgznTD8Xc0oc3p3iW/a1EVWeNj/SdUCbm5U0itZPQYRuRTW20fPMWMpcrciH2EJw==} engines: {node: '>=18'} cpu: [arm64] os: [openbsd] @@ -1526,8 +1530,8 @@ packages: cpu: [arm64] os: [openbsd] - '@esbuild/openbsd-x64@0.24.2': - resolution: {integrity: sha512-+iDS6zpNM6EnJyWv0bMGLWSWeXGN/HTaF/LXHXHwejGsVi+ooqDfMCCTerNFxEkM3wYVcExkeGXNqshc9iMaOA==} + '@esbuild/openbsd-x64@0.25.0': + resolution: {integrity: sha512-2gwwriSMPcCFRlPlKx3zLQhfN/2WjJ2NSlg5TKLQOJdV0mSxIcYNTMhk3H3ulL/cak+Xj0lY1Ym9ysDV1igceg==} engines: {node: '>=18'} cpu: [x64] os: [openbsd] @@ -1538,8 +1542,8 @@ packages: cpu: [x64] os: [openbsd] - '@esbuild/sunos-x64@0.24.2': - resolution: {integrity: sha512-hTdsW27jcktEvpwNHJU4ZwWFGkz2zRJUz8pvddmXPtXDzVKTTINmlmga3ZzwcuMpUvLw7JkLy9QLKyGpD2Yxig==} + '@esbuild/sunos-x64@0.25.0': + resolution: {integrity: sha512-bxI7ThgLzPrPz484/S9jLlvUAHYMzy6I0XiU1ZMeAEOBcS0VePBFxh1JjTQt3Xiat5b6Oh4x7UC7IwKQKIJRIg==} engines: {node: '>=18'} cpu: [x64] os: [sunos] @@ -1550,8 +1554,8 @@ packages: cpu: [x64] os: [sunos] - '@esbuild/win32-arm64@0.24.2': - resolution: {integrity: sha512-LihEQ2BBKVFLOC9ZItT9iFprsE9tqjDjnbulhHoFxYQtQfai7qfluVODIYxt1PgdoyQkz23+01rzwNwYfutxUQ==} + '@esbuild/win32-arm64@0.25.0': + resolution: {integrity: sha512-ZUAc2YK6JW89xTbXvftxdnYy3m4iHIkDtK3CLce8wg8M2L+YZhIvO1DKpxrd0Yr59AeNNkTiic9YLf6FTtXWMw==} engines: {node: '>=18'} cpu: [arm64] os: [win32] @@ -1562,8 +1566,8 @@ packages: cpu: [arm64] os: [win32] - '@esbuild/win32-ia32@0.24.2': - resolution: {integrity: sha512-q+iGUwfs8tncmFC9pcnD5IvRHAzmbwQ3GPS5/ceCyHdjXubwQWI12MKWSNSMYLJMq23/IUCvJMS76PDqXe1fxA==} + '@esbuild/win32-ia32@0.25.0': + resolution: {integrity: sha512-eSNxISBu8XweVEWG31/JzjkIGbGIJN/TrRoiSVZwZ6pkC6VX4Im/WV2cz559/TXLcYbcrDN8JtKgd9DJVIo8GA==} engines: {node: '>=18'} cpu: [ia32] os: [win32] @@ -1574,8 +1578,8 @@ packages: cpu: [ia32] os: [win32] - '@esbuild/win32-x64@0.24.2': - resolution: {integrity: sha512-7VTgWzgMGvup6aSqDPLiW5zHaxYJGTO4OokMjIlrCtf+VpEL+cXKtCvg723iguPYI5oaUNdS+/V7OU2gvXVWEg==} + '@esbuild/win32-x64@0.25.0': + resolution: {integrity: sha512-ZENoHJBxA20C2zFzh6AI4fT6RraMzjYw4xKWemRTRmRVtN9c5DcH9r/f2ihEkMjOW5eGgrwCslG/+Y/3bL+DHQ==} engines: {node: '>=18'} cpu: [x64] os: [win32] @@ -1974,74 +1978,74 @@ packages: '@jridgewell/trace-mapping@0.3.9': resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==} - '@lexical/clipboard@0.18.0': - resolution: {integrity: sha512-ybc+hx14wj0n2ZjdOkLcZ02MRB3UprXjpLDXlByFIuVcZpUxVcp3NzA0UBPOKXYKvdt0bmgjnAsFWM5OSbwS0w==} + '@lexical/clipboard@0.30.0': + resolution: {integrity: sha512-taWQURtE6xF4Jy4I8teQw3+nVBVNO1r+9N9voXeivgwxSrAM40rjqQ/aZEKxWbwZtfkABDkCEArbVrqP0SkWcQ==} - '@lexical/code@0.18.0': - resolution: {integrity: sha512-VB8fRHIrB8QTqyZUvGBMVWP2tpKe3ArOjPdWAqgrS8MVFldqUhuTHcW+XJFkVxcEBYCXynNT29YRYtQhfQ+vDQ==} + '@lexical/code@0.30.0': + resolution: {integrity: sha512-OmA6Bmp3w9SMV25Hae1dLXtPNOdCgnzo1xy84K19U+dPP5iqXagwFq5oY/9PVOOI2wgaQHrz3C+7B4phDb9xaA==} - '@lexical/devtools-core@0.18.0': - resolution: {integrity: sha512-gVgtEkLwGjz1frOmDpFJzDPFxPgAcC9n5ZaaZWHo5GLcptnQmkuLm1t+UInQWujXhFmcyJzfiqDaMJ8EIcb2Ww==} + '@lexical/devtools-core@0.30.0': + resolution: {integrity: sha512-6vKEEIUym8pQ+tWt4VfRMOGE/dtfyPr9e1zPrAAV7Y/EdzK0AJYPPlw2Dt5Uqq9rposcIriqF4MkuFvy4UcZiQ==} peerDependencies: react: '>=17.x' react-dom: '>=17.x' - '@lexical/dragon@0.18.0': - resolution: {integrity: sha512-toD/y2/TgtG+eFVKXf65kDk/Mv02FwgmcGH18nyAabZnO1TLBaMYPkGFdTTZ8hVmQxqIu9nZuLWUbdIBMs8UWw==} + '@lexical/dragon@0.30.0': + resolution: {integrity: sha512-eikVYw1pIcFIOojn2mGlps59YcyT9ATd6UMIx/ivuscakrZeU7SZM/F6c75QPJXNOu1b2koOo+4Bb1GT6jixGQ==} - '@lexical/hashtag@0.18.0': - resolution: {integrity: sha512-bm+Sv7keguVYbUY0ngd+iAv2Owd3dePzdVkzkmw9Al8GPXkE5ll8fjq6Xjw2u3OVhf+9pTnesIo/AS7H+h0exw==} + '@lexical/hashtag@0.30.0': + resolution: {integrity: sha512-gB3DobSdAc0YZUhlTT7ZAUr+6RRREQ3UWVC1twdtFvXXw1vyTUXH2gWTDp/ParwBZ16Lnrg8mxET8Nu/qD1PSw==} - '@lexical/history@0.18.0': - resolution: {integrity: sha512-c87J4ke1Sae03coElJay2Ikac/4OcA2OmhtNbt2gAi/XBtcsP4mPuz1yZfZf9XIe+weekObgjinvZekQ2AFw0g==} + '@lexical/history@0.30.0': + resolution: {integrity: sha512-dxudthi94vSLQKXVq3LSwcOVkOmb2lvxoy7sCma513yJbrsn3fPLppR2Ynhl6aB9oPw675wSDrfsE6BG3U3+CA==} - '@lexical/html@0.18.0': - resolution: {integrity: sha512-8lhba1DFnnobXgYm4Rk5Gr2tZedD4Gl6A/NKCt7whO/CET63vT3UnK2ggcVVgtIJG530Cv0bdZoJbJu5DauI5w==} + '@lexical/html@0.30.0': + resolution: {integrity: sha512-GdegWO6RjJ7eE+yD3Z0X/OpT88SZjOs3DyQ0rgrZy3z7RPaFCbEEcq0M/NssJbKAB1XOFUsUFrnS7kZs1vJzGg==} - '@lexical/link@0.18.0': - resolution: {integrity: sha512-GCYcbNTSTwJk0lr+GMc8nn6Meq44BZs3QL2d1B0skpZAspd8yI53sRS6HDy5P+jW5P0dzyZr/XJAU4U+7zsEEg==} + '@lexical/link@0.30.0': + resolution: {integrity: sha512-isD3PC0ywQIwbtekHYEvh7hDxcPz/cEr/AspYntYs08u5J0czhw3rpqnXWGauWaav5V9ExIkf1ZkGUFUI6bw5w==} - '@lexical/list@0.18.0': - resolution: {integrity: sha512-DEWs9Scbg3+STZeE2O0OoG8SWnKnxQccObBzyeHRjn4GAN6JA7lgcAzfrdgp0fNWTbMM/ku876MmXKGnqhvg9Q==} + '@lexical/list@0.30.0': + resolution: {integrity: sha512-WKnwH+Cg+j2I0EbaEyPHo8MPNyrqQV3W1NmH5Mf/iRxCq42z7NJxemhmRUxbqv8vsugACwBkh2RlkhekRXmUQQ==} - '@lexical/mark@0.18.0': - resolution: {integrity: sha512-QA4YWfTP5WWnCnoH/RmfcsSZyhhd7oeFWDpfP7S8Bbmhz6kiPwGcsVr+uRQBBT56AqEX167xX2rX8JR6FiYZqA==} + '@lexical/mark@0.30.0': + resolution: {integrity: sha512-dLFH6tJ2WQUSdo1Y2Jp81vRT8j48FjF75K5YLRsKD/UFxWEy+RFgRXsd0H/BuFkx/jPTXt6xe8CaIrZvek8mLg==} - '@lexical/markdown@0.18.0': - resolution: {integrity: sha512-uSWwcK8eJw5C+waEhU5WoX8W+JxNZbKuFnZwsn5nsp+iQgqMj4qY6g0yJub4sq8vvh6jjl4vVXhXTq2up9aykw==} + '@lexical/markdown@0.30.0': + resolution: {integrity: sha512-GGddZs63k0wb3/fdL7JyBjiy8L1AIHuRKT68riWbKAcNL7rfMl3Uy5VnMkgV/5bN/2eUQijkGjxG+VxsR8RWbw==} - '@lexical/offset@0.18.0': - resolution: {integrity: sha512-KGlboyLSxQAH5PMOlJmyvHlbYXZneVnKiHpfyBV5IUX5kuyB/eZbQEYcJP9saekfQ5Xb1FWXWmsZEo+sWtrrZA==} + '@lexical/offset@0.30.0': + resolution: {integrity: sha512-sZFbZt5dVdtrdoYk79i13xBDs8/MHXw6CqmZNht85L7UdwiuzVqA3KTyaMe60Vrg6mfsKIVjghbpMOhspcuCrw==} - '@lexical/overflow@0.18.0': - resolution: {integrity: sha512-3ATTwttVgZtVLq60ZUWbpbXBbpuMa3PZD5CxSP3nulviL+2I4phvacV4WUN+8wMeq+PGmuarl+cYfrFL02ii3g==} + '@lexical/overflow@0.30.0': + resolution: {integrity: sha512-fvjWnhtPZLMS3qJ6HC6tZTOMmcfNmeRUkgXTas9bvWT8Yul+WLJ/fWjzwvBcqpKlvPQjRFOcDcrW8T/Rp7KPrg==} - '@lexical/plain-text@0.18.0': - resolution: {integrity: sha512-L6yQpiwW0ZacY1oNwvRBxSuW2TZaUcveZLheJc8JzGcZoVxzII/CAbLZG8691VbNuKsbOURiNXZIsgwujKmo4Q==} + '@lexical/plain-text@0.30.0': + resolution: {integrity: sha512-jvxMMxFO3Yuj7evWsc33IGWfigU5A1KrJaIf6zv6GmYj0a7ZRkR1x6vJyc7AlgUM70sld+dozLdoynguQIlmrQ==} - '@lexical/react@0.18.0': - resolution: {integrity: sha512-DLvIbTsjvFIFqm+9zvAjEwuZHAbSxzZf1AGqf1lLctlL/Ran0f+8EZOv5jttELTe7xISZ2+xSXTLRfyxhNwGXQ==} + '@lexical/react@0.30.0': + resolution: {integrity: sha512-fsb6voXzxHyP55lXdmnGhHMfxe6g/f+0NpmfPCkutOXYnY8UqKa86LLYl4Nrsi8HX8BRZfh1H0IjkzDG6EzVPw==} peerDependencies: react: '>=17.x' react-dom: '>=17.x' - '@lexical/rich-text@0.18.0': - resolution: {integrity: sha512-xMANCB7WueMsmWK8qxik5FZN4ApyaHWHQILS9r4FTbdv/DlNepsR7Pt8kg2317xZ56NAueQLIdyyKYXG1nBrHw==} + '@lexical/rich-text@0.30.0': + resolution: {integrity: sha512-oitOh5u68E5DBZt5VBZIaIeM/iNdt3mIDkGp2C259x81V/9KlSNB9c3rqdTKcs/A+Msw4j60FRhdmZcKQ9uYUA==} - '@lexical/selection@0.18.0': - resolution: {integrity: sha512-mJoMhmxeZLfM9K2JMYETs9u179IkHQUlgtYG5GZJHjKx2iUn+9KvJ9RVssq+Lusi7C/N42wWPGNHDPdUvFtxXg==} + '@lexical/selection@0.30.0': + resolution: {integrity: sha512-Ys2XfSmIV/Irg6Xo663YtR4jozIv/7sDemArkEGHT0fxZn2py5qftowPF5IBqFYxKTigAdv5vVPwusBvAnLIEg==} - '@lexical/table@0.18.0': - resolution: {integrity: sha512-TeTAnuFAAgVjm1QE8adRB3GFWN+DUUiS4vzGq+ynPRCtNdpmW27NmTkRMyxKsetUtt7nIFfj4DvLvor4RwqIpA==} + '@lexical/table@0.30.0': + resolution: {integrity: sha512-XPCIMIGnZLKTa5/4cP16bXbmzvMndPR273HNl7ZaF35ky7UjZxdj42HBbE7q9zw2zbRPDiO77EyhYA0p20cbdw==} - '@lexical/text@0.18.0': - resolution: {integrity: sha512-MTHSBeq3K0+lqSsP5oysBMnY4tPVhB8kAa2xBnEc3dYgXFxEEvJwZahbHNX93EPObtJkxXfUuI63Al4G3lYK8A==} + '@lexical/text@0.30.0': + resolution: {integrity: sha512-P0ptriFwwP/hoDpz/MoBbzHxrFHqh0kCGzASWUdRZ1zrU0yPvJ9vV/UNMhyolH7xx+eAGI1Yl+m74NlpGmXqTg==} - '@lexical/utils@0.18.0': - resolution: {integrity: sha512-4s9dVpBZjqIaA/1q2GtfWFjKsv2Wqhjer0Zw2mcl1TIVN0zreXxcTKN316QppAWmSQJxVGvkWHjjaZJwl6/TSw==} + '@lexical/utils@0.30.0': + resolution: {integrity: sha512-VJlAUhupCZmnbYYX3zMWovd4viu2guR01sAqKGbbOMbP+4rlaymixFbinvNPaRKDBloOARi+fpiveQFxnyr/Ew==} - '@lexical/yjs@0.18.0': - resolution: {integrity: sha512-rl7Rl9XIb3ygQEEHOFtACdXs3BE+UUUmdyNqB6kK9A6IRGz+w4Azp+qzt8It/t+c0oaSYHpAtcLNXg1amJz+kA==} + '@lexical/yjs@0.30.0': + resolution: {integrity: sha512-mWGFAGpUPz4JoSV+Y0cZOzOZJoMLbVb/enldxEbV0xX71BBVzD0c0vjPxuaIJ9MtNkRZdK3eOubj+B45iOECtw==} peerDependencies: yjs: '>=13.5.22' @@ -4646,10 +4650,10 @@ packages: esbuild-register@3.6.0: resolution: {integrity: sha512-H2/S7Pm8a9CL1uhp9OvjwrBh5Pvx0H8qVOxNu8Wed9Y7qv56MPtq+GGM8RJpq6glYJn9Wspr8uw7l55uyinNeg==} peerDependencies: - esbuild: '>=0.12 <1' + esbuild: 0.25.0 - esbuild@0.24.2: - resolution: {integrity: sha512-+9egpBW8I3CD5XPe0n6BfT5fxLzxrlDzqydF3aviG+9ni1lDC/OvMHcxqEFV0+LANZG5R1bFMWfUrjVsdwxJvA==} + esbuild@0.25.0: + resolution: {integrity: sha512-BXq5mqc8ltbaN34cDqWuYKyNhX8D/Z0J1xdtdQ8UcIIIyJyz+ZMKUt58tF3SrZ85jcfN/PZYhjR5uDQAYNVbuw==} engines: {node: '>=18'} hasBin: true @@ -6097,8 +6101,8 @@ packages: resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} engines: {node: '>= 0.8.0'} - lexical@0.18.0: - resolution: {integrity: sha512-3K/B0RpzjoW+Wj2E455wWXxkqxqK8UgdIiuqkOqdOsoSSo5mCkHOU6eVw7Nlmlr1MFvAMzGmz4RPn8NZaLQ2Mw==} + lexical@0.30.0: + resolution: {integrity: sha512-6gxYeXaJiAcreJD0whCofvO0MuJmnWoIgIl1w7L5FTigfhnEohuCx2SoI/oywzfzXE9gzZnyr3rVvZrMItPL8A==} lib0@0.2.102: resolution: {integrity: sha512-g70kydI0I1sZU0ChO8mBbhw0oUW/8U0GHzygpvEIx8k+jgOpqnTSb/E+70toYVqHxBhrERD21TwD5QcZJQ40ZQ==} @@ -9624,151 +9628,151 @@ snapshots: esquery: 1.6.0 jsdoc-type-pratt-parser: 4.1.0 - '@esbuild/aix-ppc64@0.24.2': + '@esbuild/aix-ppc64@0.25.0': optional: true '@esbuild/aix-ppc64@0.25.2': optional: true - '@esbuild/android-arm64@0.24.2': + '@esbuild/android-arm64@0.25.0': optional: true '@esbuild/android-arm64@0.25.2': optional: true - '@esbuild/android-arm@0.24.2': + '@esbuild/android-arm@0.25.0': optional: true '@esbuild/android-arm@0.25.2': optional: true - '@esbuild/android-x64@0.24.2': + '@esbuild/android-x64@0.25.0': optional: true '@esbuild/android-x64@0.25.2': optional: true - '@esbuild/darwin-arm64@0.24.2': + '@esbuild/darwin-arm64@0.25.0': optional: true '@esbuild/darwin-arm64@0.25.2': optional: true - '@esbuild/darwin-x64@0.24.2': + '@esbuild/darwin-x64@0.25.0': optional: true '@esbuild/darwin-x64@0.25.2': optional: true - '@esbuild/freebsd-arm64@0.24.2': + '@esbuild/freebsd-arm64@0.25.0': optional: true '@esbuild/freebsd-arm64@0.25.2': optional: true - '@esbuild/freebsd-x64@0.24.2': + '@esbuild/freebsd-x64@0.25.0': optional: true '@esbuild/freebsd-x64@0.25.2': optional: true - '@esbuild/linux-arm64@0.24.2': + '@esbuild/linux-arm64@0.25.0': optional: true '@esbuild/linux-arm64@0.25.2': optional: true - '@esbuild/linux-arm@0.24.2': + '@esbuild/linux-arm@0.25.0': optional: true '@esbuild/linux-arm@0.25.2': optional: true - '@esbuild/linux-ia32@0.24.2': + '@esbuild/linux-ia32@0.25.0': optional: true '@esbuild/linux-ia32@0.25.2': optional: true - '@esbuild/linux-loong64@0.24.2': + '@esbuild/linux-loong64@0.25.0': optional: true '@esbuild/linux-loong64@0.25.2': optional: true - '@esbuild/linux-mips64el@0.24.2': + '@esbuild/linux-mips64el@0.25.0': optional: true '@esbuild/linux-mips64el@0.25.2': optional: true - '@esbuild/linux-ppc64@0.24.2': + '@esbuild/linux-ppc64@0.25.0': optional: true '@esbuild/linux-ppc64@0.25.2': optional: true - '@esbuild/linux-riscv64@0.24.2': + '@esbuild/linux-riscv64@0.25.0': optional: true '@esbuild/linux-riscv64@0.25.2': optional: true - '@esbuild/linux-s390x@0.24.2': + '@esbuild/linux-s390x@0.25.0': optional: true '@esbuild/linux-s390x@0.25.2': optional: true - '@esbuild/linux-x64@0.24.2': + '@esbuild/linux-x64@0.25.0': optional: true '@esbuild/linux-x64@0.25.2': optional: true - '@esbuild/netbsd-arm64@0.24.2': + '@esbuild/netbsd-arm64@0.25.0': optional: true '@esbuild/netbsd-arm64@0.25.2': optional: true - '@esbuild/netbsd-x64@0.24.2': + '@esbuild/netbsd-x64@0.25.0': optional: true '@esbuild/netbsd-x64@0.25.2': optional: true - '@esbuild/openbsd-arm64@0.24.2': + '@esbuild/openbsd-arm64@0.25.0': optional: true '@esbuild/openbsd-arm64@0.25.2': optional: true - '@esbuild/openbsd-x64@0.24.2': + '@esbuild/openbsd-x64@0.25.0': optional: true '@esbuild/openbsd-x64@0.25.2': optional: true - '@esbuild/sunos-x64@0.24.2': + '@esbuild/sunos-x64@0.25.0': optional: true '@esbuild/sunos-x64@0.25.2': optional: true - '@esbuild/win32-arm64@0.24.2': + '@esbuild/win32-arm64@0.25.0': optional: true '@esbuild/win32-arm64@0.25.2': optional: true - '@esbuild/win32-ia32@0.24.2': + '@esbuild/win32-ia32@0.25.0': optional: true '@esbuild/win32-ia32@0.25.2': optional: true - '@esbuild/win32-x64@0.24.2': + '@esbuild/win32-x64@0.25.0': optional: true '@esbuild/win32-x64@0.25.2': @@ -10326,151 +10330,149 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.0 - '@lexical/clipboard@0.18.0': + '@lexical/clipboard@0.30.0': dependencies: - '@lexical/html': 0.18.0 - '@lexical/list': 0.18.0 - '@lexical/selection': 0.18.0 - '@lexical/utils': 0.18.0 - lexical: 0.18.0 + '@lexical/html': 0.30.0 + '@lexical/list': 0.30.0 + '@lexical/selection': 0.30.0 + '@lexical/utils': 0.30.0 + lexical: 0.30.0 - '@lexical/code@0.18.0': + '@lexical/code@0.30.0': dependencies: - '@lexical/utils': 0.18.0 - lexical: 0.18.0 + '@lexical/utils': 0.30.0 + lexical: 0.30.0 prismjs: 1.30.0 - '@lexical/devtools-core@0.18.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': + '@lexical/devtools-core@0.30.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': dependencies: - '@lexical/html': 0.18.0 - '@lexical/link': 0.18.0 - '@lexical/mark': 0.18.0 - '@lexical/table': 0.18.0 - '@lexical/utils': 0.18.0 - lexical: 0.18.0 + '@lexical/html': 0.30.0 + '@lexical/link': 0.30.0 + '@lexical/mark': 0.30.0 + '@lexical/table': 0.30.0 + '@lexical/utils': 0.30.0 + lexical: 0.30.0 react: 19.0.0 react-dom: 19.0.0(react@19.0.0) - '@lexical/dragon@0.18.0': + '@lexical/dragon@0.30.0': dependencies: - lexical: 0.18.0 + lexical: 0.30.0 - '@lexical/hashtag@0.18.0': + '@lexical/hashtag@0.30.0': dependencies: - '@lexical/utils': 0.18.0 - lexical: 0.18.0 + '@lexical/utils': 0.30.0 + lexical: 0.30.0 - '@lexical/history@0.18.0': + '@lexical/history@0.30.0': dependencies: - '@lexical/utils': 0.18.0 - lexical: 0.18.0 + '@lexical/utils': 0.30.0 + lexical: 0.30.0 - '@lexical/html@0.18.0': + '@lexical/html@0.30.0': dependencies: - '@lexical/selection': 0.18.0 - '@lexical/utils': 0.18.0 - lexical: 0.18.0 + '@lexical/selection': 0.30.0 + '@lexical/utils': 0.30.0 + lexical: 0.30.0 - '@lexical/link@0.18.0': + '@lexical/link@0.30.0': dependencies: - '@lexical/utils': 0.18.0 - lexical: 0.18.0 + '@lexical/utils': 0.30.0 + lexical: 0.30.0 - '@lexical/list@0.18.0': + '@lexical/list@0.30.0': dependencies: - '@lexical/utils': 0.18.0 - lexical: 0.18.0 + '@lexical/selection': 0.30.0 + '@lexical/utils': 0.30.0 + lexical: 0.30.0 - '@lexical/mark@0.18.0': + '@lexical/mark@0.30.0': dependencies: - '@lexical/utils': 0.18.0 - lexical: 0.18.0 + '@lexical/utils': 0.30.0 + lexical: 0.30.0 - '@lexical/markdown@0.18.0': + '@lexical/markdown@0.30.0': dependencies: - '@lexical/code': 0.18.0 - '@lexical/link': 0.18.0 - '@lexical/list': 0.18.0 - '@lexical/rich-text': 0.18.0 - '@lexical/text': 0.18.0 - '@lexical/utils': 0.18.0 - lexical: 0.18.0 + '@lexical/code': 0.30.0 + '@lexical/link': 0.30.0 + '@lexical/list': 0.30.0 + '@lexical/rich-text': 0.30.0 + '@lexical/text': 0.30.0 + '@lexical/utils': 0.30.0 + lexical: 0.30.0 - '@lexical/offset@0.18.0': + '@lexical/offset@0.30.0': dependencies: - lexical: 0.18.0 + lexical: 0.30.0 - '@lexical/overflow@0.18.0': + '@lexical/overflow@0.30.0': dependencies: - lexical: 0.18.0 + lexical: 0.30.0 - '@lexical/plain-text@0.18.0': + '@lexical/plain-text@0.30.0': dependencies: - '@lexical/clipboard': 0.18.0 - '@lexical/selection': 0.18.0 - '@lexical/utils': 0.18.0 - lexical: 0.18.0 + '@lexical/clipboard': 0.30.0 + '@lexical/selection': 0.30.0 + '@lexical/utils': 0.30.0 + lexical: 0.30.0 - '@lexical/react@0.18.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(yjs@13.6.24)': + '@lexical/react@0.30.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(yjs@13.6.24)': dependencies: - '@lexical/clipboard': 0.18.0 - '@lexical/code': 0.18.0 - '@lexical/devtools-core': 0.18.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0) - '@lexical/dragon': 0.18.0 - '@lexical/hashtag': 0.18.0 - '@lexical/history': 0.18.0 - '@lexical/link': 0.18.0 - '@lexical/list': 0.18.0 - '@lexical/mark': 0.18.0 - '@lexical/markdown': 0.18.0 - '@lexical/overflow': 0.18.0 - '@lexical/plain-text': 0.18.0 - '@lexical/rich-text': 0.18.0 - '@lexical/selection': 0.18.0 - '@lexical/table': 0.18.0 - '@lexical/text': 0.18.0 - '@lexical/utils': 0.18.0 - '@lexical/yjs': 0.18.0(yjs@13.6.24) - lexical: 0.18.0 + '@lexical/devtools-core': 0.30.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@lexical/dragon': 0.30.0 + '@lexical/hashtag': 0.30.0 + '@lexical/history': 0.30.0 + '@lexical/link': 0.30.0 + '@lexical/list': 0.30.0 + '@lexical/mark': 0.30.0 + '@lexical/markdown': 0.30.0 + '@lexical/overflow': 0.30.0 + '@lexical/plain-text': 0.30.0 + '@lexical/rich-text': 0.30.0 + '@lexical/table': 0.30.0 + '@lexical/text': 0.30.0 + '@lexical/utils': 0.30.0 + '@lexical/yjs': 0.30.0(yjs@13.6.24) + lexical: 0.30.0 react: 19.0.0 react-dom: 19.0.0(react@19.0.0) react-error-boundary: 3.1.4(react@19.0.0) transitivePeerDependencies: - yjs - '@lexical/rich-text@0.18.0': + '@lexical/rich-text@0.30.0': dependencies: - '@lexical/clipboard': 0.18.0 - '@lexical/selection': 0.18.0 - '@lexical/utils': 0.18.0 - lexical: 0.18.0 + '@lexical/clipboard': 0.30.0 + '@lexical/selection': 0.30.0 + '@lexical/utils': 0.30.0 + lexical: 0.30.0 - '@lexical/selection@0.18.0': + '@lexical/selection@0.30.0': dependencies: - lexical: 0.18.0 + lexical: 0.30.0 - '@lexical/table@0.18.0': + '@lexical/table@0.30.0': dependencies: - '@lexical/clipboard': 0.18.0 - '@lexical/utils': 0.18.0 - lexical: 0.18.0 + '@lexical/clipboard': 0.30.0 + '@lexical/utils': 0.30.0 + lexical: 0.30.0 - '@lexical/text@0.18.0': + '@lexical/text@0.30.0': dependencies: - lexical: 0.18.0 + lexical: 0.30.0 - '@lexical/utils@0.18.0': + '@lexical/utils@0.30.0': dependencies: - '@lexical/list': 0.18.0 - '@lexical/selection': 0.18.0 - '@lexical/table': 0.18.0 - lexical: 0.18.0 + '@lexical/list': 0.30.0 + '@lexical/selection': 0.30.0 + '@lexical/table': 0.30.0 + lexical: 0.30.0 - '@lexical/yjs@0.18.0(yjs@13.6.24)': + '@lexical/yjs@0.30.0(yjs@13.6.24)': dependencies: - '@lexical/offset': 0.18.0 - '@lexical/selection': 0.18.0 - lexical: 0.18.0 + '@lexical/offset': 0.30.0 + '@lexical/selection': 0.30.0 + lexical: 0.30.0 yjs: 13.6.24 '@mapbox/node-pre-gyp@1.0.11': @@ -10489,12 +10491,12 @@ snapshots: - supports-color optional: true - '@mdx-js/loader@3.1.0(acorn@8.14.1)(webpack@5.99.5(esbuild@0.24.2)(uglify-js@3.19.3))': + '@mdx-js/loader@3.1.0(acorn@8.14.1)(webpack@5.99.5(esbuild@0.25.0)(uglify-js@3.19.3))': dependencies: '@mdx-js/mdx': 3.1.0(acorn@8.14.1) source-map: 0.7.4 optionalDependencies: - webpack: 5.99.5(esbuild@0.24.2)(uglify-js@3.19.3) + webpack: 5.99.5(esbuild@0.25.0)(uglify-js@3.19.3) transitivePeerDependencies: - acorn - supports-color @@ -10569,11 +10571,11 @@ snapshots: dependencies: fast-glob: 3.3.1 - '@next/mdx@15.2.3(@mdx-js/loader@3.1.0(acorn@8.14.1)(webpack@5.99.5(esbuild@0.24.2)(uglify-js@3.19.3)))(@mdx-js/react@3.1.0(@types/react@18.2.79)(react@19.0.0))': + '@next/mdx@15.2.3(@mdx-js/loader@3.1.0(acorn@8.14.1)(webpack@5.99.5(esbuild@0.25.0)(uglify-js@3.19.3)))(@mdx-js/react@3.1.0(@types/react@18.2.79)(react@19.0.0))': dependencies: source-map: 0.7.4 optionalDependencies: - '@mdx-js/loader': 3.1.0(acorn@8.14.1)(webpack@5.99.5(esbuild@0.24.2)(uglify-js@3.19.3)) + '@mdx-js/loader': 3.1.0(acorn@8.14.1)(webpack@5.99.5(esbuild@0.25.0)(uglify-js@3.19.3)) '@mdx-js/react': 3.1.0(@types/react@18.2.79)(react@19.0.0) '@next/swc-darwin-arm64@15.2.3': @@ -10723,7 +10725,7 @@ snapshots: '@pkgr/core@0.2.2': {} - '@pmmmwh/react-refresh-webpack-plugin@0.5.16(react-refresh@0.14.2)(type-fest@4.39.1)(webpack-hot-middleware@2.26.1)(webpack@5.99.5(esbuild@0.24.2)(uglify-js@3.19.3))': + '@pmmmwh/react-refresh-webpack-plugin@0.5.16(react-refresh@0.14.2)(type-fest@4.39.1)(webpack-hot-middleware@2.26.1)(webpack@5.99.5(esbuild@0.25.0)(uglify-js@3.19.3))': dependencies: ansi-html: 0.0.9 core-js-pure: 3.41.0 @@ -10733,7 +10735,7 @@ snapshots: react-refresh: 0.14.2 schema-utils: 4.3.0 source-map: 0.7.4 - webpack: 5.99.5(esbuild@0.24.2)(uglify-js@3.19.3) + webpack: 5.99.5(esbuild@0.25.0)(uglify-js@3.19.3) optionalDependencies: type-fest: 4.39.1 webpack-hot-middleware: 2.26.1 @@ -11111,7 +11113,7 @@ snapshots: react: 19.0.0 react-dom: 19.0.0(react@19.0.0) - '@storybook/builder-webpack5@8.5.0(esbuild@0.24.2)(storybook@8.5.0)(typescript@4.9.5)(uglify-js@3.19.3)': + '@storybook/builder-webpack5@8.5.0(esbuild@0.25.0)(storybook@8.5.0)(typescript@4.9.5)(uglify-js@3.19.3)': dependencies: '@storybook/core-webpack': 8.5.0(storybook@8.5.0) '@types/semver': 7.7.0 @@ -11119,23 +11121,23 @@ snapshots: case-sensitive-paths-webpack-plugin: 2.4.0 cjs-module-lexer: 1.4.3 constants-browserify: 1.0.0 - css-loader: 6.11.0(webpack@5.99.5(esbuild@0.24.2)(uglify-js@3.19.3)) + css-loader: 6.11.0(webpack@5.99.5(esbuild@0.25.0)(uglify-js@3.19.3)) es-module-lexer: 1.6.0 - fork-ts-checker-webpack-plugin: 8.0.0(typescript@4.9.5)(webpack@5.99.5(esbuild@0.24.2)(uglify-js@3.19.3)) - html-webpack-plugin: 5.6.3(webpack@5.99.5(esbuild@0.24.2)(uglify-js@3.19.3)) + fork-ts-checker-webpack-plugin: 8.0.0(typescript@4.9.5)(webpack@5.99.5(esbuild@0.25.0)(uglify-js@3.19.3)) + html-webpack-plugin: 5.6.3(webpack@5.99.5(esbuild@0.25.0)(uglify-js@3.19.3)) magic-string: 0.30.17 path-browserify: 1.0.1 process: 0.11.10 semver: 7.7.1 storybook: 8.5.0 - style-loader: 3.3.4(webpack@5.99.5(esbuild@0.24.2)(uglify-js@3.19.3)) - terser-webpack-plugin: 5.3.14(esbuild@0.24.2)(uglify-js@3.19.3)(webpack@5.99.5(esbuild@0.24.2)(uglify-js@3.19.3)) + style-loader: 3.3.4(webpack@5.99.5(esbuild@0.25.0)(uglify-js@3.19.3)) + terser-webpack-plugin: 5.3.14(esbuild@0.25.0)(uglify-js@3.19.3)(webpack@5.99.5(esbuild@0.25.0)(uglify-js@3.19.3)) ts-dedent: 2.2.0 url: 0.11.4 util: 0.12.5 util-deprecate: 1.0.2 - webpack: 5.99.5(esbuild@0.24.2)(uglify-js@3.19.3) - webpack-dev-middleware: 6.1.3(webpack@5.99.5(esbuild@0.24.2)(uglify-js@3.19.3)) + webpack: 5.99.5(esbuild@0.25.0)(uglify-js@3.19.3) + webpack-dev-middleware: 6.1.3(webpack@5.99.5(esbuild@0.25.0)(uglify-js@3.19.3)) webpack-hot-middleware: 2.26.1 webpack-virtual-modules: 0.6.2 optionalDependencies: @@ -11161,8 +11163,8 @@ snapshots: '@storybook/csf': 0.1.12 better-opn: 3.0.2 browser-assert: 1.2.1 - esbuild: 0.24.2 - esbuild-register: 3.6.0(esbuild@0.24.2) + esbuild: 0.25.0 + esbuild-register: 3.6.0(esbuild@0.25.0) jsdoc-type-pratt-parser: 4.1.0 process: 0.11.10 recast: 0.23.11 @@ -11209,7 +11211,7 @@ snapshots: dependencies: storybook: 8.5.0 - '@storybook/nextjs@8.5.0(esbuild@0.24.2)(next@15.2.3(@babel/core@7.26.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(sass@1.86.3))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(sass@1.86.3)(storybook@8.5.0)(type-fest@4.39.1)(typescript@4.9.5)(uglify-js@3.19.3)(webpack-hot-middleware@2.26.1)(webpack@5.99.5(esbuild@0.24.2)(uglify-js@3.19.3))': + '@storybook/nextjs@8.5.0(esbuild@0.25.0)(next@15.2.3(@babel/core@7.26.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(sass@1.86.3))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(sass@1.86.3)(storybook@8.5.0)(type-fest@4.39.1)(typescript@4.9.5)(uglify-js@3.19.3)(webpack-hot-middleware@2.26.1)(webpack@5.99.5(esbuild@0.25.0)(uglify-js@3.19.3))': dependencies: '@babel/core': 7.26.10 '@babel/plugin-syntax-bigint': 7.8.3(@babel/core@7.26.10) @@ -11224,30 +11226,30 @@ snapshots: '@babel/preset-react': 7.26.3(@babel/core@7.26.10) '@babel/preset-typescript': 7.27.0(@babel/core@7.26.10) '@babel/runtime': 7.27.0 - '@pmmmwh/react-refresh-webpack-plugin': 0.5.16(react-refresh@0.14.2)(type-fest@4.39.1)(webpack-hot-middleware@2.26.1)(webpack@5.99.5(esbuild@0.24.2)(uglify-js@3.19.3)) - '@storybook/builder-webpack5': 8.5.0(esbuild@0.24.2)(storybook@8.5.0)(typescript@4.9.5)(uglify-js@3.19.3) - '@storybook/preset-react-webpack': 8.5.0(@storybook/test@8.5.0(storybook@8.5.0))(esbuild@0.24.2)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(storybook@8.5.0)(typescript@4.9.5)(uglify-js@3.19.3) + '@pmmmwh/react-refresh-webpack-plugin': 0.5.16(react-refresh@0.14.2)(type-fest@4.39.1)(webpack-hot-middleware@2.26.1)(webpack@5.99.5(esbuild@0.25.0)(uglify-js@3.19.3)) + '@storybook/builder-webpack5': 8.5.0(esbuild@0.25.0)(storybook@8.5.0)(typescript@4.9.5)(uglify-js@3.19.3) + '@storybook/preset-react-webpack': 8.5.0(@storybook/test@8.5.0(storybook@8.5.0))(esbuild@0.25.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(storybook@8.5.0)(typescript@4.9.5)(uglify-js@3.19.3) '@storybook/react': 8.5.0(@storybook/test@8.5.0(storybook@8.5.0))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(storybook@8.5.0)(typescript@4.9.5) '@storybook/test': 8.5.0(storybook@8.5.0) '@types/semver': 7.7.0 - babel-loader: 9.2.1(@babel/core@7.26.10)(webpack@5.99.5(esbuild@0.24.2)(uglify-js@3.19.3)) - css-loader: 6.11.0(webpack@5.99.5(esbuild@0.24.2)(uglify-js@3.19.3)) + babel-loader: 9.2.1(@babel/core@7.26.10)(webpack@5.99.5(esbuild@0.25.0)(uglify-js@3.19.3)) + css-loader: 6.11.0(webpack@5.99.5(esbuild@0.25.0)(uglify-js@3.19.3)) find-up: 5.0.0 image-size: 1.2.1 loader-utils: 3.3.1 next: 15.2.3(@babel/core@7.26.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(sass@1.86.3) - node-polyfill-webpack-plugin: 2.0.1(webpack@5.99.5(esbuild@0.24.2)(uglify-js@3.19.3)) + node-polyfill-webpack-plugin: 2.0.1(webpack@5.99.5(esbuild@0.25.0)(uglify-js@3.19.3)) pnp-webpack-plugin: 1.7.0(typescript@4.9.5) postcss: 8.5.3 - postcss-loader: 8.1.1(postcss@8.5.3)(typescript@4.9.5)(webpack@5.99.5(esbuild@0.24.2)(uglify-js@3.19.3)) + postcss-loader: 8.1.1(postcss@8.5.3)(typescript@4.9.5)(webpack@5.99.5(esbuild@0.25.0)(uglify-js@3.19.3)) react: 19.0.0 react-dom: 19.0.0(react@19.0.0) react-refresh: 0.14.2 resolve-url-loader: 5.0.0 - sass-loader: 14.2.1(sass@1.86.3)(webpack@5.99.5(esbuild@0.24.2)(uglify-js@3.19.3)) + sass-loader: 14.2.1(sass@1.86.3)(webpack@5.99.5(esbuild@0.25.0)(uglify-js@3.19.3)) semver: 7.7.1 storybook: 8.5.0 - style-loader: 3.3.4(webpack@5.99.5(esbuild@0.24.2)(uglify-js@3.19.3)) + style-loader: 3.3.4(webpack@5.99.5(esbuild@0.25.0)(uglify-js@3.19.3)) styled-jsx: 5.1.6(@babel/core@7.26.10)(react@19.0.0) ts-dedent: 2.2.0 tsconfig-paths: 4.2.0 @@ -11255,7 +11257,7 @@ snapshots: optionalDependencies: sharp: 0.33.5 typescript: 4.9.5 - webpack: 5.99.5(esbuild@0.24.2)(uglify-js@3.19.3) + webpack: 5.99.5(esbuild@0.25.0)(uglify-js@3.19.3) transitivePeerDependencies: - '@rspack/core' - '@swc/core' @@ -11274,11 +11276,11 @@ snapshots: - webpack-hot-middleware - webpack-plugin-serve - '@storybook/preset-react-webpack@8.5.0(@storybook/test@8.5.0(storybook@8.5.0))(esbuild@0.24.2)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(storybook@8.5.0)(typescript@4.9.5)(uglify-js@3.19.3)': + '@storybook/preset-react-webpack@8.5.0(@storybook/test@8.5.0(storybook@8.5.0))(esbuild@0.25.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(storybook@8.5.0)(typescript@4.9.5)(uglify-js@3.19.3)': dependencies: '@storybook/core-webpack': 8.5.0(storybook@8.5.0) '@storybook/react': 8.5.0(@storybook/test@8.5.0(storybook@8.5.0))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(storybook@8.5.0)(typescript@4.9.5) - '@storybook/react-docgen-typescript-plugin': 1.0.6--canary.9.0c3f3b7.0(typescript@4.9.5)(webpack@5.99.5(esbuild@0.24.2)(uglify-js@3.19.3)) + '@storybook/react-docgen-typescript-plugin': 1.0.6--canary.9.0c3f3b7.0(typescript@4.9.5)(webpack@5.99.5(esbuild@0.25.0)(uglify-js@3.19.3)) '@types/semver': 7.7.0 find-up: 5.0.0 magic-string: 0.30.17 @@ -11289,7 +11291,7 @@ snapshots: semver: 7.7.1 storybook: 8.5.0 tsconfig-paths: 4.2.0 - webpack: 5.99.5(esbuild@0.24.2)(uglify-js@3.19.3) + webpack: 5.99.5(esbuild@0.25.0)(uglify-js@3.19.3) optionalDependencies: typescript: 4.9.5 transitivePeerDependencies: @@ -11304,7 +11306,7 @@ snapshots: dependencies: storybook: 8.5.0 - '@storybook/react-docgen-typescript-plugin@1.0.6--canary.9.0c3f3b7.0(typescript@4.9.5)(webpack@5.99.5(esbuild@0.24.2)(uglify-js@3.19.3))': + '@storybook/react-docgen-typescript-plugin@1.0.6--canary.9.0c3f3b7.0(typescript@4.9.5)(webpack@5.99.5(esbuild@0.25.0)(uglify-js@3.19.3))': dependencies: debug: 4.4.0 endent: 2.1.0 @@ -11314,7 +11316,7 @@ snapshots: react-docgen-typescript: 2.2.2(typescript@4.9.5) tslib: 2.8.1 typescript: 4.9.5 - webpack: 5.99.5(esbuild@0.24.2)(uglify-js@3.19.3) + webpack: 5.99.5(esbuild@0.25.0)(uglify-js@3.19.3) transitivePeerDependencies: - supports-color @@ -12385,12 +12387,12 @@ snapshots: transitivePeerDependencies: - supports-color - babel-loader@9.2.1(@babel/core@7.26.10)(webpack@5.99.5(esbuild@0.24.2)(uglify-js@3.19.3)): + babel-loader@9.2.1(@babel/core@7.26.10)(webpack@5.99.5(esbuild@0.25.0)(uglify-js@3.19.3)): dependencies: '@babel/core': 7.26.10 find-cache-dir: 4.0.0 schema-utils: 4.3.0 - webpack: 5.99.5(esbuild@0.24.2)(uglify-js@3.19.3) + webpack: 5.99.5(esbuild@0.25.0)(uglify-js@3.19.3) babel-plugin-istanbul@6.1.1: dependencies: @@ -12964,7 +12966,7 @@ snapshots: crypto-js@4.2.0: {} - css-loader@6.11.0(webpack@5.99.5(esbuild@0.24.2)(uglify-js@3.19.3)): + css-loader@6.11.0(webpack@5.99.5(esbuild@0.25.0)(uglify-js@3.19.3)): dependencies: icss-utils: 5.1.0(postcss@8.5.3) postcss: 8.5.3 @@ -12975,7 +12977,7 @@ snapshots: postcss-value-parser: 4.2.0 semver: 7.7.1 optionalDependencies: - webpack: 5.99.5(esbuild@0.24.2)(uglify-js@3.19.3) + webpack: 5.99.5(esbuild@0.25.0)(uglify-js@3.19.3) css-select@4.3.0: dependencies: @@ -13531,40 +13533,40 @@ snapshots: transitivePeerDependencies: - supports-color - esbuild-register@3.6.0(esbuild@0.24.2): + esbuild-register@3.6.0(esbuild@0.25.0): dependencies: debug: 4.4.0 - esbuild: 0.24.2 + esbuild: 0.25.0 transitivePeerDependencies: - supports-color - esbuild@0.24.2: + esbuild@0.25.0: optionalDependencies: - '@esbuild/aix-ppc64': 0.24.2 - '@esbuild/android-arm': 0.24.2 - '@esbuild/android-arm64': 0.24.2 - '@esbuild/android-x64': 0.24.2 - '@esbuild/darwin-arm64': 0.24.2 - '@esbuild/darwin-x64': 0.24.2 - '@esbuild/freebsd-arm64': 0.24.2 - '@esbuild/freebsd-x64': 0.24.2 - '@esbuild/linux-arm': 0.24.2 - '@esbuild/linux-arm64': 0.24.2 - '@esbuild/linux-ia32': 0.24.2 - '@esbuild/linux-loong64': 0.24.2 - '@esbuild/linux-mips64el': 0.24.2 - '@esbuild/linux-ppc64': 0.24.2 - '@esbuild/linux-riscv64': 0.24.2 - '@esbuild/linux-s390x': 0.24.2 - '@esbuild/linux-x64': 0.24.2 - '@esbuild/netbsd-arm64': 0.24.2 - '@esbuild/netbsd-x64': 0.24.2 - '@esbuild/openbsd-arm64': 0.24.2 - '@esbuild/openbsd-x64': 0.24.2 - '@esbuild/sunos-x64': 0.24.2 - '@esbuild/win32-arm64': 0.24.2 - '@esbuild/win32-ia32': 0.24.2 - '@esbuild/win32-x64': 0.24.2 + '@esbuild/aix-ppc64': 0.25.0 + '@esbuild/android-arm': 0.25.0 + '@esbuild/android-arm64': 0.25.0 + '@esbuild/android-x64': 0.25.0 + '@esbuild/darwin-arm64': 0.25.0 + '@esbuild/darwin-x64': 0.25.0 + '@esbuild/freebsd-arm64': 0.25.0 + '@esbuild/freebsd-x64': 0.25.0 + '@esbuild/linux-arm': 0.25.0 + '@esbuild/linux-arm64': 0.25.0 + '@esbuild/linux-ia32': 0.25.0 + '@esbuild/linux-loong64': 0.25.0 + '@esbuild/linux-mips64el': 0.25.0 + '@esbuild/linux-ppc64': 0.25.0 + '@esbuild/linux-riscv64': 0.25.0 + '@esbuild/linux-s390x': 0.25.0 + '@esbuild/linux-x64': 0.25.0 + '@esbuild/netbsd-arm64': 0.25.0 + '@esbuild/netbsd-x64': 0.25.0 + '@esbuild/openbsd-arm64': 0.25.0 + '@esbuild/openbsd-x64': 0.25.0 + '@esbuild/sunos-x64': 0.25.0 + '@esbuild/win32-arm64': 0.25.0 + '@esbuild/win32-ia32': 0.25.0 + '@esbuild/win32-x64': 0.25.0 esbuild@0.25.2: optionalDependencies: @@ -14375,7 +14377,7 @@ snapshots: cross-spawn: 7.0.6 signal-exit: 4.1.0 - fork-ts-checker-webpack-plugin@8.0.0(typescript@4.9.5)(webpack@5.99.5(esbuild@0.24.2)(uglify-js@3.19.3)): + fork-ts-checker-webpack-plugin@8.0.0(typescript@4.9.5)(webpack@5.99.5(esbuild@0.25.0)(uglify-js@3.19.3)): dependencies: '@babel/code-frame': 7.26.2 chalk: 4.1.2 @@ -14390,7 +14392,7 @@ snapshots: semver: 7.7.1 tapable: 2.2.1 typescript: 4.9.5 - webpack: 5.99.5(esbuild@0.24.2)(uglify-js@3.19.3) + webpack: 5.99.5(esbuild@0.25.0)(uglify-js@3.19.3) format@0.2.2: {} @@ -14770,7 +14772,7 @@ snapshots: html-void-elements@3.0.0: {} - html-webpack-plugin@5.6.3(webpack@5.99.5(esbuild@0.24.2)(uglify-js@3.19.3)): + html-webpack-plugin@5.6.3(webpack@5.99.5(esbuild@0.25.0)(uglify-js@3.19.3)): dependencies: '@types/html-minifier-terser': 6.1.0 html-minifier-terser: 6.1.0 @@ -14778,7 +14780,7 @@ snapshots: pretty-error: 4.0.0 tapable: 2.2.1 optionalDependencies: - webpack: 5.99.5(esbuild@0.24.2)(uglify-js@3.19.3) + webpack: 5.99.5(esbuild@0.25.0)(uglify-js@3.19.3) htmlparser2@6.1.0: dependencies: @@ -15554,7 +15556,7 @@ snapshots: prelude-ls: 1.2.1 type-check: 0.4.0 - lexical@0.18.0: {} + lexical@0.30.0: {} lib0@0.2.102: dependencies: @@ -16348,7 +16350,7 @@ snapshots: node-int64@0.4.0: {} - node-polyfill-webpack-plugin@2.0.1(webpack@5.99.5(esbuild@0.24.2)(uglify-js@3.19.3)): + node-polyfill-webpack-plugin@2.0.1(webpack@5.99.5(esbuild@0.25.0)(uglify-js@3.19.3)): dependencies: assert: 2.1.0 browserify-zlib: 0.2.0 @@ -16375,7 +16377,7 @@ snapshots: url: 0.11.4 util: 0.12.5 vm-browserify: 1.1.2 - webpack: 5.99.5(esbuild@0.24.2)(uglify-js@3.19.3) + webpack: 5.99.5(esbuild@0.25.0)(uglify-js@3.19.3) node-releases@2.0.19: {} @@ -16744,14 +16746,14 @@ snapshots: postcss: 8.5.3 ts-node: 10.9.2(@types/node@18.15.0)(typescript@4.9.5) - postcss-loader@8.1.1(postcss@8.5.3)(typescript@4.9.5)(webpack@5.99.5(esbuild@0.24.2)(uglify-js@3.19.3)): + postcss-loader@8.1.1(postcss@8.5.3)(typescript@4.9.5)(webpack@5.99.5(esbuild@0.25.0)(uglify-js@3.19.3)): dependencies: cosmiconfig: 9.0.0(typescript@4.9.5) jiti: 1.21.7 postcss: 8.5.3 semver: 7.7.1 optionalDependencies: - webpack: 5.99.5(esbuild@0.24.2)(uglify-js@3.19.3) + webpack: 5.99.5(esbuild@0.25.0)(uglify-js@3.19.3) transitivePeerDependencies: - typescript @@ -17510,12 +17512,12 @@ snapshots: safer-buffer@2.1.2: {} - sass-loader@14.2.1(sass@1.86.3)(webpack@5.99.5(esbuild@0.24.2)(uglify-js@3.19.3)): + sass-loader@14.2.1(sass@1.86.3)(webpack@5.99.5(esbuild@0.25.0)(uglify-js@3.19.3)): dependencies: neo-async: 2.6.2 optionalDependencies: sass: 1.86.3 - webpack: 5.99.5(esbuild@0.24.2)(uglify-js@3.19.3) + webpack: 5.99.5(esbuild@0.25.0)(uglify-js@3.19.3) sass@1.86.3: dependencies: @@ -17877,9 +17879,9 @@ snapshots: strip-json-comments@3.1.1: {} - style-loader@3.3.4(webpack@5.99.5(esbuild@0.24.2)(uglify-js@3.19.3)): + style-loader@3.3.4(webpack@5.99.5(esbuild@0.25.0)(uglify-js@3.19.3)): dependencies: - webpack: 5.99.5(esbuild@0.24.2)(uglify-js@3.19.3) + webpack: 5.99.5(esbuild@0.25.0)(uglify-js@3.19.3) style-to-js@1.1.16: dependencies: @@ -17977,16 +17979,16 @@ snapshots: yallist: 4.0.0 optional: true - terser-webpack-plugin@5.3.14(esbuild@0.24.2)(uglify-js@3.19.3)(webpack@5.99.5(esbuild@0.24.2)(uglify-js@3.19.3)): + terser-webpack-plugin@5.3.14(esbuild@0.25.0)(uglify-js@3.19.3)(webpack@5.99.5(esbuild@0.25.0)(uglify-js@3.19.3)): dependencies: '@jridgewell/trace-mapping': 0.3.25 jest-worker: 27.5.1 schema-utils: 4.3.0 serialize-javascript: 6.0.2 terser: 5.39.0 - webpack: 5.99.5(esbuild@0.24.2)(uglify-js@3.19.3) + webpack: 5.99.5(esbuild@0.25.0)(uglify-js@3.19.3) optionalDependencies: - esbuild: 0.24.2 + esbuild: 0.25.0 uglify-js: 3.19.3 terser@5.39.0: @@ -18511,7 +18513,7 @@ snapshots: transitivePeerDependencies: - supports-color - webpack-dev-middleware@6.1.3(webpack@5.99.5(esbuild@0.24.2)(uglify-js@3.19.3)): + webpack-dev-middleware@6.1.3(webpack@5.99.5(esbuild@0.25.0)(uglify-js@3.19.3)): dependencies: colorette: 2.0.20 memfs: 3.5.3 @@ -18519,7 +18521,7 @@ snapshots: range-parser: 1.2.1 schema-utils: 4.3.0 optionalDependencies: - webpack: 5.99.5(esbuild@0.24.2)(uglify-js@3.19.3) + webpack: 5.99.5(esbuild@0.25.0)(uglify-js@3.19.3) webpack-hot-middleware@2.26.1: dependencies: @@ -18531,7 +18533,7 @@ snapshots: webpack-virtual-modules@0.6.2: {} - webpack@5.99.5(esbuild@0.24.2)(uglify-js@3.19.3): + webpack@5.99.5(esbuild@0.25.0)(uglify-js@3.19.3): dependencies: '@types/eslint-scope': 3.7.7 '@types/estree': 1.0.7 @@ -18553,7 +18555,7 @@ snapshots: neo-async: 2.6.2 schema-utils: 4.3.0 tapable: 2.2.1 - terser-webpack-plugin: 5.3.14(esbuild@0.24.2)(uglify-js@3.19.3)(webpack@5.99.5(esbuild@0.24.2)(uglify-js@3.19.3)) + terser-webpack-plugin: 5.3.14(esbuild@0.25.0)(uglify-js@3.19.3)(webpack@5.99.5(esbuild@0.25.0)(uglify-js@3.19.3)) watchpack: 2.4.2 webpack-sources: 3.2.3 transitivePeerDependencies: diff --git a/web/public/embed.js b/web/public/embed.js index 8f13a7178c..1efa541a88 100644 --- a/web/public/embed.js +++ b/web/public/embed.js @@ -12,6 +12,7 @@ const buttonId = "dify-chatbot-bubble-button"; const iframeId = "dify-chatbot-bubble-window"; const config = window[configKey]; + let isExpanded = false; // SVG icons for open and close states const svgIcons = `<svg id="openIcon" width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> @@ -22,10 +23,57 @@ </svg> `; + + const originalIframeStyleText = ` + position: absolute; + display: flex; + flex-direction: column; + justify-content: space-between; + top: unset; + right: var(--${buttonId}-right, 1rem); /* Align with dify-chatbot-bubble-button. */ + bottom: var(--${buttonId}-bottom, 1rem); /* Align with dify-chatbot-bubble-button. */ + left: unset; + width: 24rem; + max-width: calc(100vw - 2rem); + height: 43.75rem; + max-height: calc(100vh - 6rem); + border: none; + z-index: 2147483640; + overflow: hidden; + user-select: none; + transition-property: width, height; + transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); + transition-duration: 150ms; + ` + + const expandedIframeStyleText = ` + position: absolute; + display: flex; + flex-direction: column; + justify-content: space-between; + top: unset; + right: var(--${buttonId}-right, 1rem); /* Align with dify-chatbot-bubble-button. */ + bottom: var(--${buttonId}-bottom, 1rem); /* Align with dify-chatbot-bubble-button. */ + left: unset; + min-width: 24rem; + width: 48%; + max-width: 40rem; /* Match mobile breakpoint*/ + min-height: 43.75rem; + height: 88%; + max-height: calc(100vh - 6rem); + border: none; + z-index: 2147483640; + overflow: hidden; + user-select: none; + transition-property: width, height; + transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); + transition-duration: 150ms; + ` + // Main function to embed the chatbot async function embedChatbot() { let isDragging = false - + if (!config || !config.token) { console.error(`${configKey} is empty or token is not provided`); return; @@ -71,6 +119,7 @@ const baseUrl = config.baseUrl || `https://${config.isDev ? "dev." : ""}udify.app`; + const targetOrigin = new URL(baseUrl).origin; // pre-check the length of the URL const iframeUrl = `${baseUrl}/chatbot/${config.token}?${params}`; @@ -81,7 +130,7 @@ // 3) APPEND it to the document body right away: document.body.appendChild(preloadedIframe); // ─── End Fix Snippet - if(iframeUrl.length > 2048) { + if (iframeUrl.length > 2048) { console.error("The URL is too long, please reduce the number of inputs to prevent the bot from failing to load"); } @@ -92,23 +141,7 @@ iframe.title = "dify chatbot bubble window"; iframe.id = iframeId; iframe.src = iframeUrl; - iframe.style.cssText = ` - position: absolute; - display: flex; - flex-direction: column; - justify-content: space-between; - left: unset; - right: 0; - bottom: 0; - width: 24rem; - max-width: calc(100vw - 2rem); - height: 43.75rem; - max-height: calc(100vh - 6rem); - border: none; - z-index: 2147483640; - overflow: hidden; - user-select: none; - `; + iframe.style.cssText = originalIframeStyleText; return iframe; } @@ -121,29 +154,70 @@ const targetButton = document.getElementById(buttonId); if (targetIframe && targetButton) { const buttonRect = targetButton.getBoundingClientRect(); + // We don't necessarily need iframeRect anymore with the center logic - const buttonInBottom = buttonRect.top - 5 > targetIframe.clientHeight; + const viewportCenterY = window.innerHeight / 2; + const buttonCenterY = buttonRect.top + buttonRect.height / 2; - if (buttonInBottom) { - targetIframe.style.bottom = "0px"; - targetIframe.style.top = "unset"; + if (buttonCenterY < viewportCenterY) { + targetIframe.style.top = `var(--${buttonId}-bottom, 1rem)`; + targetIframe.style.bottom = 'unset'; } else { - targetIframe.style.bottom = "unset"; - targetIframe.style.top = "0px"; + targetIframe.style.bottom = `var(--${buttonId}-bottom, 1rem)`; + targetIframe.style.top = 'unset'; } - const buttonInRight = buttonRect.right > targetIframe.clientWidth; + const viewportCenterX = window.innerWidth / 2; + const buttonCenterX = buttonRect.left + buttonRect.width / 2; - if (buttonInRight) { - targetIframe.style.right = "0"; - targetIframe.style.left = "unset"; + if (buttonCenterX < viewportCenterX) { + targetIframe.style.left = `var(--${buttonId}-right, 1rem)`; + targetIframe.style.right = 'unset'; } else { - targetIframe.style.right = "unset"; - targetIframe.style.left = 0; + targetIframe.style.right = `var(--${buttonId}-right, 1rem)`; + targetIframe.style.left = 'unset'; } } } + function toggleExpand() { + isExpanded = !isExpanded; + + const targetIframe = document.getElementById(iframeId); + if (!targetIframe) return; + + if (isExpanded) { + targetIframe.style.cssText = expandedIframeStyleText; + } else { + targetIframe.style.cssText = originalIframeStyleText; + } + resetIframePosition(); + } + + window.addEventListener('message', (event) => { + if (event.origin !== targetOrigin) return; + + const targetIframe = document.getElementById(iframeId); + if (!targetIframe || event.source !== targetIframe.contentWindow) return; + + if (event.data.type === 'dify-chatbot-iframe-ready') { + targetIframe.contentWindow?.postMessage( + { + type: 'dify-chatbot-config', + payload: { + isToggledByButton: true, + isDraggable: !!config.draggable, + }, + }, + targetOrigin + ); + } + + if (event.data.type === 'dify-chatbot-expand-change') { + toggleExpand(); + } + }); + // Function to create the chat button function createButton() { const containerDiv = document.createElement("div"); @@ -200,7 +274,10 @@ // Add click event listener to toggle chatbot containerDiv.addEventListener("click", handleClick); // Add touch event listener - containerDiv.addEventListener("touchend", handleClick); + containerDiv.addEventListener("touchend", (event) => { + event.preventDefault(); + handleClick(); + }, { passive: false }); function handleClick() { if (isDragging) return; @@ -252,6 +329,8 @@ } else { startX = e.clientX - element.offsetLeft; startY = e.clientY - element.offsetTop; + startClientX = e.clientX; + startClientY = e.clientY; } document.addEventListener("mousemove", drag); document.addEventListener("touchmove", drag, { passive: false }); @@ -264,7 +343,7 @@ const touch = e.type === "touchmove" ? e.touches[0] : e; const deltaX = touch.clientX - startClientX; const deltaY = touch.clientY - startClientY; - + // Determine whether it is a drag operation if (Math.abs(deltaX) > 8 || Math.abs(deltaY) > 8) { isDragging = true; diff --git a/web/public/embed.min.js b/web/public/embed.min.js index 13837183c0..b2781ee47d 100644 --- a/web/public/embed.min.js +++ b/web/public/embed.min.js @@ -1,20 +1,24 @@ -(()=>{let t="difyChatbotConfig",m="dify-chatbot-bubble-button",h="dify-chatbot-bubble-window",p=window[t];async function e(){let u=!1;if(p&&p.token){var e=new URLSearchParams({...await(async()=>{var e=p?.inputs||{};let n={};return await Promise.all(Object.entries(e).map(async([e,t])=>{n[e]=await o(t)})),n})(),...await(async()=>{var e=p?.systemVariables||{};let n={};return await Promise.all(Object.entries(e).map(async([e,t])=>{n["sys."+e]=await o(t)})),n})()});let t=`${p.baseUrl||`https://${p.isDev?"dev.":""}udify.app`}/chatbot/${p.token}?`+e;e=s();async function o(e){e=(new TextEncoder).encode(e),e=new Response(new Blob([e]).stream().pipeThrough(new CompressionStream("gzip"))).arrayBuffer(),e=new Uint8Array(await e);return btoa(String.fromCharCode(...e))}function s(){var e=document.createElement("iframe");return e.allow="fullscreen;microphone",e.title="dify chatbot bubble window",e.id=h,e.src=t,e.style.cssText=` - position: absolute; - display: flex; - flex-direction: column; - justify-content: space-between; - left: unset; - right: 0; - bottom: 0; - width: 24rem; - max-width: calc(100vw - 2rem); - height: 43.75rem; - max-height: calc(100vh - 6rem); - border: none; - z-index: 2147483640; - overflow: hidden; - user-select: none; - `,e}function d(){var e,t;window.innerWidth<=640||(e=document.getElementById(h),t=document.getElementById(m),e&&t&&((t=t.getBoundingClientRect()).top-5>e.clientHeight?(e.style.bottom="0px",e.style.top="unset"):(e.style.bottom="unset",e.style.top="0px"),t.right>e.clientWidth?(e.style.right="0",e.style.left="unset"):(e.style.right="unset",e.style.left=0)))}function n(){let n=document.createElement("div");Object.entries(p.containerProps||{}).forEach(([e,t])=>{"className"===e?n.classList.add(...t.split(" ")):"style"===e?"object"==typeof t?Object.assign(n.style,t):n.style.cssText=t:"function"==typeof t?n.addEventListener(e.replace(/^on/,"").toLowerCase(),t):n[e]=t}),n.id=m;var e=document.createElement("style"),e=(document.head.appendChild(e),e.sheet.insertRule(` +(()=>{let t="difyChatbotConfig",h="dify-chatbot-bubble-button",m="dify-chatbot-bubble-window",y=window[t],a=!1,l=` + position: absolute; + display: flex; + flex-direction: column; + justify-content: space-between; + top: unset; + right: var(--${h}-right, 1rem); /* Align with dify-chatbot-bubble-button. */ + bottom: var(--${h}-bottom, 1rem); /* Align with dify-chatbot-bubble-button. */ + left: unset; + width: 24rem; + max-width: calc(100vw - 2rem); + height: 43.75rem; + max-height: calc(100vh - 6rem); + border: none; + z-index: 2147483640; + overflow: hidden; + user-select: none; + transition-property: width, height; + transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); + transition-duration: 150ms; + `;async function e(){let u=!1;if(y&&y.token){var e=new URLSearchParams({...await(async()=>{var e=y?.inputs||{};let n={};return await Promise.all(Object.entries(e).map(async([e,t])=>{n[e]=await i(t)})),n})(),...await(async()=>{var e=y?.systemVariables||{};let n={};return await Promise.all(Object.entries(e).map(async([e,t])=>{n["sys."+e]=await i(t)})),n})()}),n=y.baseUrl||`https://${y.isDev?"dev.":""}udify.app`;let o=new URL(n).origin,t=`${n}/chatbot/${y.token}?`+e;n=s();async function i(e){e=(new TextEncoder).encode(e),e=new Response(new Blob([e]).stream().pipeThrough(new CompressionStream("gzip"))).arrayBuffer(),e=new Uint8Array(await e);return btoa(String.fromCharCode(...e))}function s(){var e=document.createElement("iframe");return e.allow="fullscreen;microphone",e.title="dify chatbot bubble window",e.id=m,e.src=t,e.style.cssText=l,e}function d(){var e,t,n;window.innerWidth<=640||(e=document.getElementById(m),t=document.getElementById(h),e&&t&&(t=t.getBoundingClientRect(),n=window.innerHeight/2,t.top+t.height/2<n?(e.style.top=`var(--${h}-bottom, 1rem)`,e.style.bottom="unset"):(e.style.bottom=`var(--${h}-bottom, 1rem)`,e.style.top="unset"),t.left+t.width/2<window.innerWidth/2?(e.style.left=`var(--${h}-right, 1rem)`,e.style.right="unset"):(e.style.right=`var(--${h}-right, 1rem)`,e.style.left="unset")))}function r(){let n=document.createElement("div");Object.entries(y.containerProps||{}).forEach(([e,t])=>{"className"===e?n.classList.add(...t.split(" ")):"style"===e?"object"==typeof t?Object.assign(n.style,t):n.style.cssText=t:"function"==typeof t?n.addEventListener(e.replace(/^on/,"").toLowerCase(),t):n[e]=t}),n.id=h;var e=document.createElement("style"),e=(document.head.appendChild(e),e.sheet.insertRule(` #${n.id} { position: fixed; bottom: var(--${n.id}-bottom, 1rem); @@ -29,10 +33,10 @@ cursor: pointer; z-index: 2147483647; } - `),document.createElement("div"));function t(){var e;u||((e=document.getElementById(h))?(e.style.display="none"===e.style.display?"block":"none","none"===e.style.display?y("open"):y("close"),"none"===e.style.display?document.removeEventListener("keydown",l):document.addEventListener("keydown",l),d()):(n.appendChild(s()),d(),this.title="Exit (ESC)",y("close"),document.addEventListener("keydown",l)))}if(e.style.cssText="position: relative; display: flex; align-items: center; justify-content: center; width: 100%; height: 100%; z-index: 2147483647;",e.innerHTML=`<svg id="openIcon" width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> + `),document.createElement("div"));function t(){var e;u||((e=document.getElementById(m))?(e.style.display="none"===e.style.display?"block":"none","none"===e.style.display?p("open"):p("close"),"none"===e.style.display?document.removeEventListener("keydown",b):document.addEventListener("keydown",b),d()):(n.appendChild(s()),d(),this.title="Exit (ESC)",p("close"),document.addEventListener("keydown",b)))}if(e.style.cssText="position: relative; display: flex; align-items: center; justify-content: center; width: 100%; height: 100%; z-index: 2147483647;",e.innerHTML=`<svg id="openIcon" width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> <path fill-rule="evenodd" clip-rule="evenodd" d="M7.7586 2L16.2412 2C17.0462 1.99999 17.7105 1.99998 18.2517 2.04419C18.8138 2.09012 19.3305 2.18868 19.8159 2.43598C20.5685 2.81947 21.1804 3.43139 21.5639 4.18404C21.8112 4.66937 21.9098 5.18608 21.9557 5.74818C21.9999 6.28937 21.9999 6.95373 21.9999 7.7587L22 14.1376C22.0004 14.933 22.0007 15.5236 21.8636 16.0353C21.4937 17.4156 20.4155 18.4938 19.0352 18.8637C18.7277 18.9461 18.3917 18.9789 17.9999 18.9918L17.9999 20.371C18 20.6062 18 20.846 17.9822 21.0425C17.9651 21.2305 17.9199 21.5852 17.6722 21.8955C17.3872 22.2525 16.9551 22.4602 16.4983 22.4597C16.1013 22.4593 15.7961 22.273 15.6386 22.1689C15.474 22.06 15.2868 21.9102 15.1031 21.7632L12.69 19.8327C12.1714 19.4178 12.0174 19.3007 11.8575 19.219C11.697 19.137 11.5262 19.0771 11.3496 19.0408C11.1737 19.0047 10.9803 19 10.3162 19H7.75858C6.95362 19 6.28927 19 5.74808 18.9558C5.18598 18.9099 4.66928 18.8113 4.18394 18.564C3.43129 18.1805 2.81937 17.5686 2.43588 16.816C2.18859 16.3306 2.09002 15.8139 2.0441 15.2518C1.99988 14.7106 1.99989 14.0463 1.9999 13.2413V7.75868C1.99989 6.95372 1.99988 6.28936 2.0441 5.74818C2.09002 5.18608 2.18859 4.66937 2.43588 4.18404C2.81937 3.43139 3.43129 2.81947 4.18394 2.43598C4.66928 2.18868 5.18598 2.09012 5.74808 2.04419C6.28927 1.99998 6.95364 1.99999 7.7586 2ZM10.5073 7.5C10.5073 6.67157 9.83575 6 9.00732 6C8.1789 6 7.50732 6.67157 7.50732 7.5C7.50732 8.32843 8.1789 9 9.00732 9C9.83575 9 10.5073 8.32843 10.5073 7.5ZM16.6073 11.7001C16.1669 11.3697 15.5426 11.4577 15.2105 11.8959C15.1488 11.9746 15.081 12.0486 15.0119 12.1207C14.8646 12.2744 14.6432 12.4829 14.3566 12.6913C13.7796 13.111 12.9818 13.5001 12.0073 13.5001C11.0328 13.5001 10.235 13.111 9.65799 12.6913C9.37138 12.4829 9.15004 12.2744 9.00274 12.1207C8.93366 12.0486 8.86581 11.9745 8.80418 11.8959C8.472 11.4577 7.84775 11.3697 7.40732 11.7001C6.96549 12.0314 6.87595 12.6582 7.20732 13.1001C7.20479 13.0968 7.21072 13.1043 7.22094 13.1171C7.24532 13.1478 7.29407 13.2091 7.31068 13.2289C7.36932 13.2987 7.45232 13.3934 7.55877 13.5045C7.77084 13.7258 8.08075 14.0172 8.48165 14.3088C9.27958 14.8891 10.4818 15.5001 12.0073 15.5001C13.5328 15.5001 14.735 14.8891 15.533 14.3088C15.9339 14.0172 16.2438 13.7258 16.4559 13.5045C16.5623 13.3934 16.6453 13.2987 16.704 13.2289C16.7333 13.1939 16.7567 13.165 16.7739 13.1432C17.1193 12.6969 17.0729 12.0493 16.6073 11.7001ZM15.0073 6C15.8358 6 16.5073 6.67157 16.5073 7.5C16.5073 8.32843 15.8358 9 15.0073 9C14.1789 9 13.5073 8.32843 13.5073 7.5C13.5073 6.67157 14.1789 6 15.0073 6Z" fill="white"/> </svg> <svg id="closeIcon" style="display:none" width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> <path d="M18 18L6 6M6 18L18 6" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> </svg> - `,n.appendChild(e),document.body.appendChild(n),n.addEventListener("click",t),n.addEventListener("touchend",t),p.draggable){var r=n;var a=p.dragAxis||"both";let s,d,t,l;function o(e){u=!1,"touchstart"===e.type?(s=e.touches[0].clientX-r.offsetLeft,d=e.touches[0].clientY-r.offsetTop,t=e.touches[0].clientX,l=e.touches[0].clientY):(s=e.clientX-r.offsetLeft,d=e.clientY-r.offsetTop),document.addEventListener("mousemove",i),document.addEventListener("touchmove",i,{passive:!1}),document.addEventListener("mouseup",c),document.addEventListener("touchend",c),e.preventDefault()}function i(n){var o="touchmove"===n.type?n.touches[0]:n,i=o.clientX-t,o=o.clientY-l;if(u=8<Math.abs(i)||8<Math.abs(o)?!0:u){r.style.transition="none",r.style.cursor="grabbing";i=document.getElementById(h);i&&(i.style.display="none",y("open"));let e,t;t="touchmove"===n.type?(e=n.touches[0].clientX-s,window.innerHeight-n.touches[0].clientY-d):(e=n.clientX-s,window.innerHeight-n.clientY-d);o=r.getBoundingClientRect(),i=window.innerWidth-o.width,n=window.innerHeight-o.height;"x"!==a&&"both"!==a||r.style.setProperty(`--${m}-left`,Math.max(0,Math.min(e,i))+"px"),"y"!==a&&"both"!==a||r.style.setProperty(`--${m}-bottom`,Math.max(0,Math.min(t,n))+"px")}}function c(){setTimeout(()=>{u=!1},0),r.style.transition="",r.style.cursor="pointer",document.removeEventListener("mousemove",i),document.removeEventListener("touchmove",i),document.removeEventListener("mouseup",c),document.removeEventListener("touchend",c)}r.addEventListener("mousedown",o),r.addEventListener("touchstart",o)}}e.style.display="none",document.body.appendChild(e),2048<t.length&&console.error("The URL is too long, please reduce the number of inputs to prevent the bot from failing to load"),document.getElementById(m)||n()}else console.error(t+" is empty or token is not provided")}function y(e="open"){"open"===e?(document.getElementById("openIcon").style.display="block",document.getElementById("closeIcon").style.display="none"):(document.getElementById("openIcon").style.display="none",document.getElementById("closeIcon").style.display="block")}function l(e){"Escape"===e.key&&(e=document.getElementById(h))&&"none"!==e.style.display&&(e.style.display="none",y("open"))}document.addEventListener("keydown",l),p?.dynamicScript?e():document.body.onload=e})(); \ No newline at end of file + `,n.appendChild(e),document.body.appendChild(n),n.addEventListener("click",t),n.addEventListener("touchend",e=>{e.preventDefault(),t()},{passive:!1}),y.draggable){var a=n;var l=y.dragAxis||"both";let s,d,t,r;function o(e){u=!1,r=("touchstart"===e.type?(s=e.touches[0].clientX-a.offsetLeft,d=e.touches[0].clientY-a.offsetTop,t=e.touches[0].clientX,e.touches[0]):(s=e.clientX-a.offsetLeft,d=e.clientY-a.offsetTop,t=e.clientX,e)).clientY,document.addEventListener("mousemove",i),document.addEventListener("touchmove",i,{passive:!1}),document.addEventListener("mouseup",c),document.addEventListener("touchend",c),e.preventDefault()}function i(n){var o="touchmove"===n.type?n.touches[0]:n,i=o.clientX-t,o=o.clientY-r;if(u=8<Math.abs(i)||8<Math.abs(o)?!0:u){a.style.transition="none",a.style.cursor="grabbing";i=document.getElementById(m);i&&(i.style.display="none",p("open"));let e,t;t="touchmove"===n.type?(e=n.touches[0].clientX-s,window.innerHeight-n.touches[0].clientY-d):(e=n.clientX-s,window.innerHeight-n.clientY-d);o=a.getBoundingClientRect(),i=window.innerWidth-o.width,n=window.innerHeight-o.height;"x"!==l&&"both"!==l||a.style.setProperty(`--${h}-left`,Math.max(0,Math.min(e,i))+"px"),"y"!==l&&"both"!==l||a.style.setProperty(`--${h}-bottom`,Math.max(0,Math.min(t,n))+"px")}}function c(){setTimeout(()=>{u=!1},0),a.style.transition="",a.style.cursor="pointer",document.removeEventListener("mousemove",i),document.removeEventListener("touchmove",i),document.removeEventListener("mouseup",c),document.removeEventListener("touchend",c)}a.addEventListener("mousedown",o),a.addEventListener("touchstart",o)}}n.style.display="none",document.body.appendChild(n),2048<t.length&&console.error("The URL is too long, please reduce the number of inputs to prevent the bot from failing to load"),window.addEventListener("message",e=>{var t,n;e.origin===o&&(t=document.getElementById(m))&&e.source===t.contentWindow&&("dify-chatbot-iframe-ready"===e.data.type&&t.contentWindow?.postMessage({type:"dify-chatbot-config",payload:{isToggledByButton:!0,isDraggable:!!y.draggable}},o),"dify-chatbot-expand-change"===e.data.type)&&(a=!a,n=document.getElementById(m))&&(a?n.style.cssText="\n position: absolute;\n display: flex;\n flex-direction: column;\n justify-content: space-between;\n top: unset;\n right: var(--dify-chatbot-bubble-button-right, 1rem); /* Align with dify-chatbot-bubble-button. */\n bottom: var(--dify-chatbot-bubble-button-bottom, 1rem); /* Align with dify-chatbot-bubble-button. */\n left: unset;\n min-width: 24rem;\n width: 48%;\n max-width: 40rem; /* Match mobile breakpoint*/\n min-height: 43.75rem;\n height: 88%;\n max-height: calc(100vh - 6rem);\n border: none;\n z-index: 2147483640;\n overflow: hidden;\n user-select: none;\n transition-property: width, height;\n transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);\n transition-duration: 150ms;\n ":n.style.cssText=l,d())}),document.getElementById(h)||r()}else console.error(t+" is empty or token is not provided")}function p(e="open"){"open"===e?(document.getElementById("openIcon").style.display="block",document.getElementById("closeIcon").style.display="none"):(document.getElementById("openIcon").style.display="none",document.getElementById("closeIcon").style.display="block")}function b(e){"Escape"===e.key&&(e=document.getElementById(m))&&"none"!==e.style.display&&(e.style.display="none",p("open"))}h,h,document.addEventListener("keydown",b),y?.dynamicScript?e():document.body.onload=e})(); \ No newline at end of file diff --git a/web/public/favicon.ico b/web/public/favicon.ico index 00c1f4fc2b..3087c738ec 100644 Binary files a/web/public/favicon.ico and b/web/public/favicon.ico differ diff --git a/web/public/logo/logo-monochrome-white.svg b/web/public/logo/logo-monochrome-white.svg new file mode 100644 index 0000000000..9a166ba6d8 --- /dev/null +++ b/web/public/logo/logo-monochrome-white.svg @@ -0,0 +1,12 @@ +<svg width="48" height="22" viewBox="0 0 48 22" fill="none" xmlns="http://www.w3.org/2000/svg"> +<g id="White=True"> +<g id="if"> +<path d="M21.2002 4.0695C22.5633 4.0695 23.0666 3.23413 23.0666 2.20309C23.0666 1.17204 22.5623 0.33667 21.2002 0.33667C19.838 0.33667 19.3337 1.17204 19.3337 2.20309C19.3337 3.23413 19.838 4.0695 21.2002 4.0695Z" fill="white"/> +<path d="M27.7336 4.46931V5.66969H24.6668V8.33667H27.7336V15.0037H22.6668V5.67063H15.9998V8.33761H19.7336V15.0046H15.3337V17.6716H35.3337V15.0046H30.6668V8.33761H35.3337V5.67063H30.6668V3.00365H35.3337V0.33667H31.8671C29.5877 0.33667 27.7336 2.19086 27.7336 4.47025V4.46931Z" fill="white"/> +</g> +<g id="Dy"> +<path d="M5.66698 0.335902H0V17.6689H5.66698C12.667 17.6689 14.667 13.6689 14.667 9.00194C14.667 4.33496 12.667 0.334961 5.66698 0.334961V0.335902ZM5.73377 15.0029H3.20038V3.00288H5.73377C9.75823 3.00288 11.4666 4.97842 11.4666 9.00288C11.4666 13.0273 9.75823 15.0029 5.73377 15.0029Z" fill="white"/> +<path d="M44.8335 5.66986L42.1665 14.3368L39.4995 5.66986H36.333L40.2013 16.8815C40.604 18.049 39.9229 19.0029 38.6886 19.0029H37.333V21.6699H39.3255C41.063 21.6699 42.6265 20.5711 43.2145 18.9361L48 5.66986H44.8335Z" fill="white"/> +</g> +</g> +</svg> diff --git a/web/public/logo/logo-site-dark.png b/web/public/logo/logo-site-dark.png index 02041f05b3..3222d40bb4 100644 Binary files a/web/public/logo/logo-site-dark.png and b/web/public/logo/logo-site-dark.png differ diff --git a/web/public/logo/logo-site.png b/web/public/logo/logo-site.png index ee8d8deab7..8c2bb469ff 100644 Binary files a/web/public/logo/logo-site.png and b/web/public/logo/logo-site.png differ diff --git a/web/public/logo/logo.png b/web/public/logo/logo.png deleted file mode 100644 index 0e77ae4814..0000000000 Binary files a/web/public/logo/logo.png and /dev/null differ diff --git a/web/public/logo/logo.svg b/web/public/logo/logo.svg new file mode 100644 index 0000000000..52ed710f52 --- /dev/null +++ b/web/public/logo/logo.svg @@ -0,0 +1,12 @@ +<svg width="48" height="22" viewBox="0 0 48 22" fill="none" xmlns="http://www.w3.org/2000/svg"> +<g id="White=False"> +<g id="if"> +<path d="M21.2002 3.73454C22.5633 3.73454 23.0666 2.89917 23.0666 1.86812C23.0666 0.837081 22.5623 0.00170898 21.2002 0.00170898C19.838 0.00170898 19.3337 0.837081 19.3337 1.86812C19.3337 2.89917 19.838 3.73454 21.2002 3.73454Z" fill="#0033FF"/> +<path d="M27.7336 4.13435V5.33473H24.6668V8.00171H27.7336V14.6687H22.6668V5.33567H15.9998V8.00265H19.7336V14.6696H15.3337V17.3366H35.3337V14.6696H30.6668V8.00265H35.3337V5.33567H30.6668V2.66869H35.3337V0.00170898H31.8671C29.5877 0.00170898 27.7336 1.8559 27.7336 4.13529V4.13435Z" fill="#0033FF"/> +</g> +<g id="Dy"> +<path d="M5.66698 0.000940576H0V17.334H5.66698C12.667 17.334 14.667 13.334 14.667 8.66698C14.667 4 12.667 0 5.66698 0V0.000940576ZM5.73377 14.6679H3.20038V2.66792H5.73377C9.75823 2.66792 11.4666 4.64346 11.4666 8.66792C11.4666 12.6924 9.75823 14.6679 5.73377 14.6679Z" fill="black"/> +<path d="M44.8335 5.3349L42.1665 14.0019L39.4995 5.3349H36.333L40.2013 16.5466C40.604 17.714 39.9229 18.6679 38.6886 18.6679H37.333V21.3349H39.3255C41.063 21.3349 42.6265 20.2361 43.2145 18.6011L48 5.3349H44.8335Z" fill="black"/> +</g> +</g> +</svg> diff --git a/web/service/access-control.ts b/web/service/access-control.ts new file mode 100644 index 0000000000..865909d2f9 --- /dev/null +++ b/web/service/access-control.ts @@ -0,0 +1,90 @@ +import { useInfiniteQuery, useMutation, useQuery, useQueryClient } from '@tanstack/react-query' +import { get, post } from './base' +import { getAppAccessMode, getUserCanAccess } from './share' +import type { AccessControlAccount, AccessControlGroup, AccessMode, Subject } from '@/models/access-control' +import type { App } from '@/types/app' + +const NAME_SPACE = 'access-control' + +export const useAppWhiteListSubjects = (appId: string | undefined, enabled: boolean) => { + return useQuery({ + queryKey: [NAME_SPACE, 'app-whitelist-subjects', appId], + queryFn: () => get<{ groups: AccessControlGroup[]; members: AccessControlAccount[] }>(`/enterprise/webapp/app/subjects?appId=${appId}`), + enabled: !!appId && enabled, + staleTime: 0, + gcTime: 0, + }) +} + +type SearchResults = { + currPage: number + totalPages: number + subjects: Subject[] + hasMore: boolean +} + +export const useSearchForWhiteListCandidates = (query: { keyword?: string; groupId?: AccessControlGroup['id']; resultsPerPage?: number }, enabled: boolean) => { + return useInfiniteQuery({ + queryKey: [NAME_SPACE, 'app-whitelist-candidates', query], + queryFn: ({ pageParam }) => { + const params = new URLSearchParams() + Object.keys(query).forEach((key) => { + const typedKey = key as keyof typeof query + if (query[typedKey]) + params.append(key, `${query[typedKey]}`) + }) + params.append('pageNumber', `${pageParam}`) + return get<SearchResults>(`/enterprise/webapp/app/subject/search?${new URLSearchParams(params).toString()}`) + }, + initialPageParam: 1, + getNextPageParam: (lastPage) => { + if (lastPage.hasMore) + return lastPage.currPage + 1 + return undefined + }, + gcTime: 0, + staleTime: 0, + enabled, + }) +} + +type UpdateAccessModeParams = { + appId: App['id'] + subjects?: Pick<Subject, 'subjectId' | 'subjectType'>[] + accessMode: AccessMode +} + +export const useUpdateAccessMode = () => { + const queryClient = useQueryClient() + return useMutation({ + mutationKey: [NAME_SPACE, 'update-access-mode'], + mutationFn: (params: UpdateAccessModeParams) => { + return post('/enterprise/webapp/app/access-mode', { body: params }) + }, + onSuccess: () => { + queryClient.invalidateQueries({ + queryKey: [NAME_SPACE, 'app-whitelist-subjects'], + }) + }, + }) +} + +export const useGetAppAccessMode = ({ appId, isInstalledApp = true, enabled }: { appId?: string; isInstalledApp?: boolean; enabled: boolean }) => { + return useQuery({ + queryKey: [NAME_SPACE, 'app-access-mode', appId], + queryFn: () => getAppAccessMode(appId!, isInstalledApp), + enabled: !!appId && enabled, + staleTime: 0, + gcTime: 0, + }) +} + +export const useGetUserCanAccessApp = ({ appId, isInstalledApp = true, enabled }: { appId?: string; isInstalledApp?: boolean; enabled: boolean }) => { + return useQuery({ + queryKey: [NAME_SPACE, 'user-can-access-app', appId], + queryFn: () => getUserCanAccess(appId!, isInstalledApp), + enabled: !!appId && enabled, + staleTime: 0, + gcTime: 0, + }) +} diff --git a/web/service/apps.ts b/web/service/apps.ts index 3f7ec7b548..d87a98412e 100644 --- a/web/service/apps.ts +++ b/web/service/apps.ts @@ -1,6 +1,6 @@ import type { Fetcher } from 'swr' import { del, get, patch, post, put } from './base' -import type { ApiKeysListResponse, AppDailyConversationsResponse, AppDailyEndUsersResponse, AppDailyMessagesResponse, AppDetailResponse, AppListResponse, AppSSOResponse, AppStatisticsResponse, AppTemplatesResponse, AppTokenCostsResponse, AppVoicesListResponse, CreateApiKeyResponse, DSLImportMode, DSLImportResponse, GenerationIntroductionResponse, TracingConfig, TracingStatus, UpdateAppModelConfigResponse, UpdateAppSiteCodeResponse, UpdateOpenAIKeyResponse, ValidateOpenAIKeyResponse, WorkflowDailyConversationsResponse } from '@/models/app' +import type { ApiKeysListResponse, AppDailyConversationsResponse, AppDailyEndUsersResponse, AppDailyMessagesResponse, AppDetailResponse, AppListResponse, AppStatisticsResponse, AppTemplatesResponse, AppTokenCostsResponse, AppVoicesListResponse, CreateApiKeyResponse, DSLImportMode, DSLImportResponse, GenerationIntroductionResponse, TracingConfig, TracingStatus, UpdateAppModelConfigResponse, UpdateAppSiteCodeResponse, UpdateOpenAIKeyResponse, ValidateOpenAIKeyResponse, WorkflowDailyConversationsResponse } from '@/models/app' import type { CommonResponse } from '@/models/common' import type { AppIconType, AppMode, ModelConfig } from '@/types/app' import type { TracingProvider } from '@/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/type' @@ -13,13 +13,6 @@ export const fetchAppDetail = ({ url, id }: { url: string; id: string }) => { return get<AppDetailResponse>(`${url}/${id}`) } -export const fetchAppSSO = async ({ appId }: { appId: string }) => { - return get<AppSSOResponse>(`/enterprise/app-setting/sso?appID=${appId}`) -} -export const updateAppSSO = async ({ id, enabled }: { id: string; enabled: boolean }) => { - return post('/enterprise/app-setting/sso', { body: { app_id: id, enabled } }) -} - export const fetchAppTemplates: Fetcher<AppTemplatesResponse, { url: string }> = ({ url }) => { return get<AppTemplatesResponse>(url) } diff --git a/web/service/base.ts b/web/service/base.ts index e3d1dc0ca2..ba398c07a6 100644 --- a/web/service/base.ts +++ b/web/service/base.ts @@ -108,8 +108,14 @@ function unicodeToChar(text: string) { }) } -function requiredWebSSOLogin() { - globalThis.location.href = `/webapp-signin?redirect_url=${globalThis.location.pathname}` +function requiredWebSSOLogin(message?: string, code?: number) { + const params = new URLSearchParams() + params.append('redirect_url', globalThis.location.pathname) + if (message) + params.append('message', message) + if (code) + params.append('code', String(code)) + globalThis.location.href = `/webapp-signin?${params.toString()}` } export function format(text: string) { @@ -397,8 +403,13 @@ export const ssePost = async ( }).catch(() => { res.json().then((data: any) => { if (isPublicAPI) { - if (data.code === 'web_sso_auth_required') + if (data.code === 'web_app_access_denied') + requiredWebSSOLogin(data.message, 403) + + if (data.code === 'web_sso_auth_required') { + removeAccessToken() requiredWebSSOLogin() + } if (data.code === 'unauthorized') { removeAccessToken() @@ -426,29 +437,29 @@ export const ssePost = async ( } onData?.(str, isFirstMessage, moreInfo) }, - onCompleted, - onThought, - onMessageEnd, - onMessageReplace, - onFile, - onWorkflowStarted, - onWorkflowFinished, - onNodeStarted, - onNodeFinished, - onIterationStart, - onIterationNext, - onIterationFinish, - onLoopStart, - onLoopNext, - onLoopFinish, - onNodeRetry, - onParallelBranchStarted, - onParallelBranchFinished, - onTextChunk, - onTTSChunk, - onTTSEnd, - onTextReplace, - onAgentLog, + onCompleted, + onThought, + onMessageEnd, + onMessageReplace, + onFile, + onWorkflowStarted, + onWorkflowFinished, + onNodeStarted, + onNodeFinished, + onIterationStart, + onIterationNext, + onIterationFinish, + onLoopStart, + onLoopNext, + onLoopFinish, + onNodeRetry, + onParallelBranchStarted, + onParallelBranchFinished, + onTextChunk, + onTTSChunk, + onTTSEnd, + onTextReplace, + onAgentLog, ) }).catch((e) => { if (e.toString() !== 'AbortError: The user aborted a request.' && !e.toString().errorMessage.includes('TypeError: Cannot assign to read only property')) @@ -475,7 +486,12 @@ export const request = async<T>(url: string, options = {}, otherOptions?: IOther // special code const { code, message } = errRespData // webapp sso + if (code === 'web_app_access_denied') { + requiredWebSSOLogin(message, 403) + return Promise.reject(err) + } if (code === 'web_sso_auth_required') { + removeAccessToken() requiredWebSSOLogin() return Promise.reject(err) } diff --git a/web/service/common.ts b/web/service/common.ts index e76cfb4196..700cd4bf51 100644 --- a/web/service/common.ts +++ b/web/service/common.ts @@ -52,6 +52,9 @@ type LoginResponse = LoginSuccess | LoginFail export const login: Fetcher<LoginResponse, { url: string; body: Record<string, any> }> = ({ url, body }) => { return post(url, { body }) as Promise<LoginResponse> } +export const webAppLogin: Fetcher<LoginResponse, { url: string; body: Record<string, any> }> = ({ url, body }) => { + return post(url, { body }, { isPublicAPI: true }) as Promise<LoginResponse> +} export const fetchNewToken: Fetcher<CommonResponse & { data: { access_token: string; refresh_token: string } }, { body: Record<string, any> }> = ({ body }) => { return post('/refresh-token', { body }) as Promise<CommonResponse & { data: { access_token: string; refresh_token: string } }> @@ -324,6 +327,16 @@ export const verifyForgotPasswordToken: Fetcher<CommonResponse & { is_valid: boo export const changePasswordWithToken: Fetcher<CommonResponse, { url: string; body: { token: string; new_password: string; password_confirm: string } }> = ({ url, body }) => post<CommonResponse>(url, { body }) +export const sendWebAppForgotPasswordEmail: Fetcher<CommonResponse & { data: string }, { url: string; body: { email: string } }> = ({ url, body }) => + post<CommonResponse & { data: string }>(url, { body }, { isPublicAPI: true }) + +export const verifyWebAppForgotPasswordToken: Fetcher<CommonResponse & { is_valid: boolean; email: string }, { url: string; body: { token: string } }> = ({ url, body }) => { + return post(url, { body }, { isPublicAPI: true }) as Promise<CommonResponse & { is_valid: boolean; email: string }> +} + +export const changeWebAppPasswordWithToken: Fetcher<CommonResponse, { url: string; body: { token: string; new_password: string; password_confirm: string } }> = ({ url, body }) => + post<CommonResponse>(url, { body }, { isPublicAPI: true }) + export const uploadRemoteFileInfo = (url: string, isPublic?: boolean) => { return post<{ id: string; name: string; size: number; mime_type: string; url: string }>('/remote-files/upload', { body: { url } }, { isPublicAPI: isPublic }) } @@ -340,6 +353,18 @@ export const sendResetPasswordCode = (email: string, language = 'en-US') => export const verifyResetPasswordCode = (body: { email: string; code: string; token: string }) => post<CommonResponse & { is_valid: boolean; token: string }>('/forgot-password/validity', { body }) +export const sendWebAppEMailLoginCode = (email: string, language = 'en-US') => + post<CommonResponse & { data: string }>('/email-code-login', { body: { email, language } }, { isPublicAPI: true }) + +export const webAppEmailLoginWithCode = (data: { email: string; code: string; token: string }) => + post<LoginResponse>('/email-code-login/validity', { body: data }, { isPublicAPI: true }) + +export const sendWebAppResetPasswordCode = (email: string, language = 'en-US') => + post<CommonResponse & { data: string; message?: string; code?: string }>('/forgot-password', { body: { email, language } }, { isPublicAPI: true }) + +export const verifyWebAppResetPasswordCode = (body: { email: string; code: string; token: string }) => + post<CommonResponse & { is_valid: boolean; token: string }>('/forgot-password/validity', { body }, { isPublicAPI: true }) + export const sendDeleteAccountCode = () => get<CommonResponse & { data: string }>('/account/delete/verify') diff --git a/web/service/fetch.ts b/web/service/fetch.ts index 5d09256f1d..713b34cdb9 100644 --- a/web/service/fetch.ts +++ b/web/service/fetch.ts @@ -135,9 +135,9 @@ async function base<T>(url: string, options: FetchOptionType = {}, otherOptions: let base: string if (isMarketplaceAPI) base = MARKETPLACE_API_PREFIX - else if (isPublicAPI) + else if (isPublicAPI) base = PUBLIC_API_PREFIX - else + else base = API_PREFIX if (getAbortController) { diff --git a/web/service/share.ts b/web/service/share.ts index 7cc292eb09..6a2a7e5b16 100644 --- a/web/service/share.ts +++ b/web/service/share.ts @@ -33,7 +33,7 @@ import type { ConversationItem, } from '@/models/share' import type { ChatConfig } from '@/app/components/base/chat/types' -import type { SystemFeatures } from '@/types/feature' +import type { AccessMode } from '@/models/access-control' function getAction(action: 'get' | 'post' | 'del' | 'patch', isInstalledApp: boolean) { switch (action) { @@ -186,10 +186,6 @@ export const fetchAppParams = async (isInstalledApp: boolean, installedAppId = ' return (getAction('get', isInstalledApp))(getUrl('parameters', isInstalledApp, installedAppId)) as Promise<ChatConfig> } -export const fetchSystemFeatures = async () => { - return (getAction('get', false))(getUrl('system-features', false, '')) as Promise<SystemFeatures> -} - export const fetchWebSAMLSSOUrl = async (appCode: string, redirectUrl: string) => { return (getAction('get', false))(getUrl('/enterprise/sso/saml/login', false, ''), { params: { @@ -218,6 +214,34 @@ export const fetchWebOAuth2SSOUrl = async (appCode: string, redirectUrl: string) }) as Promise<{ url: string }> } +export const fetchMembersSAMLSSOUrl = async (appCode: string, redirectUrl: string) => { + return (getAction('get', false))(getUrl('/enterprise/sso/members/saml/login', false, ''), { + params: { + app_code: appCode, + redirect_url: redirectUrl, + }, + }) as Promise<{ url: string }> +} + +export const fetchMembersOIDCSSOUrl = async (appCode: string, redirectUrl: string) => { + return (getAction('get', false))(getUrl('/enterprise/sso/members/oidc/login', false, ''), { + params: { + app_code: appCode, + redirect_url: redirectUrl, + }, + + }) as Promise<{ url: string }> +} + +export const fetchMembersOAuth2SSOUrl = async (appCode: string, redirectUrl: string) => { + return (getAction('get', false))(getUrl('/enterprise/sso/members/oauth2/login', false, ''), { + params: { + app_code: appCode, + redirect_url: redirectUrl, + }, + }) as Promise<{ url: string }> +} + export const fetchAppMeta = async (isInstalledApp: boolean, installedAppId = '') => { return (getAction('get', isInstalledApp))(getUrl('meta', isInstalledApp, installedAppId)) as Promise<AppMeta> } @@ -262,9 +286,30 @@ export const textToAudioStream = (url: string, isPublicAPI: boolean, header: { c return (getAction('post', !isPublicAPI))(url, { body, header }, { needAllResponseContent: true }) } -export const fetchAccessToken = async (appCode: string, userId?: string) => { +export const fetchAccessToken = async ({ appCode, userId, webAppAccessToken }: { appCode: string, userId?: string, webAppAccessToken?: string | null }) => { const headers = new Headers() headers.append('X-App-Code', appCode) - const url = userId ? `/passport?user_id=${encodeURIComponent(userId)}` : '/passport' + const params = new URLSearchParams() + webAppAccessToken && params.append('web_app_access_token', webAppAccessToken) + userId && params.append('user_id', userId) + const url = `/passport?${params.toString()}` return get(url, { headers }) as Promise<{ access_token: string }> } + +export const getAppAccessMode = (appId: string, isInstalledApp: boolean) => { + if (isInstalledApp) + return consoleGet<{ accessMode: AccessMode }>(`/enterprise/webapp/app/access-mode?appId=${appId}`) + + return get<{ accessMode: AccessMode }>(`/webapp/access-mode?appId=${appId}`) +} + +export const getUserCanAccess = (appId: string, isInstalledApp: boolean) => { + if (isInstalledApp) + return consoleGet<{ result: boolean }>(`/enterprise/webapp/permission?appId=${appId}`) + + return get<{ result: boolean }>(`/webapp/permission?appId=${appId}`) +} + +export const getAppAccessModeByAppCode = (appCode: string) => { + return get<{ accessMode: AccessMode }>(`/webapp/access-mode?appCode=${appCode}`) +} diff --git a/web/service/use-plugins.ts b/web/service/use-plugins.ts index 13a494b50d..ecfdbcf993 100644 --- a/web/service/use-plugins.ts +++ b/web/service/use-plugins.ts @@ -10,7 +10,7 @@ import type { GitHubItemAndMarketPlaceDependency, InstallPackageResponse, InstalledLatestVersionResponse, - InstalledPluginListResponse, + InstalledPluginListWithTotalResponse, PackageDependency, Permissions, Plugin, @@ -33,6 +33,7 @@ import type { import { get, getMarketplace, post, postMarketplace } from './base' import type { MutateOptions, QueryOptions } from '@tanstack/react-query' import { + useInfiniteQuery, useMutation, useQuery, useQueryClient, @@ -65,13 +66,56 @@ export const useCheckInstalled = ({ }) } -export const useInstalledPluginList = (disable?: boolean) => { - return useQuery<InstalledPluginListResponse>({ - queryKey: useInstalledPluginListKey, - queryFn: () => get<InstalledPluginListResponse>('/workspaces/current/plugin/list'), +export const useInstalledPluginList = (disable?: boolean, pageSize = 100) => { + const fetchPlugins = async ({ pageParam = 1 }) => { + const response = await get<InstalledPluginListWithTotalResponse>( + `/workspaces/current/plugin/list?page=${pageParam}&page_size=${pageSize}`, + ) + return response + } + + const { + data, + error, + fetchNextPage, + hasNextPage, + isFetchingNextPage, + isLoading, + isSuccess, + } = useInfiniteQuery({ enabled: !disable, - initialData: !disable ? undefined : { plugins: [] }, + queryKey: useInstalledPluginListKey, + queryFn: fetchPlugins, + getNextPageParam: (lastPage, pages) => { + const totalItems = lastPage.total + const currentPage = pages.length + const itemsLoaded = currentPage * pageSize + + if (itemsLoaded >= totalItems) + return + + return currentPage + 1 + }, + initialPageParam: 1, }) + + const plugins = data?.pages.flatMap(page => page.plugins) ?? [] + const total = data?.pages[0].total ?? 0 + + return { + data: disable ? undefined : { + plugins, + total, + }, + isLastPage: !hasNextPage, + loadNextPage: () => { + fetchNextPage() + }, + isLoading, + isFetching: isFetchingNextPage, + error, + isSuccess, + } } export const useInstalledLatestVersion = (pluginIds: string[]) => { diff --git a/web/service/use-share.ts b/web/service/use-share.ts new file mode 100644 index 0000000000..b8f96f6cc5 --- /dev/null +++ b/web/service/use-share.ts @@ -0,0 +1,17 @@ +import { useQuery } from '@tanstack/react-query' +import { getAppAccessModeByAppCode } from './share' + +const NAME_SPACE = 'webapp' + +export const useAppAccessModeByCode = (code: string | null) => { + return useQuery({ + queryKey: [NAME_SPACE, 'appAccessMode', code], + queryFn: () => { + if (!code) + return null + + return getAppAccessModeByAppCode(code) + }, + enabled: !!code, + }) +} diff --git a/web/themes/dark.css b/web/themes/dark.css index 5b191d1a3d..fbfacf1f45 100644 --- a/web/themes/dark.css +++ b/web/themes/dark.css @@ -386,6 +386,7 @@ html[data-theme="dark"] { --color-background-gradient-bg-fill-chat-bg-2: #1d1d20; --color-background-gradient-bg-fill-chat-bubble-bg-1: #c8ceda14; --color-background-gradient-bg-fill-chat-bubble-bg-2: #c8ceda05; + --color-background-gradient-bg-fill-chat-bubble-bg-3: #a5bddb; --color-background-gradient-bg-fill-debug-bg-1: #c8ceda14; --color-background-gradient-bg-fill-debug-bg-2: #18181b0a; @@ -736,4 +737,4 @@ html[data-theme="dark"] { --color-saas-background: #0b0b0e; --color-saas-pricing-grid-bg: #c8ceda33; -} \ No newline at end of file +} diff --git a/web/themes/light.css b/web/themes/light.css index 9c0952433d..8a3f9d9d48 100644 --- a/web/themes/light.css +++ b/web/themes/light.css @@ -386,6 +386,7 @@ html[data-theme="light"] { --color-background-gradient-bg-fill-chat-bg-2: #f2f4f7; --color-background-gradient-bg-fill-chat-bubble-bg-1: #ffffff; --color-background-gradient-bg-fill-chat-bubble-bg-2: #ffffff99; + --color-background-gradient-bg-fill-chat-bubble-bg-3: #e1effe; --color-background-gradient-bg-fill-debug-bg-1: #ffffff00; --color-background-gradient-bg-fill-debug-bg-2: #c8ceda24; @@ -736,4 +737,4 @@ html[data-theme="light"] { --color-saas-background: #fcfcfd; --color-saas-pricing-grid-bg: #c8ceda80; -} \ No newline at end of file +} diff --git a/web/themes/manual-dark.css b/web/themes/manual-dark.css index 3c2b143b3c..ed5f12bbc4 100644 --- a/web/themes/manual-dark.css +++ b/web/themes/manual-dark.css @@ -1,9 +1,4 @@ html[data-theme="dark"] { - --color-premium-yearly-tip-text-background: linear-gradient(91deg, #FDB022 2.18%, #F79009 108.79%); - --color-premium-badge-background: linear-gradient(95deg, rgba(103, 111, 131, 0.90) 0%, rgba(73, 84, 100, 0.90) 105.58%), var(--util-colors-gray-gray-200, #18222F); - --color-premium-text-background: linear-gradient(92deg, rgba(249, 250, 251, 0.95) 0%, rgba(233, 235, 240, 0.95) 97.78%); - --color-price-enterprise-background: linear-gradient(180deg, rgba(185, 211, 234, 0.00) 0%, rgba(180, 209, 234, 0.92) 100%); - --color-grid-mask-background: linear-gradient(0deg, rgba(0, 0, 0, 0.00) 0%, rgba(24, 24, 25, 0.1) 62.25%, rgba(24, 24, 25, 0.10) 100%); --color-chatbot-bg: linear-gradient(180deg, rgba(34, 34, 37, 0.9) 0%, rgba(29, 29, 32, 0.9) 90.48%); @@ -60,5 +55,12 @@ html[data-theme="dark"] { --mask-top2bottom-gray-50-to-transparent: linear-gradient(180deg, rgba(24, 24, 27, 0.08) 0%, rgba(0, 0, 0, 0) 100%); - --color-line-divider-bg: linear-gradient(90deg, rgba(200, 206, 218, 0.14) 0%, rgba(0, 0, 0, 0) 100%, ); -} \ No newline at end of file + --color-line-divider-bg: linear-gradient(90deg, rgba(200, 206, 218, 0.14) 0%, rgba(0, 0, 0, 0) 100%); + --color-access-app-icon-mask-bg: linear-gradient(135deg, rgba(255, 255, 255, 0.2) 0%, rgba(255, 255, 255, 0.03) 100%); + --color-premium-yearly-tip-text-background: linear-gradient(91deg, #FDB022 2.18%, #F79009 108.79%); + --color-premium-badge-background: linear-gradient(95deg, rgba(103, 111, 131, 0.90) 0%, rgba(73, 84, 100, 0.90) 105.58%), var(--util-colors-gray-gray-200, #18222F); + --color-premium-text-background: linear-gradient(92deg, rgba(249, 250, 251, 0.95) 0%, rgba(233, 235, 240, 0.95) 97.78%); + --color-premium-badge-border-highlight-color: #ffffff33; + --color-price-enterprise-background: linear-gradient(180deg, rgba(185, 211, 234, 0.00) 0%, rgba(180, 209, 234, 0.92) 100%); + --color-grid-mask-background: linear-gradient(0deg, rgba(0, 0, 0, 0.00) 0%, rgba(24, 24, 25, 0.1) 62.25%, rgba(24, 24, 25, 0.10) 100%); +} diff --git a/web/themes/manual-light.css b/web/themes/manual-light.css index 92473320bc..c20155036e 100644 --- a/web/themes/manual-light.css +++ b/web/themes/manual-light.css @@ -1,64 +1,66 @@ html[data-theme="light"] { - --color-premium-yearly-tip-text-background: linear-gradient(91deg, #F79009 2.18%, #DC6803 108.79%); - --color-premium-badge-background: linear-gradient(95deg, rgba(152, 162, 178, 0.90) 0%, rgba(103, 111, 131, 0.90) 105.58%); - --color-premium-text-background: linear-gradient(92deg, rgba(252, 252, 253, 0.95) 0%, rgba(242, 244, 247, 0.95) 97.78%); - --color-price-enterprise-background: linear-gradient(180deg, rgba(185, 211, 234, 0.00) 0%, rgba(180, 209, 234, 0.92) 100%); - --color-grid-mask-background: linear-gradient(0deg, #FFF 0%, rgba(217, 217, 217, 0.10) 62.25%, rgba(217, 217, 217, 0.10) 100%); - --color-chatbot-bg: linear-gradient(180deg, - rgba(249, 250, 251, 0.9) 0%, - rgba(242, 244, 247, 0.9) 90.48%); - --color-chat-bubble-bg: linear-gradient(180deg, - #fff 0%, - rgba(255, 255, 255, 0.6) 100%); - --color-chat-input-mask: linear-gradient(180deg, - rgba(255, 255, 255, 0.01) 0%, - #F2F4F7 100%); - --color-workflow-process-bg: linear-gradient(90deg, - rgba(200, 206, 218, 0.2) 0%, - rgba(200, 206, 218, 0.04) 100%); - --color-workflow-run-failed-bg: linear-gradient(98deg, - rgba(240, 68, 56, 0.10) 0%, - rgba(255, 255, 255, 0) 26.01%); - --color-workflow-batch-failed-bg: linear-gradient(92deg, - rgba(240, 68, 56, 0.25) 0%, - rgba(255, 255, 255, 0) 100%); - --color-marketplace-divider-bg: linear-gradient(90deg, - rgba(16, 24, 40, 0.08) 0%, - rgba(255, 255, 255, 0) 100%); - --color-marketplace-plugin-empty: linear-gradient(180deg, - rgba(255, 255, 255, 0) 0%, - #fcfcfd 100%); - --color-toast-success-bg: linear-gradient(92deg, - rgba(23, 178, 106, 0.25) 0%, - rgba(255, 255, 255, 0) 100%); - --color-toast-warning-bg: linear-gradient(92deg, - rgba(247, 144, 9, 0.25) 0%, - rgba(255, 255, 255, 0) 100%); - --color-toast-error-bg: linear-gradient(92deg, - rgba(240, 68, 56, 0.25) 0%, - rgba(255, 255, 255, 0) 100%); - --color-toast-info-bg: linear-gradient(92deg, - rgba(11, 165, 236, 0.25) 0%); - --color-account-teams-bg: linear-gradient(271deg, - rgba(249, 250, 251, 0.9) -0.1%, - rgba(242, 244, 247, 0.9) 98.26%); - --color-app-detail-bg: linear-gradient(169deg, - #F2F4F7 1.18%, - #F9FAFB 99.52%); - --color-app-detail-overlay-bg: linear-gradient(270deg, - rgba(0, 0, 0, 0.00) 0%, - rgba(16, 24, 40, 0.01) 8%, - rgba(16, 24, 40, 0.18) 100%); - --color-dataset-chunk-process-success-bg: linear-gradient(92deg, rgba(23, 178, 106, 0.25) 0%, rgba(255, 255, 255, 0.00) 100%); - --color-dataset-chunk-process-error-bg: linear-gradient(92deg, rgba(240, 68, 56, 0.25) 0%, rgba(255, 255, 255, 0.00) 100%); - --color-dataset-chunk-detail-card-hover-bg: linear-gradient(180deg, #F2F4F7 0%, #F9FAFB 100%); - --color-dataset-child-chunk-expand-btn-bg: linear-gradient(90deg, rgba(200, 206, 218, 0.20) 0%, rgba(200, 206, 218, 0.04) 100%); - --color-dataset-option-card-blue-gradient: linear-gradient(90deg, #F2F4F7 0%, #F9FAFB 100%); - --color-dataset-option-card-purple-gradient: linear-gradient(90deg, #F0EEFA 0%, #F9FAFB 100%); - --color-dataset-option-card-orange-gradient: linear-gradient(90deg, #F8F2EE 0%, #F9FAFB 100%); - --color-dataset-chunk-list-mask-bg: linear-gradient(180deg, rgba(255, 255, 255, 0.00) 0%, #FCFCFD 100%); - --mask-top2bottom-gray-50-to-transparent: linear-gradient(180deg, - rgba(200, 206, 218, 0.2) 0%, - rgba(255, 255, 255, 0) 100%); - --color-line-divider-bg: linear-gradient(90deg, rgba(16, 24, 40, 0.08) 0%, rgba(255, 255, 255, 0) 100%); -} \ No newline at end of file + --color-chatbot-bg: linear-gradient(180deg, + rgba(249, 250, 251, 0.9) 0%, + rgba(242, 244, 247, 0.9) 90.48%); + --color-chat-bubble-bg: linear-gradient(180deg, + #fff 0%, + rgba(255, 255, 255, 0.6) 100%); + --color-chat-input-mask: linear-gradient(180deg, + rgba(255, 255, 255, 0.01) 0%, + #F2F4F7 100%); + --color-workflow-process-bg: linear-gradient(90deg, + rgba(200, 206, 218, 0.2) 0%, + rgba(200, 206, 218, 0.04) 100%); + --color-workflow-run-failed-bg: linear-gradient(98deg, + rgba(240, 68, 56, 0.10) 0%, + rgba(255, 255, 255, 0) 26.01%); + --color-workflow-batch-failed-bg: linear-gradient(92deg, + rgba(240, 68, 56, 0.25) 0%, + rgba(255, 255, 255, 0) 100%); + --color-marketplace-divider-bg: linear-gradient(90deg, + rgba(16, 24, 40, 0.08) 0%, + rgba(255, 255, 255, 0) 100%); + --color-marketplace-plugin-empty: linear-gradient(180deg, + rgba(255, 255, 255, 0) 0%, + #fcfcfd 100%); + --color-toast-success-bg: linear-gradient(92deg, + rgba(23, 178, 106, 0.25) 0%, + rgba(255, 255, 255, 0) 100%); + --color-toast-warning-bg: linear-gradient(92deg, + rgba(247, 144, 9, 0.25) 0%, + rgba(255, 255, 255, 0) 100%); + --color-toast-error-bg: linear-gradient(92deg, + rgba(240, 68, 56, 0.25) 0%, + rgba(255, 255, 255, 0) 100%); + --color-toast-info-bg: linear-gradient(92deg, + rgba(11, 165, 236, 0.25) 0%); + --color-account-teams-bg: linear-gradient(271deg, + rgba(249, 250, 251, 0.9) -0.1%, + rgba(242, 244, 247, 0.9) 98.26%); + --color-app-detail-bg: linear-gradient(169deg, + #F2F4F7 1.18%, + #F9FAFB 99.52%); + --color-app-detail-overlay-bg: linear-gradient(270deg, + rgba(0, 0, 0, 0.00) 0%, + rgba(16, 24, 40, 0.01) 8%, + rgba(16, 24, 40, 0.18) 100%); + --color-dataset-chunk-process-success-bg: linear-gradient(92deg, rgba(23, 178, 106, 0.25) 0%, rgba(255, 255, 255, 0.00) 100%); + --color-dataset-chunk-process-error-bg: linear-gradient(92deg, rgba(240, 68, 56, 0.25) 0%, rgba(255, 255, 255, 0.00) 100%); + --color-dataset-chunk-detail-card-hover-bg: linear-gradient(180deg, #F2F4F7 0%, #F9FAFB 100%); + --color-dataset-child-chunk-expand-btn-bg: linear-gradient(90deg, rgba(200, 206, 218, 0.20) 0%, rgba(200, 206, 218, 0.04) 100%); + --color-dataset-option-card-blue-gradient: linear-gradient(90deg, #F2F4F7 0%, #F9FAFB 100%); + --color-dataset-option-card-purple-gradient: linear-gradient(90deg, #F0EEFA 0%, #F9FAFB 100%); + --color-dataset-option-card-orange-gradient: linear-gradient(90deg, #F8F2EE 0%, #F9FAFB 100%); + --color-dataset-chunk-list-mask-bg: linear-gradient(180deg, rgba(255, 255, 255, 0.00) 0%, #FCFCFD 100%); + --mask-top2bottom-gray-50-to-transparent: linear-gradient(180deg, + rgba(200, 206, 218, 0.2) 0%, + rgba(255, 255, 255, 0) 100%); + --color-line-divider-bg: linear-gradient(90deg, rgba(16, 24, 40, 0.08) 0%, rgba(255, 255, 255, 0) 100%); + --color-access-app-icon-mask-bg: linear-gradient(135deg, rgba(255, 255, 255, 0.12) 0%, rgba(255, 255, 255, 0.08) 100%); + --color-premium-yearly-tip-text-background: linear-gradient(91deg, #F79009 2.18%, #DC6803 108.79%); + --color-premium-badge-background: linear-gradient(95deg, rgba(152, 162, 178, 0.90) 0%, rgba(103, 111, 131, 0.90) 105.58%); + --color-premium-text-background: linear-gradient(92deg, rgba(252, 252, 253, 0.95) 0%, rgba(242, 244, 247, 0.95) 97.78%); + --color-premium-badge-border-highlight-color: #fffffff2; + --color-price-enterprise-background: linear-gradient(180deg, rgba(185, 211, 234, 0.00) 0%, rgba(180, 209, 234, 0.92) 100%); + --color-grid-mask-background: linear-gradient(0deg, #FFF 0%, rgba(217, 217, 217, 0.10) 62.25%, rgba(217, 217, 217, 0.10) 100%); +} diff --git a/web/themes/markdown-dark.css b/web/themes/markdown-dark.css index 7207b3e648..3dc4728490 100644 --- a/web/themes/markdown-dark.css +++ b/web/themes/markdown-dark.css @@ -41,4 +41,4 @@ html[data-theme="dark"] { --color-accent-emphasis: #0969da; --color-attention-subtle: #fff8c5; --color-danger-fg: #cf222e; - } \ No newline at end of file + } diff --git a/web/themes/markdown-light.css b/web/themes/markdown-light.css index 8f991b8920..7399325884 100644 --- a/web/themes/markdown-light.css +++ b/web/themes/markdown-light.css @@ -41,4 +41,4 @@ html[data-theme="light"] { --color-accent-emphasis: #0969da; --color-attention-subtle: #fff8c5; --color-danger-fg: #cf222e; - } \ No newline at end of file + } diff --git a/web/themes/tailwind-theme-var-define.ts b/web/themes/tailwind-theme-var-define.ts index 62c0ed82c7..11189441ee 100644 --- a/web/themes/tailwind-theme-var-define.ts +++ b/web/themes/tailwind-theme-var-define.ts @@ -386,6 +386,7 @@ const vars = { 'background-gradient-bg-fill-chat-bg-2': 'var(--color-background-gradient-bg-fill-chat-bg-2)', 'background-gradient-bg-fill-chat-bubble-bg-1': 'var(--color-background-gradient-bg-fill-chat-bubble-bg-1)', 'background-gradient-bg-fill-chat-bubble-bg-2': 'var(--color-background-gradient-bg-fill-chat-bubble-bg-2)', + 'background-gradient-bg-fill-chat-bubble-bg-3': 'var(--color-background-gradient-bg-fill-chat-bubble-bg-3)', 'background-gradient-bg-fill-debug-bg-1': 'var(--color-background-gradient-bg-fill-debug-bg-1)', 'background-gradient-bg-fill-debug-bg-2': 'var(--color-background-gradient-bg-fill-debug-bg-2)', diff --git a/web/types/app.ts b/web/types/app.ts index 39f011dcaa..e4227adbe9 100644 --- a/web/types/app.ts +++ b/web/types/app.ts @@ -7,6 +7,7 @@ import type { WeightedScoreEnum, } from '@/models/datasets' import type { UploadFileSetting } from '@/app/components/workflow/types' +import type { AccessMode } from '@/models/access-control' export enum Theme { light = 'light', @@ -315,6 +316,8 @@ export type App = { name: string /** Description */ description: string + /** Author Name */ + author_name: string; /** * Icon Type @@ -347,6 +350,8 @@ export type App = { app_model_config: ModelConfig /** Timestamp of creation */ created_at: number + /** Timestamp of update */ + updated_at: number /** Web Application Configuration */ site: SiteConfig /** api site url */ @@ -359,6 +364,8 @@ export type App = { updated_at: number updated_by?: string } + /** access control */ + access_mode: AccessMode } export type AppSSO = { diff --git a/web/types/feature.ts b/web/types/feature.ts index 3d7763bf46..cc945754b1 100644 --- a/web/types/feature.ts +++ b/web/types/feature.ts @@ -23,7 +23,6 @@ export type SystemFeatures = { sso_enforced_for_signin_protocol: SSOProtocol | '' sso_enforced_for_web: boolean sso_enforced_for_web_protocol: SSOProtocol | '' - enable_web_sso_switch_component: boolean enable_marketplace: boolean enable_email_code_login: boolean enable_email_password_login: boolean @@ -32,6 +31,22 @@ export type SystemFeatures = { is_allow_register: boolean is_email_setup: boolean license: License + branding: { + enabled: boolean + login_page_logo: string + workspace_logo: string + favicon: string + application_title: string + } + webapp_auth: { + enabled: boolean + allow_sso: boolean + sso_config: { + protocol: SSOProtocol | '' + } + allow_email_code_login: boolean + allow_email_password_login: boolean + } } export const defaultSystemFeatures: SystemFeatures = { @@ -39,7 +54,6 @@ export const defaultSystemFeatures: SystemFeatures = { sso_enforced_for_signin_protocol: '', sso_enforced_for_web: false, sso_enforced_for_web_protocol: '', - enable_web_sso_switch_component: false, enable_marketplace: false, enable_email_code_login: false, enable_email_password_login: false, @@ -51,4 +65,20 @@ export const defaultSystemFeatures: SystemFeatures = { status: LicenseStatus.NONE, expired_at: '', }, + branding: { + enabled: false, + login_page_logo: '', + workspace_logo: '', + favicon: '', + application_title: 'test title', + }, + webapp_auth: { + enabled: false, + allow_sso: false, + sso_config: { + protocol: '', + }, + allow_email_code_login: false, + allow_email_password_login: false, + }, } diff --git a/web/utils/timezone.json b/web/utils/timezone.json index f5e578d5dd..80e07ac416 100644 --- a/web/utils/timezone.json +++ b/web/utils/timezone.json @@ -1271,4 +1271,4 @@ "name": "+14:00 Line Islands Time - Kiritimati", "value": "Pacific/Kiritimati" } -] \ No newline at end of file +] diff --git a/web/utils/var-basePath.js b/web/utils/var-basePath.js index 763392086d..07b7f7581b 100644 --- a/web/utils/var-basePath.js +++ b/web/utils/var-basePath.js @@ -2,4 +2,5 @@ // same as the one exported from var.ts module.exports = { basePath: '', + assetPrefix: '', }